重识 Python
我与 python 的渊源
记得当年 python 是自学的,至今还记得入门的那本书的名字叫做《a byte of python》
2014年入行时即写 python,后端 djang 应用,当时 python 版本还是2.7,现在都进化到 3.12了,变化不可谓不大。印象中最深刻的地方(有记忆)在于 python2.7 的 print 支持以下两种写法:
Python 2.7.18 (default, Apr 20 2020, 19:34:11)
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print("hello")
hello
>>> print "hello"
hello
而在 python3.x 中,仅支持以下一种
Python 3.9.18 (main, Aug 24 2023, 18:16:58)
[Clang 15.0.0 (clang-1500.0.40.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> print("hello")
hello
>>> print "hello"
File "<stdin>", line 1
print "hello"
^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print("hello")?
>>>
后面的工作技术栈转向了 golang 和云原生相关,2019年因为工作的关系,接触过 appium,写过不到半年的 python,后面能用到 python 的地方只有处理一些字符串、表格等临时的活儿……
人生苦短,我选 python
最近公司人员变动有点大,组里的项目也不太行的样子,在此大背景下,我被派去支持隔壁 AI 项目组的一个 poc:
拿到了一个 java 写的服务 repo (项目开发人员即将于两天后离职)
一个阿里云服务器 ip、用户名、密码,用来部署 1 中的 java 服务
被拉进若干群
TL给出的 10 分钟不到需求介绍
工作日每天下午 5 点半同步进度
有一位博士负责大模型调优
deadline 为5月底
这就是全部,彼时,我手头还有其它项目支持的事情,说焦头烂额有点过,但也相差不多。虽然时不时还有其它人或事会打断我,但总归是有相对整块的时间来看遗留下来的 java 代码,梳理了一下这个 java 服务
提供了一个 post 接口,使用 SSE 模式, 用于客户方调用 ,接口有签名检验,但是代码中签名检验部分直接放过了……
调用了客户的一个 post 接口,接口同样要做签名
调用了百度千帆的 API 接口
功能还是比较简单的,困难的地方在签名规则的实现,没有详细的的文档参考;然后调用百度千帆的 API 接口这事吧,有点七弯八绕的:为啥不直接用 sdk? 问了下当时的开发,答曰:做这个项目的时候, 千帆的 java sdk 功能不全。通过翻阅官方文档发现:千帆的一众 sdk 中,对 python 的支持度是最好的。这也难怪,都要做 AI 了,没理由不用 python 啊,此时已经萌生了用 python 重写这个服务的想法。因为博士大模型调优的代码使用 python ,而我需要调用博士调优后的大模型的代码,这下没理由不用 python 了。在某天的工作进度同步会上,提出了这个设想,一帮指挥官直接说:可以啊,用啥不重要,能实现就行……
python 实现
python 版本选择
首先必须是3.x,3.12 都发布一段时间了,本着用新不用旧的原则,那就 3.12 吧(虽然我知道千帆的 python sdk 的版本是 3.9)。后面由于博士的代码是基于 python 3.9 开发的,我调用他的代码时出现了奇怪的包依赖错误,为了减少不必要的麻烦,老实切换到了 3.9, 然后就好了……
服务端框架选型
django: 以前写过,对于这个需求而言,django 显得略微有些臃肿
fastapi:在推上,fastapi 的出镜率很高,据说很强、很快
webpy、flask、bottle……听过但没咋写过的据说很轻巧的框架
如此随意,选了 fastapi,根据官方文档快速启动了,对 swagger 的支持很棒,这是我最喜欢的特性之一
依赖管理
requirements.txt: 当前我用的还是这个
pdm: 这个在我的信息流里面出现的频率也很高
pdm 很新很流行,但我在容器化部署时遇到一些奇奇怪怪的问题,彼时也没有耐心细细读文档,最后还是用了 requirements.txt 这种方式
静态检查
Ruff 的大名已经听说过很多次了
An extremely fast Python linter and code formatter, written in Rust.
主打一个快,用 Rust 编写,就是它了。
java -> python
这个时代有了各种免费的 AI 真好,将 java 函数扔给 AI 转成对应的 python 实现就好了,虽然代码有点丑陋,但是,能用。于是在 AI 的加持下,快速糊出了一个调用客户的接口及其签名实现,从这块开始的考虑很简单:
因为是调用客户的接口,便于测试
接口签名规则和算法是一致的,这个接口通了,则提供给客户的接口也就没问题了
写接口测试用例,使用 unittest 一顿操作猛如虎,写了三个 test case。
# test_push.py
import unittest
class TestPush(unittest.TestCase):
def test_answer(self):
……
self.assertEqual(push(input), 200)
执行
python3.9 -m unittest -v test_push
讲真,跑测试用例的感觉是真爽,虽然中间遇到一些问题,但都一一解决了。
于是找客户那边的对接开发索要了几个接口入参样例,先自测了签名验证的接口。不得不说,客户那边对接的开发同学蛮好的,给我提供了很多帮助,最终顺利完成了两个接口的本地自测。
类型恐惧
我是先用 python2.7,然后用 go,现在又写 python,我发现没有类型检查,内心还是有些不安的,比如以下代码
from pydantic import BaseModel
class ExampleModel(BaseModel):
one: str
two: int
if __name__ == "__main__":
# two 字段的类型为 int
em = ExampleModel(one='1', two='2')
print(em.two)
竟然能正常输出:2
再看一个例子
from pydantic import BaseModel
class ExampleModel(BaseModel):
one: str
two: int
if __name__ == "__main__":
# 我根本没有定义 three 字段
em = ExampleModel(one='1', two='2', three=True)
print(em.two)
还是能正常输出:2
毕竟,one: str
这些只是类型注解,不会在运行时执行数据验证或解析。记忆中我还是用过 python,然而,现在又感觉似乎完全不认识一样。如此情境,我只能增加测试用例,加强静态检查以及自测来补足隐藏的没有执行过的 python 代码。
fastapi 的优点
接口和入参定义真的做的蛮好
class RequestBody(BaseModel):
app_id: str
request_id: str
@app.post("/query")
async def example(
body: RequestBody,
timestamp: Union[str, None] = Header(alias='X-Signature-Timestamp', default=None),
):
header 和 body 的表达很简洁,当然,对swagger的支持也相当友好
fastapi 容器化
到这个环节,真切体会到了 go 的优势:编译后将可执行文件扔到镜像中即可(虽然在推上看过搞编译器的同学对 go 颇有微词)。来看看 fastapi 的 Dockerfile ,官网示例如下:
#
FROM python:3.9
#
WORKDIR /code
#
COPY ./requirements.txt /code/requirements.txt
#
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
#
COPY ./app /code/app
#
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
如果是本地打镜像,记得加上 .dockerignore 文件
**/__pycache__/
*.pyc
*.pyo
*.pyd
就笔者的需求而言,感观上容器化不如使用 python 虚拟环境,因为要把代码 py 文件加到镜像中让人挺难受。长期习惯了 go 应用轻巧的镜像, python 打出来的镜像动辄就 GB 级,属实有点难以接受了。
总结
python,作为我步入职场的初恋编程语言,由于自己长期的冷落,突然对其产生出一种陌生的感觉,总归还是动手少了。python 一直在持续迭代进步,而我已经固步自封了,有一种悲凉之感。不管怎么说,学习还是让人快乐的,要做到真正的终身学习,学以致用,并不简单。突然想起之前带我的一个 TL 对我说的一句话:要勇于把手弄脏,共勉。