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
 

辅助Django开发的一些隐藏资源(文档)

距离上次使用 Django 还是一年前了,最近又回到 Django 上来,慢慢读它的文档,发现了1年之前马马虎虎使用 Django 的时候没有发现的好东西。

Admin可以自动生成文档

大家都知道 Django 可以自动根据你的 Model 生成 admin,但是我还是第一次知道这个 admin 可以生成文档呢。效果如下,可以展示项目的 App 中可以用的 Tags,Model 等。

Admin自动生成的文档首页

对我来说,比较有用的是可以展示某个 Model 的所有可以用的属性,这样就不用翻代码了,一目了然。尤其是对于没有 Model 之间的关系的 related_name ,有一些没有自己定义,即使自己定义了可能也比较乱。通过这个文档来看,这个 Model 上面的可调用的东西就很清晰了。

安装方法:

  1. 将 django.contrib.admindocs 添加到 INSTALLED_APPS
  2. 在 urls.py 中添加  path('admin/doc/', include('django.contrib.admindocs.urls'))
  3. 安装 http://docutils.sourceforge.net/ (pip install docutils)

更多安装信息可以参考 Django文档

Classy Class-Based Views网站

去年写了一篇文章介绍 Django 的 Class-based-View,但是 Django 的 View 看起来比较杂乱,也比较多(好像一旦用上了多继承,就会搞得这样)。想要一个 View 或 Mixin 的时候,经常怀疑自己选择的这个是不是最合适的。

http://ccbv.co.uk 这个网站可以清楚地展示 Django(可以选择 Django 的版本) 所有可以用的 View。每个 View 的详情页展示了这个 View 的属性和可以用的方法(如果用 dir的话,你可能会被显示出的信息吓到)。并且可以查看方法的文档和源码,也可以跳转到 Django 在 Github 的源代码。

还能画出继承关系的类图,详见恨晚。

 

《罪与罚》摘录

去年去参加了 WEPLAY 游戏展,会展上《寂静岭》的音乐制作人山冈晃先生接受采访的时候说,《寂静岭》音乐的灵感来自于一本俄罗斯小说《罪与罚》,所以我决定去读一下这本书。

花了半年时间才读完这本书,读完之后发现,这个情节和游戏中的剧情有很微妙的关系。《罪与罚》中主线讲述的是大学生罗佳迫于生活的穷困,内心又有着伟大的思想,认为杀死一个蝼蚁一般的老太婆,获得短期内的钱来供自己这段时间的发展,对人类社会来说是有益的,就像拿破仑一样。然而,内心是有这样的想法,但是实践起来却畏手畏脚,最终鬼使神差践行了自己的计划,良心却备受谴责。经历了很长时间的煎熬,最后去警察局自首。我认为罗佳的悲剧来自于年纪轻轻却不甘于平庸,急切地想有非常舒适的环境去发展自己的矛盾。现实中的自己无法满足自己对自己的期望,从而对自己产生了恨,表现地对自己的妹妹和妈妈冷漠,对自己的处境也漠不关心。

除了主人公罗佳,书中还描述了很多人的悲剧。无法自制经常跑去饮酒的小官吏,硬生生断送的自己的前程,还有原本幸福的家庭;命途多舛的官吏妻子,被逼卖淫的女儿;被世俗嘲笑,被富婆包养的斯维德里盖洛夫,即使放弃了一切也换不到杜尼娅的芳心,最终自杀;对儿子百般溺爱,精神失常的罗佳的妈妈。

这本书所描述的悲剧如此突出,我觉得很大部分原因是作者陀思妥耶夫斯基喜欢让人物的感情剧烈变化,文章有打断的独白和对话,用了很多语气词,惊叹词,使得这些人物的台词感染力非常强,很容易代入。

读完这本书,也没有什么很特别的想法,就是感受到了这么多悲剧。生者还是好好活着,多考虑一下别人,小心翼翼的,生活太容易破碎了。

以下是我在 Kindle 上划出的一些摘录。

 

这种理论把芸芸众生人为地分为“非凡的人”和“平凡的人”两类:“一类是低级的人(平凡的人),也就是说,可以称之为仅仅是繁殖同类的材料;另一类是真正意义上的人,也就是具有天赋和才干,能在自己所处的社会提出新见解的人。”

个人究竟该以何种方式与途径发展自己的个性呢?

但他犯罪后良心不安,被他蔑视的道德规范暗暗地惩罚着他的灵魂,他噩梦连连,疑神疑鬼,惶惶不可终日,几次打算到警察分局去投案自首。这样,这部小说所写的真正惩罚就是道德与良心的惩罚,它所描绘的是发生在主人公内心深处的道德和心理斗争。

“那是由于没有别的人可找,也没有别的路可走啊!不是吗,任何人总得至少有一条路可走啊。因为有时候一个人必须有条路可走!

此外,要了解任何一个人,都必须一步一步地细心观察,才不致产生错误和偏见,否则,以后纠正错误和消除成见就十分困难了。

啊。当然,即使只吃黑面包只喝白开水,她也不会出卖自己的灵魂,也不会因为耽于舒适而放弃精神方面的自由;

啊,必要时我们会压制我们的道德情感,并且把自由、宁静,甚至良心,所有的一切,都拿到旧货市场上去拍卖。连生命也毫不顾惜!只要我们热爱的人能够幸福。

一种新的、无法克服的感觉以每分钟逐渐增强之势控制了他:这就是对劈面相逢的、环绕四周的一切都怀着某种无比强烈的、几乎是生理性的反感,一种持续不断的、怒气冲冲的、恨之入骨的反感。他憎恶劈面相逢的一切人,——憎恶他们的面孔、步伐、举止。假如有谁来与他攀谈,他简直要啐他一脸唾沫,可能还会咬他一口……

科学却告诉我们:首先你应该只爱自己,因为世界上的一切都以个人利益为基础。你只爱你自己,那么你就会把自己的事情办妥,你的长上衣也就会完整如一。经济学的真理进一步告诉我们,社会上办得好的私人事业越多,也就是说完整如一的长上衣越多,社会的基础就越牢固,公共事业也就会办得越发兴旺。

“您的那个讲师在莫斯科受审时被问到为何伪造有奖债券时,答道:‘大家都千方百计致富,所以我也想快速发财。’原话我记不太清了,但意思就是不劳而获,尽快地大发横财!大家都习惯于坐享其成,以别人的思想为思想,吃别人的现成饭。哈,伟大的时刻来临了,每个人都露出了自己的本性,都在看用什么法子发财……”

他只知道一点:“这一切必须就在今天结束,一次性地结束,立即结束;否则他决不回家,因为他不愿意如此活着。”如何结束?用什么法子结束?对此他毫不知晓,甚至不愿加以考虑。他驱走了这个念头,因为这个念头让他苦恼不已。他只是感觉到并且知道,一切总归都必须改变,这样变或者那样变,“不管怎样变都行”,他怀着天不怕地不怕、无可动摇的自信和决心反复喃喃着这句话。

“没什么可锁的人是幸福的,对吗?”他笑盈盈地对索尼娅说。

比方说,哪怕是人类的立法者和规章制度的创立者,从远古时代,直至后来的莱喀古士[17]、梭伦[18]、穆罕默德[19]、拿破仑等等,无一例外,都是罪犯,唯一的原因在于,他们在制定新法规的同时,也就破坏了世所公认的、神圣不可侵犯的、代代相传的古老法规,而且,当然啰,他们也不会面对流血而停步不前,只要流血(有时流的完全是无辜的、为维护古代的法规而英勇献身者的鲜血)能帮助他们成功。尤其令人注目的是,这些人类的恩人和规章制度的创立者,大多数都是血流成河的特别可怕的屠夫。

第一类人永远是现在的主人,第二类人则是未来的主人。第一类人保存这世界,增殖人口;第二类人则推动世界向前发展,并引导它奔向目的地。无论是第一类人还是第二类人,都有完全相同的生存权利。

“有良心的人一旦意识到自己的错误,就会深感痛苦。这也是对他的一种苦役之外的惩罚。”

追求进步,并跻身于“我们的年轻的一代”的行列——完全是出于一时的青春激情。他是那些数不胜数、各式各样的言行庸俗、思想幼稚、志大才疏而又刚愎自用者之一,这类人对于最流行的时髦思想必定是顷刻间便趋之若鹜,紧紧依附,为的是立即把它庸俗化,并在一瞬间把他们有时竭诚效劳的一切漫画化。

也许,在这件事上起决定作用的是那种特殊的穷人的自尊心。就是因为这种自尊心,千千万万的穷人每逢日常生活中人人必须遵守的某些社会习俗时,都会倾箱倒箧地把自己节衣缩食积攒起来的一点点钱全都花光,只是为了证明自己“毫不逊色于别人”,以及不让那些人对他们横加“指责”。

你们还不知道,还不知道,这是怎样的一颗心灵,这是怎样的一个姑娘!她竟会偷钱,她!如果你们需要的话,她为了济人之难会脱下自己身上的最后一件衣服,光着脚去把它卖掉,再把钱送给你们,瞧啊她就是这样一个姑娘!因为我的孩子啼饥号寒,她甚至去领了黄色执照,她完全是为了我们才去出卖自己的呀!……哎哟,死鬼,死鬼呀!哎哟,死鬼,死鬼呀!你看见了吗?你看见了吗?这就是为你办的丧后酬客宴!上帝啊!你们倒是保护保护她呀,究竟为什么光是站着呢!罗季昂·罗曼诺维奇!您为什么不出来主持正义啊?难道连您都不相信她?你们连她的一个小指头都抵不上,你们所有的人,所有的人,所有的人,所有的人!上帝啊!最后只好请您保护她了!”

你瞧:你已经知道,我母亲几乎是一无所有。妹妹侥幸受了些教育,也注定只能东奔西跑当家庭教师。她们把一切希望都寄托在我的身上。我已经上了大学,但在大学里我无法维持自己的生活,迫不得已只好暂时退学。假如我就这样勉强拖下去,那么十年二十年之后(如果天从人愿,情况好转),我仍然有希望当上一名教师,或者成为一个官吏,可以拿到一千卢布的年薪……(他似乎是倒背如流)然而到那个时候,母亲早已因为操劳和愁苦而憔悴不堪了,我还是不能使她感到安慰,而妹妹……唉,妹妹的情况也许更糟!……难道竟然可以一辈子对这一切都漠不关心,置若罔闻,把母亲忘记到九霄云外,让妹妹低首下心地,譬如说吧,任人侮辱?为了什么?是不是为了在埋葬她们以后,挣钱去养活其他的人——妻子和孩子,而以后又一文不名地让他们活在世上餐风饮露?唔……唔,于是我下定决心要占有老太婆的钱,把它们用做我最近几年的费用,不再使母亲担忧受苦,用这些钱维持我大学的生活,实现我大学毕业后最初的一些计划——大刀阔斧、不遗余力地彻底改变这一切,为自己开创一个崭新的前程,踏上一条独立自主的新路……唔……唔,我要说的就全在这里了……唔,当然喽,我杀死了这个老太婆,——我做了一件坏事……唉,不说了!”

一百只兔子永远拼不成一匹马,一百个疑点永远拼不成一个证据,有句英国谚语不是这样说的吗?不过这仅仅是一种理智的说法,可是一时冲动起来的感情是难以控制的。

其实,要对某些人做出公正的评判,就必须预先放弃一些先入的偏见,改变那种对待我们周围的人和事的习惯态度。

最后,我采取了一个征服女人的心的最高明、最有效的绝招,这个绝招永远都不会使人失望,对任何人都绝对管用,无一例外。这个绝招是众所周知的,它就是阿谀奉承。世界上最难的事是开诚相见,而最容易的事是阿谀奉承。开诚相见,只要有百分之一的虚假,那么马上就会出现不和谐,而麻烦就会随之而来。至于阿谀奉承,哪怕从头至尾都是虚假,但还是令人感到高兴,听起来不会不舒服;哪怕觉得肉麻,但还是觉得舒服。不管阿谀奉承是如何肉麻,其中肯定至少有一半使人觉得真实。

“罪行?什么罪行?”他突然出人意外地狂叫,“我杀死的是一只可恶的、有害的虱子,一个对任何人都没有益处的放高利贷的老太婆,一个穷人的吸血鬼,杀了她可以赎四十桩罪行,这也算是犯罪吗?我可不认为这是罪行,也没有想去赎罪。为什么大家都指着我说:‘犯罪,犯罪!’直到现在我才明白,我的胆怯是非常愚蠢的,现在我已决定去受这种不应该受到的耻辱!我这样做,只是由于自己的卑鄙和无能,也许还由于这个……波尔菲里建议的好处!……”

但是他对自己进行了严格的审判,他那变得冷酷无情的良心,在他以往的行为中没有发现任何特别严重的罪过,除了人人都免不了的一般的失误以外。