日志打印

  1. 在运行 cmd,shell 等命令时,以至少info级别打印执行的命令,以至少debug级别打印命令的原始输出。

  2. 在进行单文件 io 操作前,至少以info级别打印 io 对象的路径。同时操作复数同类文件,如["1.jpg","2.jpg","3.jpg","4.jpg"...]则至少要以debug级别打印

路径操作

获取根目录

在源码运行、pyinstaller 打包、nuitka 打包等不同运行条件下,__file__的值并不相同。一般情况下我们更倾向于推荐使用sys.argv[0]作为获取目录的方法,但这个方法也有需要注意的地方。请看下面这个 fastapi 的例子。

1
uvicorn main:app --reload --port 8001

在这个例子中,我们使用了 uvicorn 来启动main.py中的 fastapi 服务。但如果在这个程序中执行sys.argv[0],将获取到的路径会是uvicorn的而非main.py的。

不要用内置函数获取工作目录

任何情况下,我们都强烈建议不要使用os.getcwd()Path.cwd()方法。来看下面的例子。

1
2
3
4
5
foo/
├─ foo1/
│ └─ ...
└─ foo2/
└─ main.py

如果在 foo1 尝试执行python ../foo2/main.py,可能会产生意料外的状况。

1
/foo/foo1> python ../foo2/main.py

此时获取的结果是foo1而非foo2

谨慎使用 with 语句操作临时文件和临时目录

来看一个 Fastapi 中的例子。

1
2
3
4
5
6
7
8
@app.get("/repeat")
def read_root(msg: str):
"""Repeat the message in a file and return it as a file response."""
with TemporaryDirectory() as tmpdir:
with open(f"{tmpdir}/msg.txt", "w", encoding="utf-8") as f:
f.write(msg)

return FileResponse(f"{tmpdir}/msg.txt", filename="msg.txt")

在这个例子中,使用了TemporaryDirectory来创建一个临时目录并在其中创建了一个文件,然后将这个文件作为响应返回给客户端。然而,这里存在一个问题 —— 这个函数执行return语句之前,会先对with语句进行后处理。在with语句结束时,TemporaryDirectory就会被删除,所以返回的FileResponse实际上是在外部进行处理的。也就是说这段代码实际上是会在with语句的外面读取这个文件,因此将会收到一个FileNotFound错误。(我个人使用的做法是,将文件写入 buffer 变量,然后再进行返回)

http 请求

在日志中记录请求的完整信息

在处理 http 请求时,除了记录请求的完整信息,还要记录请求的响应。这对排查与外部服务有关的问题时非常有帮助。

Python 的语言特性

不会发生的变量遮盖

在 Python 中,变量初始化语句和复制的语句是相同的。同时,for语句不具备单独的作用域。因此,在for循环中,如果将程序过程的中途变量定义为与外部相同,则会错误的修改外部变量。

1
2
3
4
5
6
7
8
9
10
11
12
// javascript
if (true) {
let a = 1;
}

a; // ReferenceError: a is not defined

for (let i = 0; i < 10; i++) {
let b = 2;
}

b; // ReferenceError: b is not defined
1
2
3
4
5
6
7
8
9
10
# python
if True:
a = 1

a # 1

for i in range(10):
b = 2

b # 2