十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python包开发全流程:从脚本到可pip安装的专业工具

Python包开发全流程:从脚本到可pip安装的专业工具 1. 从“脚本小子”到“包作者”的认知跃迁我刚开始学Python那会儿和很多新手一样写代码就是在一个.py文件里堆逻辑最多分几个函数。项目稍微大点就搞出十几个文件然后写个run.py里面用一堆import把其他文件串起来。这种“游击队”式的开发方式在个人学习和小型项目里还能凑合但一旦代码需要复用、分享或者项目结构复杂起来立刻就捉襟见肘。比如你想把写好的数据处理模块给同事用总不能把十几个文件一股脑发过去然后说“你把它们放一起注意导入路径”吧这既不专业也容易出错。“包”Package这个概念就是Python世界用来解决这个问题的标准答案。它不仅仅是一个文件夹更是一种组织代码、管理依赖、分发软件的规范。学会写包标志着你从一个只会写脚本的“小白”开始向一个懂得工程化思维的“高手”迈进。这不仅仅是技术上的提升更是开发理念的升级。今天我就以一个过来人的身份手把手带你走一遍从零开始编写一个完整Python包的完整流程过程中我会穿插很多官方文档不会写的“坑”和“技巧”让你少走弯路。2. 项目规划你的包到底要解决什么问题在动手创建文件夹和文件之前最重要的一步是想清楚。盲目开始只会导致结构混乱后期重构成本极高。2.1 明确包的核心功能与定位假设我们要创建一个名为text_cleaner的包。它的核心功能是提供一系列简单易用的函数帮助用户快速清洗和预处理中文或英文文本数据比如去除HTML标签、标准化空白字符、处理特殊符号等。这是一个在数据分析、自然语言处理入门领域非常实用的工具。为什么选这个例子因为它足够小便于演示同时又具备一个真实包的典型特征有明确的功能边界、可能包含多个模块、需要考虑配置和扩展性。在规划时你需要问自己几个问题核心用户是谁是数据分析师、学生还是其他开发者这决定了你的API设计是偏向高层易用还是底层灵活。功能边界在哪text_cleaner就只做基础的文本清洗不做分词、词性标注等复杂NLP任务。边界清晰用户不会产生混淆。未来可能如何扩展比如未来可能会增加对日文、韩文的支持或者增加更复杂的清洗规则。在初期设计目录结构时就要为这些可能性留出空间。2.2 设计包的基本结构与模块划分一个结构良好的包其目录本身就是一份文档。对于text_cleaner我建议的初始结构如下text_cleaner_project/ # 项目根目录通常也是Git仓库根目录 ├── text_cleaner/ # 包的源代码目录核心与包名一致 │ ├── __init__.py # 包的初始化文件将模块暴露给用户 │ ├── cleaners.py # 主模块存放核心清洗函数 │ ├── utils.py # 工具模块存放辅助函数 │ └── config.py # 配置模块存放默认配置或常量 ├── tests/ # 单元测试目录 │ ├── __init__.py │ ├── test_cleaners.py │ └── test_utils.py ├── docs/ # 文档目录可选但推荐 ├── examples/ # 使用示例目录强烈推荐 ├── README.md # 项目说明文件 ├── pyproject.toml # 现代Python项目构建和依赖声明文件推荐 ├── setup.py # 传统的包安装脚本备用与pyproject.toml二选一或共存 ├── setup.cfg # 配合setup.py的配置文件 ├── requirements.txt # 开发环境依赖清单 └── .gitignore # Git忽略文件为什么这样设计text_cleaner/与项目根目录分离这是关键。你的包源码放在以包名命名的子目录里。项目根目录用于存放构建、测试、文档等“周边”文件。这符合大多数开源项目的惯例也便于打包工具识别。__init__.py这是将一个普通文件夹变为Python包的关键。即使是空文件也必须存在。我们会在里面写重要的内容。模块按功能分离cleaners.py放核心功能utils.py放内部辅助函数config.py放配置。避免一个文件上千行难以维护。tests/目录独立测试代码不应该混在源码中。独立的测试目录结构清晰也方便用pytest等工具自动发现和运行测试。pyproject.tomlvssetup.py这是现代Python打包的演进。pyproject.toml是PEP 518引入的标准用于声明构建系统要求和项目元数据是当前的首选。setup.py是传统方式虽然仍广泛支持但趋势是向前者迁移。我们的教程会以pyproject.toml为主。3. 核心实现编写包内的源代码规划好了我们就进入text_cleaner/目录开始编写真正的代码。3.1 编写功能模块 (cleaners.py)这是包的核心价值所在。我们实现几个简单的清洗函数。# text_cleaner/cleaners.py import re import html def remove_html_tags(text: str) - str: 移除文本中的HTML标签。 参数: text (str): 可能包含HTML标签的原始文本。 返回: str: 移除HTML标签后的纯净文本。 示例: remove_html_tags(pHello bWorld/b!/p) Hello World! # 使用一个简单的正则表达式移除尖括号及其内容 # 注意这个正则对于复杂的HTML如嵌套标签、属性包含可能不完美适用于简单场景。 clean_text re.sub(r‘[^]‘, ‘’, text) return clean_text def normalize_whitespace(text: str) - str: 标准化文本中的空白字符。 将连续的空白字符空格、制表符、换行等替换为单个空格并去除首尾空格。 参数: text (str): 原始文本。 返回: str: 标准化空白后的文本。 # 使用正则匹配任意空白字符序列 cleaned re.sub(r‘\s‘, ‘ ‘, text) return cleaned.strip() def clean_text_basic(text: str, remove_html: bool True) - str: 基础文本清洗流水线。 参数: text (str): 原始文本。 remove_html (bool): 是否移除HTML标签。默认为True。 返回: str: 经过清洗的文本。 if not isinstance(text, str): raise TypeError(f“输入必须是字符串类型当前类型为 {type(text).__name__}”) cleaned text if remove_html: cleaned remove_html_tags(cleaned) cleaned normalize_whitespace(cleaned) # 可以在这里添加更多基础清洗步骤比如解码HTML实体 cleaned html.unescape(cleaned) return cleaned要点与避坑类型提示像text: str-str这样的类型提示Type Hints在Python 3.5中非常推荐使用。它不会影响运行时但能让IDE如VSCode、PyCharm提供更好的代码补全和错误检查也让你的代码更清晰。文档字符串每个函数下的“”“ ... ”“”是文档字符串。这是编写高质量包的基本素养。好的文档字符串应该说明功能、参数、返回值和简单示例。这些内容未来会自动生成到API文档中。参数验证在clean_text_basic中我们检查了输入类型。对于面向用户的函数进行基本的参数验证可以避免难以理解的底层错误提升用户体验。函数设计我们既提供了细粒度的remove_html_tags也提供了聚合的clean_text_basic。这样既满足了需要灵活组合的高级用户也满足了希望开箱即用的初级用户。3.2 编写工具模块 (utils.py)这个模块放一些内部使用的辅助函数这些函数通常不直接暴露给最终用户。# text_cleaner/utils.py import unicodedata def _is_punctuation(char: str) - bool: 内部函数判断一个字符是否为标点符号。 # 使用unicodedata.category判断字符的Unicode分类 # ‘P‘ 开头的分类代表标点符号Punctuation return unicodedata.category(char).startswith(‘P‘) def remove_punctuation(text: str) - str: 移除文本中的所有标点符号。 参数: text (str): 原始文本。 返回: str: 移除标点后的文本。 return ‘‘.join(char for char in text if not _is_punctuation(char))要点命名约定以一个下划线_开头的函数如_is_punctuation是Python中约定俗成的“私有”函数。它意味着“这个函数是模块内部使用的外部用户请不要直接调用因为我不保证其接口稳定性”。这是一种软性约束有助于维护清晰的API边界。使用标准库unicodedata是Python标准库用于处理Unicode字符。用它来判断标点比写一堆正则表达式更可靠、更国际化。3.3 编写配置模块 (config.py)存放一些常量或默认配置。# text_cleaner/config.py # 默认的清洗规则开关 DEFAULT_REMOVE_HTML True DEFAULT_REMOVE_EXTRA_SPACES True # 支持的语言列表为未来扩展预留 SUPPORTED_LANGUAGES [‘en‘, ‘zh‘]3.4 激活包编写__init__.py这是包的门面决定了用户import text_cleaner时到底能直接用到什么。# text_cleaner/__init__.py “”“ Text Cleaner - 一个简单实用的文本清洗工具包。 “”“ # 定义包的版本号便于管理和查询 __version__ ‘0.1.0‘ __author__ ‘Your Name‘ __email__ ‘your.emailexample.com‘ # 将核心功能直接暴露在包顶层方便用户使用 # 用户可以通过 from text_cleaner import clean_text_basic 导入 from .cleaners import ( clean_text_basic, remove_html_tags, normalize_whitespace, ) # 选择性暴露工具函数 from .utils import remove_punctuation # 也可以暴露整个模块让用户按需深入使用 # 用户可以通过 import text_cleaner.cleaners as tc 导入 from . import cleaners from . import utils from . import config # 定义一个方便的 __all__ 列表用于控制 from text_cleaner import * 的行为 __all__ [ ‘clean_text_basic‘, ‘remove_html_tags‘, ‘normalize_whitespace‘, ‘remove_punctuation‘, ‘cleaners‘, ‘utils‘, ‘config‘, ‘__version__‘, ]__init__.py的几种风格与选择空文件最简单的包。用户必须import text_cleaner.cleaners然后text_cleaner.cleaners.clean_text_basic(...)这样调用比较繁琐。暴露关键函数/类推荐如上例所示将最常用的功能提升到包顶层。用户from text_cleaner import clean_text_basic即可体验最好。延迟导入如果包很大在__init__.py中导入所有子模块可能导致启动变慢。可以使用动态导入或只在__init__.py中定义__all__在实际使用时再导入。但对于我们这种小包直接导入完全没问题。重要经验在__init__.py中暴露什么是你作为包作者对用户的承诺。一旦发布随意移除或更改顶层API会破坏用户的代码即破坏向后兼容性。因此初期要谨慎设计后期变更要通过版本号如从0.1.0升到0.2.0来明确告知用户。4. 打包与发布准备让世界能用上你的包代码写好了但还只是你本地的一堆文件。要让它成为一个可以pip install的包需要完成“打包”工作。4.1 现代配置使用pyproject.toml在项目根目录text_cleaner_project/下创建pyproject.toml文件。这是现代Python项目的核心配置文件。# pyproject.toml [build-system] requires [“setuptools61.0“, “wheel“] build-backend “setuptools.build_meta“ # 项目元数据 [project] name “text-cleaner“ # 包在PyPI上的名字通常用小写和连字符 version “0.1.0“ # 版本号遵循语义化版本规范 authors [ {name “Your Name“, email “your.emailexample.com“}, ] description “A simple and practical text cleaning toolkit for Chinese and English.“ readme “README.md“ license {text “MIT“} # 选择合适的开源协议 classifiers [ # PyPI分类帮助用户找到你的包 “Programming Language :: Python :: 3“, “Programming Language :: Python :: 3.8“, “Programming Language :: Python :: 3.9“, “Programming Language :: Python :: 3.10“, “Programming Language :: Python :: 3.11“, “Programming Language :: Python :: 3.12“, “License :: OSI Approved :: MIT License“, “Operating System :: OS Independent“, “Topic :: Text Processing“, “Topic :: Utilities“, ] keywords [“text“, “cleaning“, “preprocessing“, “nlp“] dependencies [ # 运行时依赖用户安装你的包时会自动安装这些 # 我们这个包只用了标准库所以这里可以是空的 # “requests2.25.0“, # 如果需要第三方库在这里声明 ] [project.urls] “Homepage“ “https://github.com/yourusername/text_cleaner“ # 项目主页 “Bug Tracker“ “https://github.com/yourusername/text_cleaner/issues“ # 问题追踪 # 可选定义开发/测试所需的额外依赖组 [project.optional-dependencies] dev [ # 开发环境依赖如测试框架、代码检查工具 “pytest7.0“, “black23.0“, # 代码格式化 “isort5.12“, # import排序 “flake86.0“, # 代码风格检查 ]关键字段解读name:这是最重要的字段之一。它在PyPIPython包索引上必须是唯一的。通常采用小写字母和连字符。注意这和你的源码目录名text_cleaner以及用户导入时的名字import text_leaner可以不同但强烈建议保持关联避免混淆。version: 遵循 语义化版本规范 Major.Minor.Patch。0.1.0表示初始开发版本。dependencies: 列出你的包运行时必须依赖的其他包。如果这里写了requests那么用户pip install text-cleaner时requests会被自动安装。务必谨慎只放真正必需的依赖避免给用户安装不必要的包。[project.optional-dependencies]: 定义可选依赖组。比如dev组包含了测试、格式化等只在开发时需要用户安装时不需要的工具。用户可以通过pip install “text-cleaner[dev]“来安装这些额外依赖。4.2 传统配置了解setup.py和setup.cfg虽然pyproject.toml是趋势但你仍然会在很多老项目中看到setup.py。它的作用类似但使用Python脚本编写。# setup.py (备用如果使用pyproject.toml这个文件可以简化或不要) from setuptools import setup, find_packages setup( name“text-cleaner“, version“0.1.0“, author“Your Name“, author_email“your.emailexample.com“, description“A simple text cleaning toolkit.“, long_descriptionopen(“README.md“).read(), long_description_content_type“text/markdown“, packagesfind_packages(), # 自动发现所有包 python_requires“3.8“, # 指定支持的Python版本 install_requires[], # 运行时依赖同pyproject.toml的dependencies extras_require{ # 可选依赖同pyproject.toml的optional-dependencies “dev“: [“pytest“, “black“, “isort“, “flake8“], }, classifiers[...], # 同pyproject.toml的classifiers )当前最佳实践对于新项目优先使用pyproject.toml来声明元数据和依赖。setup.py可以保留一个极简版本或者完全不用。工具链如pip、build会优先读取pyproject.toml。4.3 编写重要的辅助文件README.md: 项目的门面应该包含项目简介、快速安装指南、简单使用示例、功能特性列表、贡献指南、许可证信息等。一个好的README能极大提升项目的吸引力。requirements.txt: 通常用于记录开发环境的精确依赖版本便于复现环境。可以通过pip freeze requirements.txt生成。注意它和pyproject.toml中的dependencies目的不同。dependencies声明的是“这个包需要什么”而requirements.txt记录的是“开发这个项目的环境里具体有哪些包和版本”。.gitignore: 忽略不需要提交到Git仓库的文件如__pycache__/,*.pyc,dist/,build/,.env,.idea/等。可以从 github/gitignore 获取Python项目的模板。4.4 本地构建与测试安装在发布到PyPI之前一定要在本地测试打包和安装。安装构建工具pip install build twine构建分发文件 在项目根目录运行python -m build这个命令会读取pyproject.toml在dist/目录下生成源代码包.tar.gz和构建发行版.whl文件。本地安装测试 你可以直接从本地文件安装测试是否成功# 使用pip安装当前目录开发模式代码改动直接生效 pip install -e . # 或者从构建好的wheel文件安装 pip install dist/text_cleaner-0.1.0-py3-none-any.whl安装成功后打开Python解释器测试 import text_cleaner print(text_cleaner.__version__) ‘0.1.0‘ from text_cleaner import clean_text_basic clean_text_basic(‘p Hello world! /p‘) ‘Hello world!‘5. 测试、文档与持续集成打造专业级项目一个可用的包和一个专业的包之间差的就是测试、文档和自动化。5.1 编写单元测试在tests/目录下编写测试文件。使用pytest框架非常方便。# tests/test_cleaners.py import pytest from text_cleaner import clean_text_basic, remove_html_tags def test_remove_html_tags(): assert remove_html_tags(‘pHello/p‘) ‘Hello‘ assert remove_html_tags(‘HellobrWorld‘) ‘HelloWorld‘ # 注意这里标签被移除中间没有空格了 assert remove_html_tags(‘No tags here‘) ‘No tags here‘ def test_clean_text_basic(): # 测试基础功能 result clean_text_basic(‘ bHi/b there! ‘) assert result ‘Hi there!‘ # 测试关闭HTML移除 result clean_text_basic(‘ bHi/b there! ‘, remove_htmlFalse) assert result ‘bHi/b there!‘ # 测试类型错误 with pytest.raises(TypeError): clean_text_basic(123) def test_normalize_whitespace(): from text_cleaner.cleaners import normalize_whitespace assert normalize_whitespace(‘ hello world \n\n‘) ‘hello world‘运行测试# 在项目根目录运行 pytest # 或者带详细输出 pytest -v测试的重要性测试不仅能保证代码质量更是你未来修改代码时的“安全网”。每次添加新功能或修复Bug都应补充相应的测试。5.2 生成API文档虽然README.md提供了概述但详细的API文档对于用户至关重要。可以使用Sphinx或pdoc等工具自动从代码的文档字符串生成。一个更轻量级的方法是使用pdocpip install pdoc # 为你的包生成HTML文档 pdoc text_cleaner --http localhost:8080然后打开浏览器访问http://localhost:8080就能看到自动生成的、格式美观的API文档了。你可以将生成的静态文件部署到GitHub Pages等地方。5.3 配置代码风格与质量检查统一的代码风格让项目更易维护。常用的工具有Black: 自动格式化代码无需争论风格。isort: 自动排序import语句。Flake8: 检查代码风格和潜在错误。可以在pyproject.toml中配置它们这也是现代项目的做法# 在pyproject.toml中添加可选但推荐 [tool.black] line-length 88 target-version [‘py38‘] [tool.isort] profile “black“ line_length 88 [tool.flake8] max-line-length 88 extend-ignore “E203, W503“ # 忽略一些与black冲突的规则然后你可以配置IDE在保存时自动运行这些工具或者将其添加到Git的pre-commit钩子中确保提交的代码都是符合规范的。5.4 使用Git进行版本控制这是现代软件开发的基础。初始化Git仓库将代码提交上去。git init git add . git commit -m “Initial commit: basic text cleaner package“将仓库推送到GitHub或GitLab等平台不仅是为了备份更是为了协作和开源。5.5 设置持续集成CI对于开源项目设置CI如GitHub Actions可以自动化测试、代码检查和发布流程。例如每次你推送代码到GitHubGitHub Actions可以自动运行pytest确保测试通过用black和flake8检查代码风格。一个简单的.github/workflows/test.yml示例name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: [“3.8“, “3.9“, “3.10“, “3.11“, “3.12“] steps: - uses: actions/checkoutv3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-pythonv4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install .[dev] # 安装包及其开发依赖 - name: Lint with flake8 run: flake8 text_cleaner tests - name: Test with pytest run: pytest6. 发布到PyPI与版本管理当你的包经过充分测试文档也准备妥当后就可以考虑发布到PyPIPython Package Index让全世界的Python用户都能通过pip install安装它。6.1 发布到PyPI注册PyPI账户前往 pypi.org 注册一个账户。同时建议也注册 test.pypi.org 账户用于测试发布。配置认证在用户主目录创建.pypirc文件存放你的API令牌在PyPI网站账户设置中生成。[distutils] index-servers pypi testpypi [pypi] username __token__ password 你的PyPI API令牌 [testpypi] repository https://test.pypi.org/legacy/ username __token__ password 你的TestPyPI API令牌构建包确保pyproject.toml配置正确然后运行python -m build。上传到TestPyPI强烈推荐先测试python -m twine upload --repository testpypi dist/*从TestPyPI安装测试pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple text-cleaner测试一切正常。正式发布到PyPIpython -m twine upload dist/*6.2 版本管理策略发布后当你修复Bug或添加新功能时需要更新版本并重新发布。遵循语义化版本规范主版本号Major当你做了不兼容的API修改。次版本号Minor当你向下兼容地新增了功能。修订号Patch当你向下兼容地修复了问题。例如从0.1.0开始修复了一个HTML标签移除的小Bug - 发布0.1.1新增了一个remove_emojis函数 - 发布0.2.0重构了API将clean_text_basic改名为clean_text- 发布1.0.0每次发布前更新pyproject.toml中的version字段提交代码打上Git标签git tag v0.1.1然后构建并上传新的分发文件。6.3 维护与更新包发布后工作并未结束。你需要关注Issues用户可能会在GitHub上提出问题或Bug。及时响应和处理。定期更新依赖如果你的包依赖了第三方库定期检查并更新它们的版本以修复安全漏洞或兼容新Python版本。撰写更新日志CHANGELOG在CHANGELOG.md文件中记录每个版本的变更让用户清楚知道升级后有什么不同。从写一个简单的.py文件到构建一个结构清晰、测试完备、文档齐全、可以通过pip安装的标准化Python包这个过程是每个Python开发者成长的必经之路。它强迫你思考代码的组织、API的设计、用户的体验和项目的可持续性。虽然初期会感觉繁琐但一旦掌握你将拥有创建可复用、可维护、可协作的软件组件的能力这才是真正从“脚本小子”迈向“软件工程师”的关键一步。我建议你从今天这个text_cleaner的例子开始亲手实践每一个步骤遇到问题就去查阅官方文档如 Python Packaging User Guide 和社区资源。很快你就会发现打包和发布不再是神秘的黑盒而是一个清晰、可控的工程流程。
返回列表