# 重识 Python

## **我与 python 的渊源**

记得当年 python 是自学的，至今还记得入门的那本书的名字叫做《a byte of python》

2014年入行时即写 python，后端 djang 应用，当时 python 版本还是2.7，现在都进化到 3.12了，变化不可谓不大。印象中最深刻的地方(有记忆)在于 python2.7 的 print 支持以下两种写法：

```bash
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 中，仅支持以下一种

```bash
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：

1. 拿到了一个 java 写的服务 repo (项目开发人员即将于两天后离职)
    
2. 一个阿里云服务器 ip、用户名、密码，用来部署 1 中的 java 服务
    
3. 被拉进若干群
    
4. TL给出的 10 分钟不到需求介绍
    
5. 工作日每天下午 5 点半同步进度
    
6. 有一位博士负责大模型调优
    
7. deadline 为5月底
    

这就是全部，彼时，我手头还有其它项目支持的事情，说焦头烂额有点过，但也相差不多。虽然时不时还有其它人或事会打断我，但总归是有相对整块的时间来看遗留下来的 java 代码，梳理了一下这个 java 服务

1. 提供了一个 post 接口，使用 *SSE* 模式， 用于客户方调用 ，接口有签名检验，但是代码中签名检验部分直接放过了……
    
2. 调用了客户的一个 post 接口，接口同样要做签名
    
3. 调用了百度千帆的 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 -&gt; python**

这个时代有了各种免费的 AI 真好，将 java 函数扔给 AI 转成对应的 python 实现就好了，虽然代码有点丑陋，但是，能用。于是在 AI 的加持下，快速糊出了一个调用客户的接口及其签名实现，从这块开始的考虑很简单：

* 因为是调用客户的接口，便于测试
    
* 接口签名规则和算法是一致的，这个接口通了，则提供给客户的接口也就没问题了
    

写接口测试用例，使用 unittest 一顿操作猛如虎，写了三个 test case。

```bash
# test_push.py
import unittest
​
class TestPush(unittest.TestCase):
    def test_answer(self):
        ……
        self.assertEqual(push(input), 200)
```

执行

```bash
python3.9 -m unittest -v test_push
```

讲真，跑测试用例的感觉是真爽，虽然中间遇到一些问题，但都一一解决了。

于是找客户那边的对接开发索要了几个接口入参样例，先自测了签名验证的接口。不得不说，客户那边对接的开发同学蛮好的，给我提供了很多帮助，最终顺利完成了两个接口的本地自测。

## **类型恐惧**

我是先用 python2.7，然后用 go，现在又写 python，我发现没有类型检查，内心还是有些不安的，比如以下代码

```bash
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

再看一个例子

```bash
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 的优点**

接口和入参定义真的做的蛮好

```bash
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 ，官网示例如下：

```yaml

# 
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** 文件

```yaml
**/__pycache__/
*.pyc
*.pyo
*.pyd
```

就笔者的需求而言，感观上容器化不如使用 python 虚拟环境，因为要把代码 py 文件加到镜像中让人挺难受。长期习惯了 go 应用轻巧的镜像， python 打出来的镜像动辄就 GB 级，属实有点难以接受了。

## 总结

python，作为我步入职场的初恋编程语言，由于自己长期的冷落，突然对其产生出一种陌生的感觉，总归还是动手少了。python 一直在持续迭代进步，而我已经固步自封了，有一种悲凉之感。不管怎么说，学习还是让人快乐的，要做到真正的终身学习，学以致用，并不简单。突然想起之前带我的一个 TL 对我说的一句话：要勇于把手弄脏，共勉。
