聊聊我对python的感受
工作中主要是在写python2,把python作为用户逻辑的载体的,一般是C++写好底层,导出成python模块使用,或者是可执行程序带起来一个python虚拟机,把函数注入进去,然后加载python代码执行。我接触的这部分python,没有太多的框架,几乎不使用基本数据结构之外的标准库,因为很多东西都是C++封装好给python的,性能比纯python好很多。因此下面的文字也主要围绕着我使用的这个python的子集展开,如果有所偏差,那也是无法避免的。
python的一些优点
hook一切的能力
python的装饰器提供了一种在定义处直接修改被定义的对象的能力,有点类似清洁宏。宏这种东西,目的就是对语言的语法树做变换,生成被调整过的语法树结构,来达到抽象某些东西的意义。然而大部分加宏,尤其是清洁宏的语言,都不可避免的要考虑一个问题,就是如何表示语法树和语法树的变换。Lisp系因为本身代码就是数据,所以语法树,代码,变换都是S-expr;rust则是发明了另一套语法来做这一件事情;还在proposal阶段的C++ reflection,不仅要发明语法,还要发明对应的库和namespace,甚至还要和constexpr之类的东西耦合,这就很丑,而且又很大地增加了语言的复杂度。不喷C++了,python的装饰器很巧妙的复用了python语法,通过放弃一些变换的自由度的方式(装饰器只能以函数/类为单位进行变换)实现了较为舒服的宏。
另一个要说的就是metaclass,metaclass用来hook类型的创建,可以用来做一些在类型创建时对类型进行调整的工作,比如增减类变量,修改基类,注册一些东西,变换一下函数等等。
最后就是属性索引器__get__和__set__,我曾经在看到他们之后突然意识到,这就是C#的DependencyProperty需要的东西,不通过用户编写代码,来达到hook一个属性的读取和写入的能力。写过WPF的同学都知道,WPF里面有一套很复杂的DependencyProperty/Object系统,其实说白了就是需要hook属性更改,用来做数据绑定之类的东西,写起来很麻烦,VS也专门提供了插入dependency property定义的boilerplate的功能。python这个属性索引器设计就很好的解决了这个问题,至少让代码写起来没有那么多废话了。
有人可能觉得我用C++和C#跟python对比不合适,的确,这些语言的主战场是大型系统,高性能,硬件抽象,的确不适合用来实现过于灵活的东西。
不吝于用开洞的方法来解决问题
python有很多魔术方法,而且随着语言的进化,还在加入更多的魔术方法。对象构造走魔术方法,运算符重载走魔术方法,上面说的hook属性索引器也是魔术方法,甚至检查一个类是不是另一个的基类,也可以走魔术方法。当语言层面原生为你提供很多hook的时候,尤其是需要灵活性的动态语言,他的灵活性就真的有了。反观C++一直在吹嘘我们可以用抽象来实现一切东西,然后写出来的东西都是一坨一坨的模板,然后initializer_list,structured binding,attribute还是忍不住开了洞。C#这样语法还算很优雅的语言,也需要一堆boilerplate来做dependency property。当然他们都是静态语言,主战场是性能,本来就不应该和动态语言相比较。
另一个要说的就是__getitem__和__setitem__,python的obj[key]和obj[key]=value走了两个不同的方法,这样就很棒地绕开了key不存在时到底是要添加,还是丢异常的问题。因为分开了读取和写入,obj[key],不存在丢异常,obj[key]=value,不存在就添加,和C#/C++的Dictionary/map设计相比好了不少,这俩一个不存在都丢异常,一个不存在都添加。
与C++杂乱的开洞相比,python的洞基本都开在魔术方法上,位置相对比较统一。
python的垃圾之处
智障缩进/换行语法
强制缩进,换行,python是唯一一个lambda会降低代码可读性的语言。就没听说过其他哪个语言的lambda会让代码可读性变差的
强行换行用\,尤其是当if条件稍微长一点的时候,你不想用\,把if拆开,就得缩进好几个tab,你用\换行,看着就跟C++写了一坨宏似的
基本控制流太少
虽然传统的for、do-while都可以变换成while来做,但是while True: if xxx break很丑哎,加个do-while又不会怀孕
虽然switch可以用dict+lambda做,但lambda都那么丑了,我又不想白白产生一个构造dict的overhead,只能写一坨if else
慢
真的慢,没有jit,没有内联优化,没有逃逸分析,对象全都开在堆上,引用计数+mark sweep也慢,处理个数组能比C++慢两个数量级,小函数调用特别伤。