Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python入门:项目Layout和技术选型 #99

Open
QingyaFan opened this issue Dec 17, 2024 · 0 comments
Open

Python入门:项目Layout和技术选型 #99

QingyaFan opened this issue Dec 17, 2024 · 0 comments

Comments

@QingyaFan
Copy link
Owner

QingyaFan commented Dec 17, 2024

项目Layout

虽然没有标准的官方推荐,社区还是有一些最佳实践可以参考。1

例如,一个包含多个子模块的Python项目,推荐的项目结构如下:

my_project/
├── README.md
├── LICENSE
├── setup.py
├── requirements.txt
├── .gitignore
├── subproject1/
│   ├── README.md
│   ├── requirements.txt
│   ├── src/
│   │   └── subproject1/
│   │       ├── __init__.py
│   │       └── module1.py
│   ├── tests/
│   │   └── test_module1.py
│   └── scripts/
│       └── script1.py
├── subproject2/
│   ├── README.md
│   ├── requirements.txt
│   ├── src/
│   │   └── subproject2/
│   │       ├── __init__.py
│   │       └── module2.py
│   ├── tests/
│   │   └── test_module2.py
│   └── scripts/
│       └── script2.py
└── common/
    ├── README.md
    ├── requirements.txt
    ├── src/
    │   └── common/
    │       ├── __init__.py
    │       └── utils.py
    ├── tests/
    │   └── test_utils.py
    └── scripts/
        └── common_script.py
  • setup.py,项目的安装脚本,用于定义项目的构建、打包和分发方式。它是基于 setuptools 或 distutils 库编写的脚本,包含了项目的元数据、依赖关系以及其他配置信息。
  • requirements.txt: 根目录下的依赖文件,管理全局依赖。
  • common 是一些通用功能的包,供各个子项目依赖使用

setup.py

基于 setuptools 或 distutils 库编写的脚本,包含了项目的元数据、依赖关系以及其他配置信息。具体作用包括:

  1. 包的元数据定义:项目的名称、版本、描述、作者信息、项目主页等
  2. 依赖关系管理:install_requires 列表中指定的项目运行所需的第三方库及其版本,这样在安装该包时,相关依赖也会自动安装
  3. 入口定义:使用 entry_points 参数可以创建命令行脚本,使得用户在安装包后可以直接通过命令行调用特定功能
  4. 包的包含内容:通过 packagespy_modules 参数指定需要打包的模块和包;还可以使用 package_datainclude_package_data 包含非 Python 文件,如配置文件、静态资源等
  5. 构建和分发:运行 python setup.py sdist 可以生成源码分发包;运行 python setup.py bdist_wheel 可以生成二进制分发包(wheel格式:todo: 什么是wheel格式?)。通过 twine 等工具将构建好的包上传到 PyPI 等包管理平台,方便他人安装使用。
  6. 自定义构建步骤:通过继承和覆盖 setuptools 的命令类,可以在构建过程中添加自定义的操作,例如编译扩展模块、生成文档等。

例如,如下是 vllmsetup.py 中的 setup 命令的内容(当然还有一些辅助内容,这里不列出):

setup(
    name="vllm",
    version=get_vllm_version(),
    author="vLLM Team",
    license="Apache 2.0",
    description=("A high-throughput and memory-efficient inference and "
                 "serving engine for LLMs"),
    long_description=read_readme(),
    long_description_content_type="text/markdown",
    url="https://github.com/vllm-project/vllm",
    project_urls={
        "Homepage": "https://github.com/vllm-project/vllm",
        "Documentation": "https://vllm.readthedocs.io/en/latest/",
    },
    classifiers=[
        "Programming Language :: Python :: 3.9",
        "Programming Language :: Python :: 3.10",
        "Programming Language :: Python :: 3.11",
        "Programming Language :: Python :: 3.12",
        "License :: OSI Approved :: Apache Software License",
        "Intended Audience :: Developers",
        "Intended Audience :: Information Technology",
        "Intended Audience :: Science/Research",
        "Topic :: Scientific/Engineering :: Artificial Intelligence",
        "Topic :: Scientific/Engineering :: Information Analysis",
    ],
    packages=find_packages(exclude=("benchmarks", "csrc", "docs", "examples",
                                    "tests*")),
    python_requires=">=3.9",
    install_requires=get_requirements(),
    ext_modules=ext_modules,
    extras_require={
        "tensorizer": ["tensorizer>=2.9.0"],
        "audio": ["librosa", "soundfile"],  # Required for audio processing
        "video": ["decord"]  # Required for video processing
    },
    cmdclass={"build_ext": cmake_build_ext} if len(ext_modules) > 0 else {},
    package_data=package_data,
    entry_points={
        "console_scripts": [
            "vllm=vllm.scripts:main",
        ],
    },
)

不过:随着 Python 包管理工具的发展,例如 poetry 和 flit,以及 PEP 517/518 标准的引入,setup.py 的使用逐渐被 pyproject.toml 等配置文件所补充或替代。但在很多现有项目和传统流程中,setup.py 仍然是核心的构建和分发脚本。

pyproject.toml

相比于传统的 setup.py,pyproject.toml 更加标准,提升了可读性和可维护性,增强了与现代工具链的兼容性和集成能力,正逐步取代 setup.py 成为项目配置的标准方式。

  • 更加标准:pyproject.toml 是根据 PEP 518 和 621 提供的标准项目配置方式
  • 可读性和可维护性:TOML是声明式定义配置,setup.py 是命令/过程式编程,toml 降低了出错风险
  • 工具链广泛支持:Poetry、Flit等支持并推荐 pyproject.toml,且许多工具配置可以集中在一个配置文件中,减少了需要管理的多个配置文件。

例子:

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "example-package"
version = "0.1.0"
description = "An example Python package"
authors = ["Jane Doe <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.8"
requests = "^2.25.1"

[tool.poetry.dev-dependencies]
pytest = "^6.2.2"
black = "^21.7b0"

[tool.black]
line-length = 88

[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q"
testpaths = ["tests"]

项目技术选型

数据库ORM

sqlalchemy 是一个比较常见的选择,相对于其他ORM:

  • 性能:在处理大规模数据和复杂事务时表现优异;
  • 灵活性:相比于一些专注于高层抽象的 ORM(如 Django ORM),提供了更细粒度的控制,适用于需要复杂查询和定制化操作的项目。

还有与之配套的数据库迁移工具(alembic)。

alembic的基本用法

todo

数据验证与序列化

Pydantic 是一个用于数据验证和设置管理的 Python 库,利用 Python 的类型提示(type annotations)提供强大的数据解析、验证和转换功能。它主要通过定义数据模型(通常是继承自 BaseModel的类)来确保输入数据符合预期的结构和类型,从而解决如下问题:

  1. 数据验证与解析:自动类型转换:Pydantic 可以根据定义的模型自动将输入数据转换为指定的类型。例如,将字符串 "123" 转换为整数 123。强制数据完整性:确保输入数据包含所有必需的字段,并且字段类型符合预期,避免运行时错误。
  2. 简化配置管理:环境变量处理:Pydantic 的 BaseSettings 类可以方便地从环境变量、.env 文件等来源读取配置,适用于管理应用配置
  3. 提升代码可读性与维护性:明确的数据结构定义:通过类型注解和模型类,代码更具自文档性,易于理解和维护;减少样板代码:自动处理数据验证和转换逻辑,减少手动编写重复的验证代码。
  4. 集成与兼容性:与FastAPI 等框架集成良好:Pydantic 是 FastAPI 的核心组件之一,用于定义请求和响应的数据模型,确保 API 的数据交互类型安全。支持复杂嵌套数据结构:可以轻松处理嵌套的 JSON 对象、列表等复杂数据结构。

在Golang的项目中,我们可以类比 Struct + 类型注解的组合,在gin中可以以同样的逻辑进行输入参数的检查。

重试机制

重试机制是一个更加细节的能力,但却是横向能力,我们请求外部系统可能会失败,但大多数的失败是偶发的,可以通过重试来避免这种偶发失败对复杂工作流程的影响。tenacity 是python 生态中一个常用的重试库,当然我们也可以自行编写简单的重试逻辑。

tenacity 使用

todo

依赖管理

现代python 开发中,一般都是为项目启动一个单独的 virtual env,这个evev 有自己的隔离的 python环境和独立的包管理,不会影响其他的应用。在这种模式下,项目的依赖常用 requirements.txt 作为配置。生成这个文件可以有多种方法:

  1. pip freeze > requirements.txt
  2. pipreqs .

实际使用中,pipreqs 生成的依赖总是有一些问题,使用 pip freeze 生成的依赖配置可以顺利安装:pip install -r requirements.txt

python这种依赖管理行为不像一些现代的依赖管理工具链,如 golang 的 go mod 和 rust 中的 cargo。例如:golang 中每次通过 go get 安装依赖包,都会更新和维护 go.mod 和 go.sum,也可以通过 go mod tidy 来整理包。pip 不会这样维护 requirements.txt。

一些新的依赖管理工具如 poetry,会像 golang 中一样维护依赖文件(pyproject.toml 和 poetry.lock 文件),当然poetry 也可以兼容传统的 requirements.txt,所以也可以兼容 CICD 环境不支持 pyproject.toml 的情况。所以,为什么不用 poetry呢?

使用 poetry

poetry 是命令行,推荐使用 pipx 安装。就像 nodejs 中的 npm 和 npx 两个命令,npx 是管理命令行的依赖安装。2

pipx install poetry

如果你是现有项目,在项目根目录执行 poetry init,就会得到 pyproject.toml 配置文件,接下来就通过 poetry add xxx 安装依赖就行,在 pyproject.toml 中的 dependencies 部分就会有依赖和版本限制说明,同时还会写入 poetry.lock 来锁定安装的依赖版本。3

单元测试

pytest 是 python生态中常用的单元测试工具,它有几项很有用的能力:参数化测试(table)、Fixtures、测试标记、捕获日志输出、测试插件。

  1. 参数化测试

这种测试也叫 table test,例如:

import pytest

@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),
    (3, 2, 5),
    (3, 5, 8)
])
def test_addition(a, b, expected):
    assert a + b == expected
  1. Fixtures

pytest 提供了 Fixture 功能,适合用于设置和清理测试环境。例如,测试数据库操作,需要预先准备数据库连接,初始化数据。例子如下:

import pytest
import sqlite3

@pytest.fixture(scope="module")
def db_connection():
    conn = sqlite3.connect(":memory")
    conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)")
    yield conn
    conn.close()

@pytest.fixture
def insert_user(db_connection):
    def _insert_user(user_id, name):
        db_connection.execute("INSERT INTO users VALUES (?, ?)", (user_id, name))
        db_connection.commit()
    return _insert_user

def test_user_operations(db_connection, insert_user):
    insert_user(1, "Alice")
    cursor = db_connection.cursor()
    cursor.execute("SELECT * FROM users WHERE id=1")
    assert cursor.fetchone() == (1, "Alice")
  1. 测试标记

todo

  1. 捕获日志输出

todo

  1. 测试插件

todo

参考

Footnotes

  1. https://stackoverflow.com/questions/193161/what-is-the-best-project-structure-for-a-python-application

  2. https://python-poetry.org/docs/#installation

  3. https://python-poetry.org/docs/basic-usage/

@QingyaFan QingyaFan changed the title Python入门:项目Layout Python入门:项目Layout和技术选型 Dec 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant