Bottle 框架源码阅读

写这篇文章最开心的一点是终于可以用这张截图了: 相比名声在外的 Django/Flask/FastAPI,Bottle 可以说是非常不起眼了,甚至很多人并不知道它的存在。其实在很多方面,这个框架都极其优秀: 速度:截止到 2022-04-13,Bottle 在一众 Python Web 框架的测评中名列第二,要知道这可是十年以上的老前辈了。 易用性:Bottle 早在 Flask 之前就使用了装饰器来定义路由,此外还有全局可用的 Request/Response 对象。 文档:不仅将框架本身的使用讲得很清楚,还总结了很多 Web 场景下的解决方案。 代码质量:虽然为了 Python 2 做了不少兼容,但是代码很精炼,而且 Pythonic。 其他:Bottle 坚持单模块以及无第三方库依赖;仓库仍然在积极维护中。 换作几年前,我会一开始就使用并将 Bottle 研究透彻,而不是让自己淹没在 Django 浩瀚如烟的文档中。下面开始梳理 Bottle 源码的阅读理解。因为代码量不大,所以就直接看最新的版本了:0.11.1 - 5a6c620。 Web 框架的基本元素 参考 The Hitchhiker’s Guide to Python 的说法,一个 Web 框架要满足的基本功能: URL Routing Request and Response Objects Template Engine Development Web Server 从后端的角度来讲更重要的是 1、2、4 三项,其中 1 负责转发请求到对应的视图函数,2 是对 HTTP 协议元素的解析处理,而 4 决定了服务的部署方式和基础性能。...

04-22 · 6 min

Python 中的 TLS 是如何实现的

TLS(Thread Local Storage),或者说 Threadlocal,可以说是一种并发编程的常用模式,既实现了线程之间的资源隔离,又满足了全局变量的使用。 从 TLS 出发,这篇文章研究了 Python 中的 Threadlocal 是如何实现的,比如自带的 threading.local,再比如 Flask 框架中 Local 对象。 Why Threadlocal 先思考一下为什么要用 Threadlocal,这就不得不提到线程安全。Race condition 说到底是因为数据共享和非原子操作,这可以体现在函数的两种基本写法:一种是显式地传参(参数对象也可能变化?这也是为什么最好不要传递可变对象),没有共享自然安全;另一种就是全局对象,这么写既简化了函数签名,代码也比较清晰,缺点就是很容易出现线程不安全的问题,所以经常会和锁配合使用。 而 Threadlocal 就结合了两者的优点,在共享全局变量的同时,保证每个线程操作的都是自己独有的数据对象。 对比一下 Django 和 Flask 两大框架就会发现,前者总是在视图函数中显式声明 request 参数,而后者的只需要 import 一次就可以到处使用。在 Flask 的文档中,Armin Ronacher 也提及了这一点: For example, Flask uses thread-local objects internally so that you don’t have to pass objects around from function to function within a request in order to stay threadsafe. 不过 Flask 并没有直接使用 Python 内置的 threading....

04-11 · 4 min

Python Logging 源码分析

阅读了源码之后,我对 Python Logging 模块的几大疑惑都得到了解答: 为什么 Logger 和 Handler 都有 setLevel 方法? Logging 中会出现 Race condition 吗?(感觉都是很直接的 write 操作) 正式环境中想看日志又没办法动态调整 logLevel,感觉很鸡肋。 用起来好像还不如 print 方便。 会有性能问题吗? 日常使用 首先要了解下 Logging 的用法。 1. 配置 基本上有三种方式,代码、文件和字典。先看下如何用代码设置: import logging # create logger logger = logging.getLogger('simple_example') logger.setLevel(logging.DEBUG) # create console handler and set level to debug ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) # create formatter formatter = logging....

04-09 · 5 min

关于 Pager

好久没有更新了,最近研究了下如何用 Python 实现 Pager 的功能,这里指的是 Terminal 中的 Paging 程序,比如 less。 Why Pager Pager 在大段文字的展示中很常见,比如 Linux 的 man page,而 $PAGER 就是用来指定默认 Paging 程序的环境变量。Python shell 里面的 help() 会默认翻页显示,IPython 的 ? 则更胜一筹,能够判断当前屏幕的可用空间来决定是否 Paging。 Less 应该算是最流行的 Pager 了,相比于 more,它同时支持前进和后退翻页,而且因为不需要一次性读取整个文件,它的启动速度在打开大型文件时要远远快于 vi。因此,许多 Pager 都是通过启动系统自带的 less 程序来实现。 Don’t Reinvent the Wheel 轮子总是有的,而且还很多,这里说几个比较好用的: Pydoc 的 pager Click 的 echo_via_pager IPython 的 page Pydoc 是 Python 自带的,已经稳定存在了很多年,轻巧好用;Click 的实现类似,而且支持传入一个 generator;IPython 的 page 更加强大,可以自动判断当前的屏幕大小,再结合一个 screen_lines 参数来计算最终的可用空间。 再说说这几个轮子的实现,基本思路都是上面提到的调用系统 Pager。因为要兼容五花八门的操作系统,大致上又分为三种处理方式: 理想情况下是使用 PIPE。因为打开的系统 Pager 必然是子进程,而 PIPE 通过内存中的缓冲区实现了 IPC,这样既不用一次性读取所有数据,后续的 write 操作效率也高。...

04-02 · 2 min

Python 中的闭包

def f1(): l = [] def c(): l.append(1) def f2(): a = 1 def c(): print(a) a = 2 类似 f1 和 f2 中的闭包写法,之前总是在用了前者多次之后,再写后一种就报错,感觉很莫名其妙,明明都差不多。研究了下发现,其实这是 Python 闭包的 BugFeature,理解之后反而觉得这样的设计是合理的。 首先说 Closure,也就是闭包,特指内部的函数及其引用的超出本身作用域的对象,总是在函数嵌套时发生。在 f1 中,c 就是一个闭包函数,同时 l 也算作其中的一部分。因为 c 使用了 l 导致延伸了原有的作用域,所以才有闭包的产生。 再看 f2,如果我们只对 a 做 read 操作是不会引发问题的,由于 c 中尝试了赋值操作,才导致了 UnboundLocalError. 这是因为 Python 解释器会假定函数中赋值的变量是局部变量,而 c 中本身并没有定义 local 的 a 变量;其次,异常在 print(a) 时就会抛出,不会等到 a = 2 的执行。...

01-29 · 1 min