我写这篇文章的原因是分享一些关于保持项目干净的见解,即使有很多贡献者。考虑到数据不断变化的性质以及 Python 库和应用程序中的处理需求,这一点对于数据工程师来说尤其重要。
标题可能听起来有点标题党,但如果您遵循这些步骤,您的 Python 代码将变得更容易管理。如果您是一名高级开发人员,您可能不会在这里找到任何新东西 - 别担心,我为您准备了一个有趣的模因。
这可能看起来微不足道,但我实际上认识一些人,他们只将代码存储在本地计算机上 - 不幸的是,由于他们没有在其他地方备份它,所以丢失了代码。有多种可用的版本控制系统,例如 Git,它可与 GitHub、GitLab 和 Bitbucket 等平台配合使用。虽然 Git 是许多人的首选,但 SVN (Subversion) 和 Mercurial 等其他版本控制系统仍然在代码管理中发挥着重要作用。
在本指南中,我打算创建一个小型演示 Python 库,其中包含一个函数来说明基本数据处理。它并不是一个完整的工具包,而是作为一个简单的示例来演示代码质量、环境管理和 CI/CD 工作流程等最佳实践。
首先,我为我们的演示 Python 库创建了一个存储库,并将其命名为 blog_de_toolkit。它是公开的,因此请随意分叉它并使用现有代码作为您自己的项目的起点。由于良好的文档至关重要,因此我提供了一个建议的空自述文件。为了保持仓库整洁,我添加了一个默认的 Python .gitignore 模板,以防止上传不必要的文件。
现在我们有了存储库,我们可以克隆它。
我不打算在这里深入探讨 Git 命令——你可以在网上找到大量教程。如果您不喜欢使用普通终端 CLI,您还可以使用 GitHub Desktop 或 SourceTree 等工具来管理存储库,它们提供了更直观的界面。
就我个人而言,我非常喜欢使用 GitHub Desktop。因此,让我们将存储库克隆到本地计算机并在您喜欢的 IDE 中打开它。
让我们看看到目前为止我们得到了什么:
开始还不错!
对于第 2 步,我们将组织我们的 de_toolkit 项目。良好的结构可以轻松找到东西并保持一切整洁。我们将为代码、测试和文档创建文件夹,设置一个简单、干净的框架来构建。
blog_de_toolkit/ │ ├── de_toolkit/ │ ├── __init__.py │ └── data_tools.py │ ├── tests/ │ ├── __init__.py │ └── test_data_tools.py │ ├── .gitignore ├── setup.py └── README.md
我们有一个主文件夹来存放我们将要添加的所有有用代码,一个 tests 文件夹用于我们未来的单元测试,以及一个 .gitignore 将不必要的文件保留在我们的存储库之外。还有一个 setup.py 文件,这是使项目可安装的基本设置。我现在不会详细介绍它,因为我们稍后将在第 8 步:创建分发包中介绍它。
在设置项目结构时,保持一致会产生巨大的差异。随着项目的发展,最好将其分解为更小的模块,例如将 data_tools.py 拆分为 csv_tools.py 和 json_tools.py。这样,您就可以更轻松地管理和查找所需内容,而无需翻阅长文件。
添加 docs/ 文件夹也是一个明智之举,即使它只是以一些注释开头。它将帮助您(和其他人)随着项目的发展跟踪事情的运作方式。如果您正在使用 YAML 或 JSON 等配置,configs/ 文件夹可以帮助保持整洁。如果您计划编写自动化或测试脚本,scripts/ 文件夹将使它们井然有序。
此时,我们需要安装一些额外的库来继续构建项目。
当然,我们可以从命令行运行 pip install 来安装我们需要的依赖项。但是,如果我们要处理多个项目,每个项目都需要不同版本的 Python 和库怎么办?这就是虚拟环境的用武之地——它们隔离每个项目的依赖关系,包括特定的 Python 版本,因此一切都保持独立和独立。
幸运的是,有很多工具可以创建虚拟环境,因此您可以选择最适合您的工具:
virtualenv
venv
康达
pyenv
pipenv
诗歌
就我个人而言,我是 pyenv 的忠实粉丝,所以这就是我将在这里使用的。我已经将它安装在我的笔记本电脑上,因为它是我工作和个人项目的首选。
让我们从安装 Python 开始:
blog_de_toolkit/ │ ├── de_toolkit/ │ ├── __init__.py │ └── data_tools.py │ ├── tests/ │ ├── __init__.py │ └── test_data_tools.py │ ├── .gitignore ├── setup.py └── README.md
如果 pyenv 无法识别此 Python 版本,请先尝试更新它。例如,如果您使用的是 Mac 并使用 Homebrew 安装了 pyenv,请运行:
pyenv install 3.12.2
如果遇到错误 ModuleNotFoundError:没有名为 '_lzma' 的模块,请尝试:
brew update && brew upgrade pyenv
接下来,在我们的项目文件夹中,创建一个新的虚拟环境:
brew install readline xz
现在,将本地Python版本设置为您刚刚创建的虚拟环境:
pyenv virtualenv 3.12.2 de_toolkit
如果在 MacOS 上运行命令后环境没有切换,网上有一个有用的线程提供了解决方案。一旦一切设置正确,您应该在命令行开头看到 de_toolkit,如下所示:
现在,让我们安装我们的依赖项:
pyenv local de_toolkit
接下来,我们将把所有已安装的软件包及其版本保存到requirements.txt 文件中。这使得共享项目的依赖项或在其他地方重新创建相同的环境变得容易:
pip install setuptools wheel twine pandas
这是我们得到的已安装软件包的列表:
当然,如果您愿意,您可以编辑requirements.txt 文件以仅保留主库及其版本。
这一步至关重要——可能是最重要的步骤之一。您可能听说过有关 GitHub 存储库中的凭据被泄露或敏感令牌意外在公共场合共享的恐怖故事。为了避免这种情况,必须从一开始就将敏感信息排除在代码之外。否则,很容易忘记您硬编码了数据库密码,推送了更改,然后突然——您的凭据现在公开了。
硬编码密码、API 密钥或数据库凭据是主要的安全风险。如果它们进入公共仓库,它们可能会损害您的整个系统。处理秘密的最安全方法是将它们存储在环境变量或 .env 文件中。为了帮助将这些变量加载到您的 Python 项目中,我们将使用 python-dotenv 库。它从 .env 文件中读取键值对,并将它们用作代码中的环境变量。
首先,安装库:
pip freeze > requirements.txt
在项目文件夹中创建一个 .env 文件,其中包含以下内容:
pip install python-dotenv
现在,让我们修改 data_tools.py 以使用 python-dotenv:
加载这些机密当您调用 load_dotenv() 时,它会在当前目录中搜索 .env 文件并将其内容加载到环境中。使用 os.getenv() 允许您在代码中安全地访问这些变量,使凭据与源代码隔离并降低意外暴露的风险。
一个关键提示是避免将 .env 文件提交到版本控制。将其添加到 .gitignore 中以防止意外推送:
blog_de_toolkit/ │ ├── de_toolkit/ │ ├── __init__.py │ └── data_tools.py │ ├── tests/ │ ├── __init__.py │ └── test_data_tools.py │ ├── .gitignore ├── setup.py └── README.md
如果您使用 VSCode,有一个有用的 dotenv 扩展可以自动识别您的 .env * 文件。如果您更喜欢从终端工作,您可以导出 *.env 文件,如下所示:
pyenv install 3.12.2
在处理项目时,尝试编写易于理解和管理的小型、可重用的函数。一个好的经验法则是:“如果您使用它超过两次,请将其变成一个函数。”
在我们的 data_tools.py 中,我们创建一个函数来演示典型的数据工程逻辑,例如从 CSV 加载数据并清理它:
专业提示:在 Python 中坚持使用 snake_case
作为函数和变量名称 - 它可以使您的代码保持一致且易于阅读。避免使用 x 或 df2 等神秘名称;清晰的描述性名称使您的代码更易于使用。我们在这里使用文档字符串来描述函数的功能、参数和返回类型。这使得其他开发人员(以及未来的你)可以轻松了解如何使用该功能。有几种流行的文档字符串约定,但最常见的包括 PEP 257、Google Style 和 NumPy Style:
对于较小的函数,PEP 257 通常就足够了,但对于更复杂的项目,Google 或 NumPy 样式提供了更多的清晰度和结构。
Python 中的类型提示(如我们示例中的 file_path: str)显示函数输入和输出的预期数据类型。它们提高了可读性,帮助发现错误,并通过设定明确的期望使协作变得更容易。
以下是类型提示如何改进函数签名的示例:
blog_de_toolkit/ │ ├── de_toolkit/ │ ├── __init__.py │ └── data_tools.py │ ├── tests/ │ ├── __init__.py │ └── test_data_tools.py │ ├── .gitignore ├── setup.py └── README.md
在这个例子中,类型提示 file_path: str 表明参数应该是一个字符串,而 ->; pd.DataFrame 指示该函数返回一个 Pandas DataFrame。这使得该函数的行为一目了然。类型提示还可以与 IDE 和 linter(例如 PyCharm、VSCode 或 mypy)很好地配合使用,在传递不兼容的类型时提供自动完成和早期警告。
如果函数可以返回多种类型或无,您可以使用输入模块中的可选:
pyenv install 3.12.2
这表明该函数可以返回字符串或 None。对于更复杂的数据结构,您可以使用输入模块中的 List、Dict 或 Tuple 来指定预期类型。
编写单元测试是一种简单的方法,可以确保您的代码执行预期的操作,而不会出现意外的情况。它们可以帮助您及早发现错误并充满信心地进行更改,因为您知道一切仍然按预期运行。在 Python 中,有多个可用于单元测试的库,每个库都有其优点和生态系统:
单元测试
pytest
鼻子2
假设
对于本指南,我们将使用 pytest 因为它简单、灵活且易于使用。您可以使用以下命令安装它:
brew update && brew upgrade pyenv
接下来,在 tests/ 文件夹中创建一个名为 test_data_tools.py 的文件。让我们为之前实现的代码编写一些测试。这是我们的 load_and_clean_data() 函数和环境变量检索逻辑的示例测试:
在 test_load_and_clean_data() 中,我们使用 StringIO 模拟 CSV 文件作为输入。这使我们无需实际文件即可进行测试。该测试验证该函数是否正确删除重复项和 NaN 值,检查 DataFrame 是否没有丢失数据,并确认“名称”列中的唯一条目正确。
在test_get_database_url()和test_get_api_key()中,我们使用pytest提供的monkeypatch工具来在测试过程中临时设置环境变量。这确保函数返回预期值,而不需要真正的环境变量。
要运行所有测试,只需执行以下命令:
blog_de_toolkit/ │ ├── de_toolkit/ │ ├── __init__.py │ └── data_tools.py │ ├── tests/ │ ├── __init__.py │ └── test_data_tools.py │ ├── .gitignore ├── setup.py └── README.md
我喜欢pytest的原因之一是它的灵活性。它超越了基本的单元测试,提供了强大的功能,如夹具、参数化测试和插件。夹具可让您设置多个测试可以重复使用的测试数据或配置,从而使您的代码保持干燥(不要重复)。参数化测试允许您使用不同的输入运行相同的测试,从而节省时间并减少重复。如果您需要扩展 pytest 的功能,可以使用广泛的插件生态系统,例如测试 Django 应用程序、测量代码覆盖率或模拟 HTTP 请求。
保持高代码质量可确保您的代码易于阅读、维护并且没有常见错误。有多种工具可以帮助实施一致的编码标准、自动格式化代码并及早检测潜在问题。一些流行的选项包括 pylint、flake8、黑色 和 检测秘密。
pylint 强制执行编码标准并捕获常见错误。
flake8 结合了检测样式违规和逻辑错误的工具。
black 是一个固执己见的格式化程序,可确保您的代码遵循 PEP8 标准。
检测秘密扫描您的代码以防止硬编码的秘密被暴露。
您可以通过以下方式安装这些工具:
pyenv install 3.12.2
例如,在特定文件或目录上运行 pylint:
brew update && brew upgrade pyenv
您将收到一份报告,其中包含警告和改进代码的建议。要忽略特定警告,您可以使用:
brew install readline xz
您还可以使用flake8来查找样式问题和逻辑错误:
blog_de_toolkit/ │ ├── de_toolkit/ │ ├── __init__.py │ └── data_tools.py │ ├── tests/ │ ├── __init__.py │ └── test_data_tools.py │ ├── .gitignore ├── setup.py └── README.md
要自动格式化代码,请运行 black:
pyenv install 3.12.2
您可以使用预提交挂钩自动执行该过程,而不是每次进行更改时手动运行这些工具。预提交挂钩在每次提交之前自动运行,如果任何工具失败,则会阻止提交。
首先,安装预提交软件包:
brew update && brew upgrade pyenv
接下来,在项目目录中创建一个 .pre-commit-config.yaml 文件,其中包含以下内容(这里我使用了所有我最喜欢的基本预提交):
激活本地存储库中的预提交挂钩:
blog_de_toolkit/ │ ├── de_toolkit/ │ ├── __init__.py │ └── data_tools.py │ ├── tests/ │ ├── __init__.py │ └── test_data_tools.py │ ├── .gitignore ├── setup.py └── README.md
现在,每次您尝试提交时,这些工具都会自动运行。如果任何工具失败,提交将被阻止,直到问题得到解决。您还可以在代码库中手动运行所有挂钩:
pyenv install 3.12.2
现在我们已经构建了我们的项目,编写了一些代码,添加了测试并设置了预提交挂钩,下一步是弄清楚其他人(甚至未来的我们)如何轻松使用它。打包项目使这成为可能。它允许我们将所有内容整齐地捆绑在一起,以便无需手动复制文件即可安装和使用。
要共享您的项目,您需要正确构建包、编写有意义的自述文件、创建启动脚本并生成分发包。一个好的自述文件通常包括项目名称和其功能的简要描述、安装说明、使用示例、设置环境的开发说明以及贡献指南。您可以在存储库中找到我们的 blog_de_toolkit 项目的简单 README.md 示例。
任何 Python 包的核心都是 setup.py 文件。该文件是我们定义打包和安装项目所需的元数据和配置的地方。它包括项目的 名称、版本和描述,这使其可识别。 long_description 从 README 文件中读取内容,以便当用户在 PyPI 上看到该项目时为他们提供有关该项目的更多背景信息。我们在 install_requires 列表中指定依赖项,以便它们与包一起自动安装。 entry_points 部分定义了命令行界面 (CLI) 条目,以便用户可以从终端运行该工具。我们使用 find_packages() 来包含包中的所有子模块,并且 classifiers 部分提供元数据,例如项目使用的 Python 版本和许可证。最后,python_requires 字段确保包仅安装在兼容的 Python 版本上。这是我们的 blog_de_toolkit 项目的 setup.py 配置:
配置setup.py后,您可以构建分发包。首先安装必要的工具:
blog_de_toolkit/ │ ├── de_toolkit/ │ ├── __init__.py │ └── data_tools.py │ ├── tests/ │ ├── __init__.py │ └── test_data_tools.py │ ├── .gitignore ├── setup.py └── README.md
然后构建包:
pyenv install 3.12.2
此命令创建两个分发文件:
sdist:源存档(例如 .tar.gz)
bdist_wheel:构建的包(例如.whl)
这些文件将位于 dist/ 目录中。要测试该包,请使用以下命令在本地安装它:
brew update && brew upgrade pyenv
您还可以通过运行来测试 CLI 命令:
brew install readline xz
他应该打印数据库 URL、API 密钥以及来自 sample_data.csv.
的已清理数据如果您想公开分享该包,您可以将其上传到PyPI。首先,安装 Twine:
pyenv virtualenv 3.12.2 de_toolkit
然后上传包:
pyenv local de_toolkit
系统将提示您输入 PyPI 凭据。上传后,其他人可以直接从 PyPI 安装您的包:
pip install setuptools wheel twine pandas
随着您的项目的发展,更多的人通常会同时在同一个代码库上工作。如果没有适当的保护措施,错误、未经测试的代码或意外合并很容易潜入并造成混乱。为了保持事情顺利进行并保持高标准,保护主干就变得至关重要。在此步骤中,我们将了解如何设置分支保护规则,并分享一些对拉取请求进行顺利代码审查的技巧。
分支保护规则确保任何人都无法在未通过测试或代码审查的情况下直接推送到主分支。这可以防止未完成的功能、错误或不良代码潜入并破坏项目。它还通过要求拉取请求来促进团队合作,让其他人有机会提供反馈。另外,自动检查(如测试和 linter)可确保代码在合并之前是可靠的。
在 GitHub 上设置分支保护规则非常简单。前往存储库的设置,然后单击“代码和自动化”部分下的分支。查找分支保护规则并单击添加分支保护规则。在分支名称字段中输入 main,现在是时候调整一些设置了。
您可以设置分支保护规则来要求拉取请求审查,确保有人在合并之前检查代码。状态检查确保测试通过并且 linter 顺利运行,并使分支保持最新更改有助于避免冲突。如果需要,您可以限制谁可以推送到分支,甚至需要签名提交以提高安全性。一切设置完毕后,单击创建,就像这样 - 不再直接推送或跳过测试。
当您的拉取请求接受审核时,最好让审核者感到轻松。首先清楚地描述您的更改的作用以及为什么需要它们。使用有意义的提交消息来反映已更新的内容。如果更改较小且重点突出,则审核过程会变得更加顺利和更快。不要忘记礼貌地回复评论并跟进请求的更改 - 这表明您重视反馈并有助于保持积极的合作。
如果您是审查拉取请求的人,那么您的工作不仅仅是发现错误 - 还包括改进代码并支持您的队友。首先阅读拉取请求描述,以了解更改试图实现的目标。专注于提供建设性反馈——如果需要,建议替代方案,并解释为什么它们可能效果更好。通过简单的“重构不错?!”来认可优秀的工作还有助于创造积极的评论体验。密切关注测试,确保它们存在、相关且通过。如果有些事情不清楚,请提出问题而不是做出假设。归根结底,评论是关于团队合作的——合作使项目变得更好。
使用审核模板可以让每个人都专注于重要的事情,从而使流程更加顺利。以下是拉取请求审核模板的示例:
blog_de_toolkit/ │ ├── de_toolkit/ │ ├── __init__.py │ └── data_tools.py │ ├── tests/ │ ├── __init__.py │ └── test_data_tools.py │ ├── .gitignore ├── setup.py └── README.md
将这样的模板添加到您的贡献指南中或将其链接到存储库中,可以让审阅者轻松保持在正轨上。它还使评论之间的内容保持一致,帮助团队维护干净且有组织的代码库。
基于保护主分支的重要性,确保在合并或部署之前对每个代码更改进行正确的测试、审查和验证也至关重要。这就是持续集成(CI)和持续交付/部署(CD)发挥作用的地方。 CI/CD 自动化运行测试、执行代码检查和部署更改的过程,为开发人员提供快速反馈并减少错误进入生产的机会。
GitHub Actions 是直接集成到 GitHub 中的自动化工具。它使您能够创建响应存储库中的事件(例如推送或拉取请求)的工作流程。在 GitHub Actions 中,我们可以自动执行多项关键任务以维护健康的代码库。例如:
每当推送代码或创建拉取请求时运行测试,确保新的更改不会破坏任何内容。
检查代码风格和 linting 以执行一致的编码标准。
应用预提交挂钩来格式化代码并捕获尾随空格等小问题。
在所有检查通过后生成文档甚至部署代码。
让我们设置一个 GitHub Actions 工作流程,运行我们的单元测试,并在主分支上发生推送或拉取请求时应用预提交 linter(如黑色)。
首先,创建工作流程文件:
blog_de_toolkit/ │ ├── de_toolkit/ │ ├── __init__.py │ └── data_tools.py │ ├── tests/ │ ├── __init__.py │ └── test_data_tools.py │ ├── .gitignore ├── setup.py └── README.md
这是ci.yml的内容:
每当在主分支上推送代码或打开拉取请求时,此工作流程都会自动进行测试和检查。它确保在合并代码之前通过所有质量检查。 actions/checkout 操作将存储库克隆到运行器中,我们使用 actions/setup-python 为工作流程配置 Python 3.12。依赖项是使用 pip 从requirements.txt 安装的。之后,所有测试都使用 pytest 运行,预提交挂钩确保代码遵循格式和样式指南。如果任何测试或检查失败,工作流程就会停止,以防止损坏的代码被合并。
让我们测试一下。首先,从主分支创建一个新分支并进行一些更改。
就我而言,我更新了自述文件文件。提交您的更改并向主分支打开拉取请求。
现在您将看到需要进行审核,并且 GitHub Actions (GA) 正在运行所有检查。即使合并被分支保护规则阻止,我仍然可以“无需等待满足要求即可合并”,因为我的权限允许绕过保护。
您可以在 Actions 选项卡中跟踪 GitHub Actions 工作流程的结果。
以下是运行期间 pytest 步骤的示例:
手动跟踪项目的版本可能会变得混乱,尤其是随着项目的增长。这就是 语义版本控制 (SemVer) 的用武之地 - 它遵循 MAJOR.MINOR.PATCH 模式来传达每个版本中发生的更改。使用 python-semantic-release 自动进行版本控制使这变得更加容易。它会分析您的提交消息,根据更改类型、标记版本来升级版本,如果需要,甚至可以将您的包发布到 PyPI。这消除了版本管理中的猜测并确保了一致性。
为了实现无缝版本控制,您可以将 python-semantic-release 直接集成到 GitHub Actions 中。官方文档提供了每当您推送到主分支时自动进行版本更新和发布的工作流程。通过此设置,发布过程变得顺畅且无需干预,因此您可以专注于编写代码,而不必担心手动管理版本。
常见工作流程示例 — python-semantic-release
为了实现此目的,您的提交消息需要遵循传统的提交标准。每种类型的提交决定版本是否会提升 PATCH、MINOR 或 MAJOR 级别:
修复:触发 PATCH 版本更新(例如 1.0.0 → 1.0.1)。
壮举:触发次要版本更新(例如,1.0.0 → 1.1.0)。
重大更改:提交消息中的 或 ! 会触发主要版本更新(例如,1.0.0 → 2.0.0)。
通过遵循这些简单的约定,您将始终知道每个新版本会发生什么。
我们涵盖了从组织项目和管理机密到编写测试、自动化工作流程以及使用语义版本控制处理发布的所有内容。有了正确的工具和流程,构建可靠且可维护的项目就会变得更加顺利,甚至充满乐趣。
关键是保持一致,尽可能实现自动化,并不断改进。随着时间的推移,每一个小步骤都会产生很大的变化。现在轮到你了——去构建、实验并享受这个过程!尝试将这些步骤应用到您的下一个项目中 - 并随时在评论中分享您的经验!
以上是为初学者组织和维护 Python 代码库的步骤的详细内容。更多信息请关注PHP中文网其他相关文章!