近期对 Python 选型的一些思考与决策
2019 6 5 05:02 PM 95次查看
Go 确实是门很容易学习的语言,我大概读了一周的历史遗留代码,连语法都没去细读,就开始使用 Go 了。它最让我诟病的缺点是在明知内存结构相同的情况下,却无法强制转换类型(例如把
[]int 转换成 []uint),直到我掌握了 unsafe 库的用法后。其他不爽之处也都大同小异,概括起来就是「高不成低不就」,代码写起来达不到 Python 那样简洁,追求性能时又要比 C 付出更多的努力。所以我决定将公司的项目架构调整一下,底层处理数据包的代码仍然使用 Go,而业务接口(REST API)替换成 Python 实现。这样不但能大幅提升开发效率,而且还顺带降低了招聘的难度。
考虑到现在已经 2019 年了,Python 2.x 只剩最后几个月的寿命,因此我决定切换到 Python 3。
而 Python 3 与 Python 2 相比,最大的优势在于异步编程,其中最重要的
async / await 语法是 3.5 引入的,所以之前的版本也不用考虑了。而在抉择 Web 框架时我又犯难了,之前最熟悉的 Tornado 在 Python 3 的时代已经失去了其异步的优势,反而因为自己实现了一套异步调用的轮子而显得比较臃肿难用。
于是我又学起了自己历来不喜欢但是却大火的 Flask。这个框架其实主要是对 Werkzeug 的封装。它对
werkzeug.local.LocalProxy 的使用算是其主要的亮点,但这种实现却给我一种有捷径不走,非要去绕路开山的感觉。Flask 的插件大都也不敢恭维,有种「国产自主研发的安卓系统」的感觉。大失所望的我在后续的挑选过程中,发现了 Starlette 和 Uvicorn,感觉这对组合才是 Python Web 开发的未来:原生的异步函数支持,内置对 WebSocket 的支持。不过 Starlette 的文档不够详细,很多地方需要读源码去了解它的实现。但在阅读源码的过程中,我发现它实现得很轻量,没有过多的限制和依赖,这使得它比 Tornado 和 Flask 都更容易改造,以满足特殊的需求。如果想锻炼自己的异步编程能力,可以去尝试用 Starlette + aioredis 实现一个基于 WebSocket 的通知系统(类似使用知乎或微博时,实时收到的点赞通知等)。
Starlette 的作者还实现了 databases 和 ORM 库,支持对 PostgreSQL、MySQL 和 SQLite 的异步操作。但如果对性能要求不高,也可以容忍这里的阻塞。
顺带一提,我在做性能测试时,发现 Starlette 搭配 Uvicorn 要比 Gunicorn 慢。我提了个 pull request 来修复,然而过了一个月也毫无反应。看了眼 Uvicorn 近期的提交记录,大概每个月只有一两次提交。而之前给 Gunicorn 提交代码时,作者的沟通和反馈还是挺及时的,只是它的架构相对而言要重很多,也更容易出 bug。
此外,Starlette 最近从 ASGI 2 升级到了 ASGI 3,导致 WebSocketRoute 不向下兼容了,但是文档里并没有提到。
由于 Starlette 的语法依赖了 Python 3.6 引入的异步生成器,而 Python 3.7 也提升了 asyncio 的性能,所以对于没有历史包袱的新项目而言,采用最新的稳定版是最好的选择。
在写 Python 3 的代码时,我又发现 type hints 的好用,有它之后 PyCharm 不会再提示一堆无关的方法,用错了类型也会有提示。
不过 Python 2 要写成类型注释的形式。有些类型需要用到 typing 库,这在 Python 3.5 以下还得额外安装。而且很多类型只在注释中使用,也得
import 这些类型所在的包,对有代码洁癖的人而言不太能忍。所以不如再等段时间,直接放弃对低版本的支持即可。最后,Python 相较于 Go 而言,另一个缺点是不好做源码的保护。权衡之后我选择了用 Cython 来编译。
但是
inspect 库并不能识别 Cython 编译出来的函数和方法,asyncio.iscoroutinefunction() 也不能识别 Cython 编译出来的异步函数,而 Starlette 却有几处依赖。前者可以通过判断函数的类型来绕过:
def is_cython_function_or_method(func):
return type(func).__name__ == 'cython_function_or_method'后者暂时就没办法了,Cython 的开发者故意要不兼容,能做的只有绕过这个检查,人为要求都用异步函数了。
0条评论 你不来一发么↓