我在参与不同公司和项目时发现了一些与 Python 相关的问题,这些问题通常令人烦恼、难以维护,而且已经显得过时了。使用以下这些方式,能让你的项目更加优雅。
使用 pyproject.toml
不要再在你的 README.md
文件里写用 pip install -r requirements.txt
来安装依赖了。虽然 pip
确实可以做到,但我们不推荐这么做。requirements.txt
只是个意外的“标准”。
相反,直接使用 pyproject.toml
,它更合理、更现代化,并且 pip
能完全理解它。它还能帮助你定义开发环境和依赖组,避免创建额外的文件如 dev-requirements.txt
和复杂的 CI/CD 管道规则。
使用版本与项目管理工具
我个人偏爱 UV,Poetry 也很不错。这些工具都能读取 pyproject.toml
文件,处理依赖管理、构建、在沙盒中运行应用、管理 Python 版本等等。花一小时学习这些工具,能让你少掉不少头疼事。请使用一个虚拟环境(如 venv
),以免全局范围的依赖冲突。
顺便提一句,UV 的依赖安装速度比 pip
快得多。
使用类型提示
类型提示非常有助于代码的可读性,也能帮助像 ruff
和 mypy
这样的静态检查工具了解代码逻辑。你的同事也会更喜欢你的代码。
这段代码很难理解:
def do_something(data):
for x in data:
for y in data[x]:
transmute(data[x][y])
但加上类型提示后:
def alter_map(data: Map) -> None:
"""修改二维 map 数据。直接修改传入的对象"""
for x in data:
for y in data[x]:
transmute(data[x][y])
更清晰、直观,尤其是两年后回头看自己的代码。请给函数的所有参数、返回值等添加类型提示。
在函数注释中添加 Raises
部分
虽然有些人可能觉得这是多余的,但添加一个 Raises
部分可以减少代码的意外行为,尤其在维护阶段很有帮助。
from typing import Dict, Any, Optional
def query_db(query: SqlQuery) -> Optional[Dict[str, Any]]:
"""调用数据库。
Args:
query: 数据库查询对象。
Raises:
ClientException: 客户端错误。
TimeoutException: 数据库超时。
ConnectionException: 网络连接问题。
Returns:
数据库查询的响应(如果有)。
"""
pass
这样的注释对开发和维护都很友好。
使用 Pydantic 模型
函数中用多个参数传递不变的数据是一种常见反模式。例如,数据处理管道中环境变量通常只被查一次且不会改变。与其这样操作:
def process_data(env, data, config):
...
不如直接定义一个 Pydantic 模型:
from pydantic import BaseModel
class EnvConfig(BaseModel):
env: str
data: dict
config: dict
然后只传递一个参数:
def process_data(config: EnvConfig):
...
这样不仅有类型检查,还能减少参数传递的复杂性。
代码是不是优雅了许多?
使用格式化工具和静态检查工具
JavaScript 项目几乎已经把 Prettier 当成标准了。在 Python 中,可以使用 Ruff,它以 Black 的格式化标准为基础,速度极快。它不仅能格式化代码,还能检查一些常见问题。
虽然 Ruff 的 linting 不如静态分析工具(如 mypy)全面,但已经足够强大。
以下是可以加入到 pyproject.toml
的一些规则配置:
[tool.ruff]
target-version = "py12"
[tool.ruff.lint]
extend-select = [
'D', # pydocstyle
'E', 'W', # pycodestyle
'F', # pyflakes
'I', # sort imports
'UP', # pyupgrade
'RUF', # ruff 自定义规则
'SIM', # 简化性规则
'C90', # 复杂度规则
]
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.isort]
combine-as-imports = true
split-on-trailing-commas = false
这些规则可以根据需要自由选择。
使用 Pytest 替代 Unittest
如果有机会,将 unittest
替换为 pytest
。
它提供了更加简洁的语法和丰富的功能。使用 pytest
不需要像 unittest
那样继承 TestCase
类,只需直接定义测试函数,并用 Python 的原生断言语法(assert)即可。相比 unittest
的 self.assertEqual
等方法,pytest
的断言更加直观,输出也更清晰。
测试文件命名需要以 test_
开头或以 _test.py
结尾,测试函数名称也需以 test_
开头。pytest
提供了强大的参数化测试功能,可以通过 @pytest.mark.parametrize
轻松实现多组输入输出的测试。
pytest
还拥有丰富的插件生态,可以扩展测试功能,比如生成测试覆盖率报告或进行性能分析。相比 unittest
,pytest
更适合快速开发和维护现代项目,尤其是在需要灵活性和扩展性的场景中。
其他建议
- 用
orjson
替代内置的json
库:性能更高。 - 始终使用 f-strings:取代字符串拼接、
.format()
或%
格式化。 - 用
pathlib
替代os.path
:更现代化且更具可读性。 - 用
click
替代argparse
或sys.argv
:更强大的命令行参数解析工具。