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.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') # add formatter to ch ch....

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 的执行。 那为什么 f1 没问题呢,是因为列表为可变对象,append 操作只是更新了里面的内容,并不存在赋值。 def f(): l = [] def c(): l....

01-29 · 1 min

关于 HTTP Auth

Auth 代表了 Authentication 和 Authorization 两个概念,也就是认证与授权。基于 HTTP,两者得以遵循一定的标准,SSL/TLS 之后,又出现了 OAuth 2.0,让授权也简单了许多。 Authentication 认证相对来说比较直接,核心就是对 Credential(e.g. username/password) 的验证。HTTP 提供了多种认证方案,比如最常见的 Basic auth, Digest access 和 Bearer. Basic auth 具体来说就是服务器用 WWW-Authenticate 表示需要认证,比如 WWW-Authenticate: Basic realm='Accessing to xx site',客户端则通过 Authorization 提供相关信息:Authorization: Basic Zm9vOmJhcg==,后面的一串编码是对用户名密码明文进行 base64 的结果,即可以直接从中 decode 出原始信息 foo:bar. 没有 HTTPS 的保护,这样很不安全,所以 Apache/Nginx 对 BA 的实现都会使用密码的哈希结果而不是原文,拿后者举例: http { server { location / { auth_basic "Accessing to xx site"; auth_basic_user_file /path/to/authfile; } } } 然后需要在 authfile 中保存 username/password pair,比如 sudo htpasswd -c /path/to/authfile user1,htpasswd 是 Apache 提供的专门用来生成 BA 使用的 Credential file 的工具。不用额外安装,我们直接用 openssl 代替:...

01-28 · 3 min