Python 提高对比效率的一个技巧(位运算&)

最近在看 Python epoll howto 的时候,发现里面对比一个 event 是不是某种 EVENT 类型用的是 & 操作符,比如 elif event & select.EPOLLIN 。一开始还以为是写错了,后来想了一下这样确实能起到 == 的作用,只要事件的实际数字值特殊设计的话。

只比较相等的话,只要不存在包含关系就可以。比如最简单的,每一个 bit 的1表示一个类型(这里都以一个 bytes 作例子吧。

上面4个数字分别是1,2,4,8,分别可以表示四种类型。对比的时候,除非两个类型相等,否则都是 0(Flase)。或者可以用下面这种方式,使用两个1,可以表示的类型多一些。

上面这种也可以达到类似的效果。如果存在包含关系的话就不行了,在不相等的情况下依然可能得到非0值。但是如果不是比较相等的情况,而是比较是不是包含,就可以使用这种表示了。比如 Unix 的文件权限系统,用 4 2 1 来表示执行:

如果验证 6 是否有读权限的话也可以用 & :

结果是非0值,表示拥有此权限。

为什么这么写呢,估计是C程序的风格吧,我用 Python 测试了一下性能,& 操作是比 == 效率高的。因为 epoll 这部分的代码是非常频繁的操作,能节省一些性能的话,还是很可观的。

通过反编译发现,其实 & 编译成 Python 字节码是 BINARY_AND ,而 == 是 COMPARE_OP。应该是 BINARY_AND 效率会比 COMPARE_OP 效率高一些吧。

 

 

PyCon2018 Review (Part 2)

最近空闲时间在看 PyCon 的一些分享,2018年的视频好多,不知道啥时候才能看完。这里将一些个人看过比较好的记录一下。上一次的文章在这里:PyCon2018 Review (Part 1)

5. GC再见,GC你好——Instagram Django进程调优案例

当初 Instagram 禁用 GC 的那篇文章发布之后,声名大噪。Zekun Li 在大会上讲了一下 Instagram 在 Python GC 上下的功夫。起先因为 GC 回收循环引用的问题,共享内容用的少,fork 出来的进程会占用很多内存,于是 Instagram 就想办法禁用了 GC,这样可以减少 Copy-on-Write 需要复制的内存,当然禁用了 GC 肯定会有内存泄漏的问题,Instagram 的选择是保证代码质量,避免在代码中使用循环引用。然而虽然项目增长,这样严格的代码质量难以保证,Instagram 又打开了GC。

演讲者听口音应该是大陆人,这个视频有助于对内存的理解,值得一看。

推荐指数:5

演讲者:Zekun Li from Instagram

相关项目:Django, uWSGI

Youtube: Zekun Li – There and Back Again: Disable and re-enable garbage collector at Instagram – PyCon 2018

6. 一份简单的 Pythonic 指南

我见过很多人写的带有“Java味道”的 Python 代码,里面很多复杂的抽象和继承关系;在循环里自己维护下标等行为,实在头疼。这个演讲适用于新手或者从其他语言过来的人,可以快速过一下 Python 的一些不错的特性。对于写了很长时间的 Python 程序员来说,可能用处不大。吐个槽:整天被业务赶着,时间长了危害实在是大,不抽出时间看看自己的工具都有什么不错的功能,就永远不会有什么长进。

演讲者来自微软,是一位女性,比较好懂。

推荐指数:4

演讲者:Nina Zakharenko

相关项目:The Zen of Pythonagithub, freezegun

Youtube: Nina Zakharenko – Elegant Solutions For Everyday Python Problems – PyCon 2018

7. 打一场值得打的仗:Facebook 的 Python 3迁移之路

现在 Facebook 的大多数项目都是运行在 Python3.6.3 上面的,但是到 2014 年,在 Facebook 内部使用 Python3 几乎是不可能的事情,大部分工程师都持 “我们将永远使用 Python2.7 的态度。一小部分人用业余时间驱动了公司向 Python3 迁移,看本篇演讲,学习如何在你的公司打一场值得的仗。变化不会自己发生,必须有人来引导做出改变。在迁移的过程中,作者首先面对的是 Facebook 所有的 build 系统都是针对 Python2.7 的,只好去一个一个修复,兼容 Python3.然后又要面对同事不停地提交 Python2 代码,作者又加入了 pyflake 检查。(一个小技巧,如果你在你们公司装的很权威,那么别人就会以为你在这方面是权威人士)

后来一些项目只是运行了 2to3,没做其他改变,结果导致因此占用内存降低了一半,运行速度提升了 40%。

Educate for the future you want, not the present you have.

美中不足的是,演讲者经常停顿和重复,对于我来说理解起来不是那么简单。

演讲者:Jason Fried

推荐指数:4

相关项目: 2to3, pyflake

Youtube: Jason Fried – Fighting the Good Fight: Python 3 in your organization – PyCon 2018

8. 字典技术的演化

这个视频是 laike9m 上次在线下给我推荐的,同样不是一个今年的 Pycon 视频,但是号称是“质量最高的一个演讲”,回来看了一下,真的很不错。

大家可能都有听说,Python3.6 里面字典的速度和内存都有了提升,同时带来一个副作用:默认有序了。并且这个副作用在 3.7 版本中写入了语言标准。Raymond 这个演讲涉及了字典的很多常见的技术,例如 Key Sharing,Open Addressing,Double Hashing 等,从头到尾展示和这些技术的演化过程、性能和原理等,介绍了 Python 的 dict 从一开始浪费内存、速度慢进化到了今天的甚至对 key lookup 有缓存。Python 基本是运行在字典上面的,即使有时候我们的感知很弱:module 是一个字典,global 是一个字典,Django 是运行在无数的字典上的,所以如果你优化了字典,你就优化了 Python。相比于 Python2.7,如果你不用 AsyncIO,你可能没有特别的理由升级到 Python3.5,但是 Python3.6 提升了 dict 的性能,这就很划算了。新的 dict (compact dict)使用 list 来存储字典的 key(这样空的位置只需要 8bytes 就够了),减少的空间同时也可以加快 hash 表的载入速度。字典还涉及到一些细节问题,比如删除元素,最后的 QA 也有很多干货。

有意思的是,Raymond在2016年就提到:Guido 目前还认为字典的 key 保持插入顺序这个特性还不能被依赖,但是他会被说付的,简直是预测帝啊。

演讲非常搞笑,Raymond 对自己的 Compact Dict 太满意了哈哈哈哈哈。

另外,我之前也花了很多时间研究字典一些相关的问题,看视频之前也可以看一下我写的这两篇博客,可能加深你对字典的理解,我自认为写的还是比较好懂的:《Hash碰撞和解决策略》、《Python 为什么list不能作为字典的key?

演讲者:Raymond Hettinger(资深Python讲师)

推荐指数:5

相关项目:builtin dict

Youtube:

一共找到两个版本,建议都看一下,67分钟版本更早,里面比较轻松,PyCon 那个比较正式,更加条理。不过67分钟版本的后面有很多 QA,也很有价值。

  1. PyCon2017 37分钟版本:Raymond Hettinger Modern Python Dictionaries A confluence of a dozen great ideas PyCon 2017
  2. 2016年的67分钟版本:Modern Dictionaries by Raymond Hettinger

最后赠送一个我最近在听得一个 podcast(macdavid推荐的):teahour ,内容不错,挺丰富的,从区块链到编辑器都有,很适合程序员。

 

Python3.7 dataclass 介绍

Python3.7 加入了一个新的 module:dataclasses。可以简单的理解成“支持默认值、可以修改的tuple”( “mutable namedtuples with defaults”)。其实没什么特别的,就是你定义一个很普通的类,@dataclass 装饰器可以帮你生成 __repr__ __init__ 等等方法,就不用自己写一遍了。但是此装饰器返回的依然是一个 class,这意味着并没有带来任何不便,你依然可以使用继承、metaclass、docstring、定义方法等。

先展示一个 PEP 中举的例子,下面的这段代码(Python3.7):

@dataclass 会自动生成

引入dataclass的理念

Python 想简单的定义一种容器,支持通过的对象属性进行访问。在这方面已经有很多尝试了:

  1. 标准库的 collections.namedtuple
  2. 标准库的 typing.NamedTuple
  3. 著名的 attr 库
  4. 各种 Snippet,问题和回答

那么为什么还需要 dataclass 呢?主要的好处有:

  1. 没有使用 BaseClass 或者 metaclass,不会影响代码的继承关系。被装饰的类依然是一个普通的类
  2. 使用类的 Fields 类型注解,用原生的方法支持类型检查,不侵入代码,不像 attr 这种库对代码有侵入性(要用 attr 的函数将一些东西处理)

dataclass 并不是要取代这些库,作为标准库的 dataclass 只是提供了一种更加方便使用的途径来定义 Data Class。以上这些库有不同的 feature,依然有存在的意义。

基本用法

dataclasses 的 dataclass 装饰器的原型如下:

很明显,这些默认参数可以控制是否生成魔术方法。通过本文开头的例子可以看出,不用加括号也可以调用。

通过 field 可以对参数做更多的定制化,比如默认值、是否参与repr、是否参与hash等。比如文档中的这个例子,由于 mylist 的缺失,就调用了 default_factory 。更多 field 能做的事情参考文档吧。

此外,dataclasses 模块还提供了很多有用的函数,可以将 dataclass 转换成 tuple、dict 等形式。话说我自己重复过很多这样的方法了……

Hook init

自动生成的 __init__ 可以被 hook。很简单,自动生成的 __init__ 方法会调用 __post_init__

如果想传给 __post_init__ 方法但是不传给 __init__ ,可以使用一个特殊的类型 InitVar

不可修改的功能

Python 没有 const 类似的东西,理论上任何东西都是可以修改的。如果非要说不能修改的实现呢,这里有个比较著名的实现。只有不到10行代码。

但是有了 dataclass ,可以直接使用 @dataclass(frozen=True) 了。然后装饰器会对 Class 添加上 __setattr__ 和 __delattr__ 。Raise 一个 FrozenInstanceError。缺点是会有一些性能损失,因为 __init__ 必须通过 object.__setattr__ 。

继承

对于有继承关系的 dataclass,会按照 MRO 的反顺序(从object开始),对于每一个基类,将在基类找到的 fields 添加到顺序的一个 mapping 中。所有的基类都找完了,按照这个 mapping 生成所有的魔术方法。所以方法中这些参数的顺序,是按照找到的顺序排的,先找到的排在前面。因为是先找的基类,所以相同 name 的话,后面子类的 fields 定义会覆盖基类的。比如文档中的这个例子:

那么最后生成的将会是:

注意 x y 的顺序是 Base 中的顺序,但是 C 的 x 是 int 类型,覆盖了 Base 中的 Any。

可变对象的陷阱

在前面的“基本用法”一节中,使用了 default_factory 。为什么不直接使用 [] 作为默认呢?

老鸟都会知道 Python 这么一个坑:将可变对象比如 list 作为函数的默认参数,那么这个参数会被缓存,导致意外的错误。详细的可以参考这里:Python Common Gotchas

考虑到下面的代码:

将会生成:

这样无论实例化多少对象,x 变量将在多个实例之间共享。dataclass 很难有一个比较好的办法预防这种情况。所以这个地方做的设计是:如果默认参数的类型是 list dict 或 set ,就抛出一个 TypeError。虽然不算完美,但是可以预防很大一部分情况了。

如果默认参数需要是 list,那么就用上面提到的 default_factory 。

 

参考资料:

  1. PEP 557
  2. dataclass标准库文档
 

推荐一本计算机基础的科普书:《穿越计算机的迷雾》

小时候经常想,计算器是如何将两个数字加起来的?难道计算器保存了所有的个位数相加的表吗?为什么能将数字显示出来?为什么只能做简单的操作,不能做混合运算(后来知道了一种叫“科学计算器”的东西)?

后来学到了计算机内部使用二进制表示的,但是小时候那种困惑依然没有解决。这个二进制到底是如何应用的?内存里面是有很多点记录了0和1吗?那这些点有多小呢,才能保存那么多数据?这些数据在计算机内部又是怎么取出来的,怎么使用的?

直到同事给我推荐了《穿越计算机的迷雾》这本书,这些困扰我十几年的问题才渐渐明了。当然,如果大学的时候选修计算机组成原理好好上一下的话,可能也早就明白了。对计算机感兴趣,或者对于计算机到底是如何工作还有疑问的同学,非常推荐读一读这本书。这本书语言非常通俗,虽然有些地方需要仔细思考才能跟上,但是作者已经是非常努力的将这些难的东西写明白了。有些地方可能有些啰嗦,像《鸟哥的Linux私房菜》一样,但是也可以当做是幽默吧。

计算机的发明是一个非常漫长的过程,这段历史中有很多伟大的科学家的努力。首先人类先是发现了电,然后发现了电流的磁效应,法拉第又发现了磁能生电,接着开启了用电磁来传递信息的漫长探索之路,摩斯密码,电报,电话因此诞生了。

让计算机做加法这一个简单的操作,也经历了漫长的探索过程。这又和古老的逻辑学分不开,有了逻辑上的真值表和推论,才有可能用二进制表示逻辑问题,将数字逻辑转换成逻辑电路。通过一番精彩的推导,就可以制造出一个电路复杂的全加器。到这里我也对二进制有了一个更深刻的理解:计算机的计算方式和人类是有本质的不同的,人类可以理解复杂的事情,可以处理十进制。计算机天生就是处理很简单的东西,然后使用很简单的东西组合出来很复杂的东西。比如说,计算机当然也可以用十进制,不同的电压(比如0~10v表示1,11~20v表示2……)来表示。但是这样的问题是,我们要设计电路表示1+1=2,1+2=3等等。如果用二进制,只有很有限的几种情况:1-1 1-0 0-0。这种枚举起来简单,大大的简化了电路的设计。

这基本就是 CPU 的原型了:能做简单的计算。借着触发器问世,多个触发器组成的电路可以保存中间结果,支持读写。为了读取这些触发器保存的值,又出现了地址译码器,这样无论读写哪个地址都可以很快,也很简单。这就叫做随机读写(RAM)。

这些基本就是现代计算机的老底了。接下来就是我们熟悉的汇编指令,网络内容等。可以说,电子计算机的发明并不像电灯泡、轮子这种东西一样,经过多次尝试或者从仿生获得的灵感就可以一下子发明出来,电子计算机是无数人经过不断的探索才一步一步走到今天这种形态。我常常会想,现在的生活多么幸福啊,可以跳过那段没有网络,没有现在这种性能的计算机的“黑暗时代”。可是今天的计算机依然在发展,人工智能,区块链这些新技术层出不穷,说不定后人也觉得我们今天的生活是“黑暗的时代”呢。

 

 

部署 Django 项目背后的原理:为什么需要 Nginx 和 Gunicron这些东西?

相信用过 Django 的同学一定会被 “Very easy to setup” 惊艳到。只要一行命令,就可以在 admin 界面看到一个完整的登陆注册。但是到了部署的时候,你一定会被网上复杂的部署教程搞的头晕,为啥本地开发这么简单,到了服务器却需要又是 Nginx,又是 uWSGI 这种东西呢?

摘自The Full Stack Python Guide to Deployments 一书

本文试图解释这些程序在一个 Web 服务中扮演的角色,为什么部署 Python web程序需要它们。本文不介绍如何部署 Django 应用等,这一类教程网上有很多,读者可自行搜索。其中 Nginx 和 Apache 算是一类的,可以替换。Gunicorn 和 uWSGI 的角色是类似的。同理最后端的 Web 框架是一类的,比如 Django 和 Flask。本文的内容替换以上任意一个应该也适用。

如果你读了网上任意一篇教程,你应该知道一个完整的部署应该类似这样:

最后后面的 App 我们知道,只要 runserver 就可以访问了,但是 Django 的文档明确说明:

DO NOT USE THIS SERVER IN A PRODUCTION SETTING. It has not gone through security audits or performance tests. (And that’s how it’s gonna stay. We’re in the business of making Web frameworks, not Web servers, so improving this server to be able to handle a production environment is outside the scope of Django.)

即 Django 做的事情只是一个框架,不会去关心一些安全问题、HTTP 的性能问题等。所以我们需要一个专业的 HTTP 服务器。这就出现了 Nginx 或 Apache。那么如何将 HTTP 服务器和我们的应用连接起来呢?动态网站问世的时候,就出现了 CGI 协议。注意这是一个协议,定义了HTTP 服务器如何通过后端的应用获取动态内容。可以简单的理解成 HTTP 服务器通过 CGI 协议调用后端应用吧!WSGI 可以理解成 Python 的 CGI。uWSGI 和 Gunicorn 是这种 WSGI 的一些实现。通过像 uWSGI 和 Gunicorn 的实现,Nginx 这样的通用 web 服务器就可以和 uWSGI 来沟通了。那 Django 呢?上文说到,Django 是一个 Framework,而不是一个 Web Server,Django 只是帮你去写 Web 程序的代码,真正运行的其实是 uWSGI 进程 (而并没有一个 django 进程)。就出现了上面提到的三层的部署。

但是,为什么我们还需要 Nginx 呢?这些 WSGI 程序本身不是也提供访 HTTP 问吗?

是的,uWSGI 和 Gunicorn 本身就是一个便携的 web 服务器了,但是我们一般还是在它们前面档一个更加专业的 HTTP 服务器。Nginx 作为一个经过更长时间验证的 HTTP 服务器来说,它有很多 uWSGI 没有支持的 feature。比如:

  1. 处理静态资源更加优秀,E-Tag 的设置,Gzip 压缩等
  2. 处理网络连接,降低网络负载。例如 reuqest_buffering ,假如部署一个 uWSGI 程序,如果有慢的请求存在,uWSGI 必须等待整个 HTTP 请求发过来之后才开始处理请求。但是如果前置 Nginx,那么 Nginx 会帮你收到整个 HTTP 请求之后才交给 uWSGI 处理,也就是说,uWSGI获得的永远会是完整的 HTTP 请求,不会占用一个线程在等待。(为什么慢请求不会占用 Nginx 一个线程但是会占用 uWSGI 的一个线程,这个是因为 Nginx 是基于 epoll 的异步模型,而 uWSGI 是同步模型,这里面要详细了解的话,就要去搜索和阅读很多东西了)
  3. 甚至缓存动态的内容,例如将博客的首页缓存 5 分钟
  4. 作为一个负载均衡的前置,这样每一层的实例数量可以横向扩展
  5. Nginx 本身是作为一个服务存在的,几乎在任何 Linux 版本上安装之后都可以用 initd 启动,就像 MySQL 那样。uWSGI 一般是需要自己将其配置成服务的。(虽然前置了 Nginx 依然是需要配置 uWSGI)
  6. ……

此外,这样分开的好处还是得,到达 uWSGI 和 Gunicorn 的请求的情况变得简单了很多,Nginx 处理了一层,将过滤和处理之后的请求交给 uWSGI 或 Gunicorn。这使得这些 WSGI 程序的实现简单了一些,简化了开发的工作。专业的事情交给专业的人去做。

当然,并不是所有的项目都需要这么复杂的部署,有一个可选的是将 WSGI 程序嫁接到 HTTP 服务器上,比如 Apache httpd + mod_wsgi, Nginx + mod_uwsgi 等。

参考资料:

  1. 一份比较不错的部署指南
  2. Why do I need Nginx and something like Gunicorn?
  3. Why do I need nginx when I have uWSGI