PYTHONPATH变量官方Python包装指南
Python中的模块是一个包含Python代码的单个文件(带有.py扩展名)。它通常由其他Python代码可以使用的类,功能和变量组成。模块用于将代码整理成逻辑单元,并促进代码可重复使用性。
例如,考虑一个名为math_operations.py的模块,该模块包含可以执行数学操作(例如加法,减法,乘法等)的功能。您可以在其他Python脚本中导入并使用这些功能。
# math_operations.py
def add ( x , y ):
return x + y
def subtract ( x , y ):
return x - yPython中的包装是将组合在一起的模块集合。包装通常由包含__init__.py文件(可以为空)和一个或多个Python模块的目录表示。 __init__.py文件向Python表示该目录应视为包裹。
例如,考虑一个名为my_package的软件包:
my_package/
├── __init__.py
├── module1.py
└── module2.py
在这里, my_package是一个包含module1.py和module2.py的软件包,可以使用dot note( my_package.module1 , my_package.module2 )导入该软件包。
Python中的子包装是嵌套在另一个软件包中的软件包。这意味着包装可以包含其他软件包以及模块。子包是通过组织目录和添加__init__.py文件适当定义软件包结构来创建的。
例如:
my_parent_package/
├── __init__.py
└── my_sub_package/
├── __init__.py
├── module3.py
└── module4.py
在此结构中, my_sub_package是my_parent_package的子包装,它可以包含其自己的模块( module3.py , module4.py )。可以使用点符号( my_parent_package.my_sub_package.module3 )导入子包装。
Python中的分发软件包(或简单的分发)是指可用于安装的Python代码和资源的包装集合。它通常包括用于特定目的所需的模块,软件包,数据文件,配置文件以及其他资源(例如,库或应用程序)。
分发软件包通常是使用Python软件包管理器(例如pip进行分发和安装的,并且可以上传到PAXPES库(例如PYPI(Python软件包索引)等软件包存储库,以便于其他开发人员的分发和安装。
例如,流行的配电包包括使用pip安装的numpy , fast-api , pandas等,并提供可用于Python项目中的功能。
sdist格式setup.pypython setup.py build sdist以构建源分布pip install ./dist/<package_name>.tar.gz安装软件包pip list查看是否安装了软件包。如果包装中进行更改,请使用pip install .它将即时构建和安装最新的软件包。
或者只是简单地使用可编辑,以便您不必总是重建包装EVRERYTIME的新更改:
pip install --editable . sdist是源分发的缩写,一个.tar文件包含我们的代码称为“ SDIST”。这意味着分发软件包仅包含我们源代码的子集。
“源分布”本质上是包含我们源代码的Zipped文件夹。
wheel格式来源分布( sdist ) :
*.py ),配置文件和其他与项目相关的资产。python setup.py sdist创建。车轮( bdist_wheel ) :
sdist相比,更先进的分布形式。.whl扩展名表示一个拉链文件。python setup.py bdist_wheel创建。 更快的安装:
*.whl )通常比从源分布( *.tar.gz )安装的速度快。用户的易用性:
sdist和bdist_wheelsdist和bdist_wheel ,以容纳不同的用例和平台。python setup.py sdist bdist_wheel汇编要求:
不幸的是,如果您需要编译步骤,则所有操作系统的构建车轮都会变得困难,因此一些OSS维护者仅为单个操作系统构建车轮。
pip都会在下载SDIST后立即在计算机上执行setup.py (或同等文件)。构建轮子可能需要编译代码
gcc ,例如源代码在C中,但其他语言需要自己的编译器。用户必须将它们安装在机器或pip install ...仅会失败。当您安装numpy , pandas , scipy , pytorch , tensorflow等时,这可能会发生这种情况。 setup.py可能包含任意代码。这是非常不安全的。 setup.py可能包含恶意代码。
依赖管理:
gcc等),以成功安装需要编译的软件包。安全问题:
setup.py进行软件包安装可能是不安全的,因为它执行任意代码。wheel车轮文件名分为连字符分开的部分:
{dist}-{version}(-{build})?-{python}-{abi}-{platform}.whl {brackets}中的每个部分都是标签,也是轮子的组件,它具有有关车轮所包含的内容以及车轮将在何处或将不起作用的含义。
例如: dist/packaging-0.0.0-py3-none-any.whl
packaging是包装名称0.0.0是verison编号py3表示它是为python3构建的abi标签。 ABI代表应用程序二进制界面。any代表此软件包都可以在任何平台上使用。其他示例:
cryptography-2.9.2-cp35-abi3-macosx_10_9_x86_64.whlchardet-3.0.4-py2.py3-none-any.whlPyYAML-5.3.1-cp38-cp38-win_amd64.whlnumpy-1.18.4-cp38-cp38-win32.whlscipy-1.4.1-cp36-cp36m-macosx_10_6_intel.whl原因是,车轮文件包含预先符合的二进制代码,该代码有助于快速安装软件包。
包装维护:
sdist和bdist_wheel分布,以最大程度地提高用户的兼容性和易用性。考虑用户体验:
build CLI工具和pyproject.toml“构建依赖关系”是系统上必须安装的任何内容,以便将分配包构建到SDIST或车轮中。
例如,我们需要pip install wheel以运行python setup.py bdist_wheel SO wheel是构建车轮的构建依赖性。
setup.py文件可能会变得复杂。
您可能需要pip install ...外部库并将其导入setup.py文件以适应复杂的构建过程。
演讲显示了pytorch和airflow作为带有复杂setup.py的包装的示例。
您需要以某种方式记录setup.py之外的构建依赖关系。
如果它们是在setup.py文件中记录的……您将无法执行setup.py文件以读取已记录的依赖项(例如,如果它们是在文件中某个地方的list中指定的)。
这是原始问题pyproject.toml是为了解决的。
# pyproject.toml
[ build-system ]
# Minimum requirements for the build system to execute.
requires = [ " setuptools>=62.0.0 " , " wheel " ] pyproject.toml位于文件树中的setup.py
build CLI工具( pip install build )是Python包装局(PYPA)的一个特殊项目
pyproject.toml中读取[build-system]表pip install build
# both setup.py and pypproject.toml should be together, ideally in the root directory
# python -m build --sdist --wheel path/to/dir/with/setup.py/and/pyproject.toml
python -m build --sdist --wheel . setup.py移动到setup.cfg配置文件从
# setup.py
from pathlib import Path
from setuptools import find_packages , setup
import wheel
# Function to read the contents of README.md
def read_file ( filename : str ) -> str :
filepath = Path ( __file__ ). resolve (). parent / filename
with open ( filepath , encoding = "utf-8" ) as file :
return file . read ()
setup (
name = "packaging-demo" ,
version = "0.0.0" ,
packages = find_packages (),
# package meta-data
author = "Amit Vikram Raj" ,
author_email = "[email protected]" ,
description = "Demo for Python Packaging" ,
license = "MIT" ,
# Set the long description from README.md
long_description = read_file ( "README.md" ),
long_description_content_type = "text/markdown" ,
# install requires: libraries that are needed for the package to work
install_requires = [
"numpy" , # our package depends on numpy
],
# setup requires: the libraries that are needed to setup/build
# the package distribution
# setup_requires=[
# "wheel", # to build the binary distribution we need wheel package
# ],
)到
# setup.py
from setuptools import setup
# Now setup.py takes it's configurations from setup.cfg file
setup () # setup.cfg
[metadata]
name = packaging-demo
version = attr: packaging_demo.VERSION
author = Amit Vikram Raj
author_email = [email protected]
description = Demo for Python Packaging
long_description = file: README.md
keywords = one, two
license = MIT
classifiers =
Framework :: Django
Programming Language :: Python :: 3
[options]
zip_safe = False
include_package_data = True
# same as find_packages() in setup()
packages = find:
python_requires = >=3.8
install_requires =
numpy
importlib-metadata ; python_version<"3.10"另外,附加设置将传递给
pyproject.toml文件。在这里,我们指定的build-system类似于setup.py中的setup_requires
# pyproject.toml
[ build-system ]
# Minimum requirements for the build system to execute
requires = [ " setuptools " , " wheel " , " numpy<1.24.3 " ]
# Adding ruff.toml to pyproject.toml
[ tool . ruff ]
line-length = 99
[ tool . ruff . lint ]
# 1. Enable flake8-bugbear (`B`) rules, in addition to the defaults.
select = [ " E " , " F " , " B " , " ERA " ]
# 2. Avoid enforcing line-length violations (`E501`)
ignore = [ " E501 " ]
# 3. Avoid trying to fix flake8-bugbear (`B`) violations.
unfixable = [ " B " ]
# 4. Ignore `E402` (import violations) in all `__init__.py` files, and in select subdirectories.
[ tool . ruff . lint . per-file-ignores ]
"__init__.py" = [ " E402 " ]
"**/{tests,docs,tools}/*" = [ " E402 " ]
# copying isort configurations from .isort.cfg to pyproject.toml
[ tool . isort ]
profile = " black "
multi_line_output = " VERTICAL_HANGING_INDENT "
force_grid_wrap = 2
line_length = 99
# copying balck config from .black.toml to pyproject.toml
[ tool . black ]
line-length = 99
exclude = " .venv "
# copying flake8 config from .flake8 to pyproject.toml
[ tool . flake8 ]
docstring-convention = " all "
extend-ignore = [ " D107 " , " D212 " , " E501 " , " W503 " , " W605 " , " D203 " , " D100 " ,
" E305 " , " E701 " , " DAR101 " , " DAR201 " ]
exclude = [ " .venv " ]
max-line-length = 99
# radon
radon-max-cc = 10
# copying pylint config from .pylintrc to pyproject.toml
[ tool . pylint . "messages control" ]
disable = [
" line-too-long " ,
" trailing-whitespace " ,
" missing-function-docstring " ,
" consider-using-f-string " ,
" import-error " ,
" too-few-public-methods " ,
" redefined-outer-name " ,
]我们一直在将setup.py为荣耀的配置文件,而不是真正利用它是通过向其添加逻辑的python文件的事实。
这更常见。另外,从将python用于配置文件的一般转变,因为这样做为使用配置文件增加了复杂性(例如必须安装库才能执行配置文件)。
setup.cfg是一个供setup.py伴侣文件,允许我们在静态文本文件中定义我们的软件包配置 - 特别是INI格式文件。
我们不直接传递的任何值,因为setup.cfg文件中的setup()invocation都会查找setup()的参数,该设置()旨在使用该文件,该文件旨在在文件树中与setup.py相邻。
现在,我们正在积累很多文件!
setup.pysetup.cfgpyproject.tomlREADME.md.pylintrc , .flake8 , .blackrc , ruff.toml , .mypy , pre-commit-config.yaml ,etctCHANGELOG或CHANGELOG.mdVERSION或version.txt事实证明,几乎所有这些文件都可以用pyproject.toml替换。 [tool.black]所有pyproject.toml覆盖 /代码质量工具都支持解析一个称为[tool.<name>]
每个工具的文档都应告诉您如何完成此操作。
上面显示的是pyproject.toml ,带有用于我们在课程中使用的许多伸长工具的配置。
也可以更换设置
setup.cfg和setup.py吗?
setup.cfg到pyproject.toml来自
setup.cfg
# setup.cfg
[metadata]
name = packaging-demo
version = attr: packaging_demo.VERSION
author = Amit Vikram Raj
author_email = [email protected]
description = Demo for Python Packaging
long_description = file: README.md
keywords = one, two
license = MIT
classifiers =
Programming Language :: Python :: 3
[options]
zip_safe = False
include_package_data = True
# same as find_packages() in setup()
packages = find:
python_requires = >=3.8
install_requires =
numpy
importlib-metadata ; python_version<"3.10"到
# pyproject.toml
[ build-system ]
# Minimum requirements for the build system to execute
requires = [ " setuptools>=61.0.0 " , " wheel " ]
# Adding these from setup.cfg in pyproject.toml file
[ project ]
name = " packaging-demo "
authors = [{ name = " Amit Vikram Raj " , email = " [email protected] " }]
description = " Demo for Python Packaging "
readme = " README.md "
requires-python = " >=3.8 "
keywords = [ " one " , " two " ]
license = { text = " MIT " }
classifiers = [ " Programming Language :: Python :: 3 " ]
dependencies = [ " numpy " , ' importlib-metadata; python_version<"3.10" ' ]
dynamic = [ " version " ]
# version = "0.0.3"
# https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html#dynamic-metadata
[ tool . setuptools . dynamic ]
# every while making changes in package, you can change the verison in one of these files
# version = {attr = "packaging_demo.VERSION"} # version read by 'packaging_demo/__init__.py' file
version = { file = [ " version.txt " ]} # version read by 'version.txt' file in root folder
python -m build --sdist --wheel .- 运行完美,我们摆脱了另一个配置文件(setup.cfg)
build-backend替换setup.py PEP 517向pyproject.toml添加了一个build-backend依据:
[ build-system ]
# Defined by PEP 518:
requires = [ " flit " ]
# Defined by this PEP:
build-backend = " flit.api:main " # The above toml config is equivalent to
import flit . api
backend = flit . api . main build-backend定义了一个入门点(在这种情况下,在这种情况下为可执行的Python模块),该build CLI实际上用来完成解析pyproject.toml并构建轮子和SDIST的工作。
这意味着您可以通过编写一个程序来实现自己的构建后端,并且可以通过将软件包添加到requires = [...]并在build-backend = ...中指定入口点来使用它。
如果您没有在pyproject.toml中指定build-backend ,则假定setuptools,并且包装将完全良好。
setup.py python -m build --sdist --wheel .它可以完美地没有它运行,因为build-system的默认值设置为build-backend = "setuptools.build_meta"在build我们的软件包的构建CLI中。但是您仍然可以明确声明setuptools ,因为这样
# pyproject.toml
...
[ build-system ]
requires = [ " setuptools>=61.0.0 " , " wheel " ]
build-backend = " setuptools.build_meta "
...每个构建后端通常都使用其自己的配置选项扩展了pyproject.toml文件。例如,
# pyproject.toml
...
[ tool . setuptools . package-data ]
package_demo = [ " *.json " ]
[ tool . setuptools . dynamic ]
version = { file = " version.txt " }
long_description = { file = " README.md " }
...如果您选择在项目中使用setuptools ,则可以将这些部分添加到pyproject.toml 。您可以在setuptools文档中阅读有关此信息的更多信息
由于您的Python代码依赖于这些非Python文件,因此在包装中包含非Python文件(例如数据文件或二进制文件)通常是有益的。
然后我们看到,如果要包含这些文件,我们需要将这些文件最终放在包装文件夹中,因为安装后的包装文件夹最终在用户虚拟环境内部。
我们还看到,默认情况下,所有非Python文件都不会将其纳入最终的虚拟环境文件夹。也就是说,在构建过程中,实际上并没有将其放入我们的包装文件夹中。
那么,我们如何确保这些文件最终以包装的车轮/区域构建?例如,在这里,我们用states_info.py文件在包中使用的cities.json文件进行演示。
官方
setuptools文档以获取数据支持
# pyprject.toml
[ tool . setuptools ]
# ...
# By default, include-package-data is true in pyproject.toml, so you do
# NOT have to specify this line.
include-package-data = true因此,默认情况下,Setuptools的include-package-data值设置为true ,如官方文档所示,但是我们需要创建一个额外的文件MANIFEST.in ,并指定我们希望在Root Dir上存在的软件包中批准的数据。
IMP:它是将包装目录中的所有文件夹导入
__init__.py文件,灌输了我们要包含的数据目录,因为find_packages()setuptools确实不会陷入__init__.py文件中的fo;
# MANIFEST.in
include packaging_demo/*.json
include packaging_demo/my_folder/*.json
OR
Recursive include all json files in the package directory
recursive-include packaging_demo/ *.json
关于配置清单的文档
MANIFEST.in从setuptools文档中,我们可以在我们的pyproject.toml文件中添加它:
# this is by default true so no need to explicitly add it
# but as mentioned in the docs, it is false for other methods like setup.py or setup.cfg
[ tool . setuptools ]
include-package-data = true
# add the data here, it's finding the files recursively
[ tool . setuptools . package-data ]
package_demo = [ " *.json " ]build-backend setuptools以外除了setuptools我们可以使用这些构建后端系统。要注意的要点是,当使用其他系统时, pyproject.toml cofuration应遵循其站立。
孵化
[ build-system ]
requires = [ " hatchling " ]
build-backend = " hatchling.build "诗
[ build-system ]
requires = [ " poetry-core>=1.0.0 " ]
build-backend = " poetry.core.masonry.api " 记录我们的依赖性及其依赖性的确切版本,依此类推。
建议与我们的软件包相关的依赖次数尽可能少,因为它可能导致依赖性地狱,或与讲座中解释的其他软件包相冲突。
依赖树越复杂,与其他库的未来版本发生冲突的机会越高。
ERIC的依赖图分析
保留依赖性和Python版本的固定版本可用于故障排除目的:
pip freeze > requirements.txt pip install pipdeptree graphviz
sudo apt-get install graphviz
# generate the dependency graph
pipdeptree -p packaging-demo --graph-output png > dependency-graph.png[ project . optional-dependencies ]
dev = [ " ruff " , " mypy " , " black " ] # installing our package with optional dependencies
pip install ' .[dev] ' [ project . optional-dependencies ]
# for developement
dev = [ " ruff " , " mypy " , " black " ]
# plugin based architecture
colors = [ " rich " ] # plugin based installation
pip install ' .[colors] '
# here we demo with rich library, if user wants the output to be
# colorized then they can install our package like this.
# we can add multiple optional dependencies like:
pip install ' .[colors, dev] ' [ project . optional-dependencies ]
# for developement
dev = [ " ruff " , " mypy " , " black " ]
# plugin based architecture
colors = [ " rich " ]
# install all dependencies
all = [ " packaging-demo[dev, colors] " ] # Installing all dependencies all at once
pip install ' .[all] ' 我们可以使用SNYK检查我们将用于包裹的依赖项是否存在稳定,支持,如果有任何安全问题等,然后决定在项目中使用它的决定。
正如官方指南所述,要将我们的包装置于PYPI [Python包装索引],我们使用twine CLI工具。
pip install twine
twine upload --help生成用于PYPI测试或PYPI产品的API令牌
构建您的Python软件包: python -m build --sdist --wheel "${PACKAGE_DIR}" ,我们在这里构建SDIST和WHEEL,并建议使用。
运行麻线工具: twine upload --repository testpypi ./dist/*
CMake和Makefile
sudo apt-get install make任务文件
justfile
pyinvoke
开发
$ rightarrow $ 质量检查/分期$ rightarrow $ 产品



# .github/workflows/publish.yaml
name : Build, Test, and Publish
# triggers: whenever there is new changes pulled/pushed on this
# repo under given conditions, run the below jobs
on :
pull_request :
types : [opened, synchronize]
push :
branches :
- main
# Manually trigger a workflow
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch
workflow_dispatch :
jobs :
build-test-and-publish :
runs-on : ubuntu-latest
steps :
# github actions checksout, clones our repo, and checks out the branch we're working in
- uses : actions/checkout@v3
with :
# Number of commits to fetch. 0 indicates all history for all branches and tags
# fetching all tags so to aviod duplicate version tagging in 'Tag with the Release Version'
fetch-depth : 0
- name : Set up Python 3.8
uses : actions/setup-python@v3
with :
python-version : 3.8
# tagging the release version to avoid duplicate releases
- name : Tag with the Release Version
run : |
git tag $(cat version.txt)
- name : Install Python Dependencies
run : |
/bin/bash -x run.sh install
- name : Lint, Format, and Other Static Code Quality Check
run : |
/bin/bash -x run.sh lint:ci
- name : Build Python Package
run : |
/bin/bash -x run.sh build
- name : Publish to Test PyPI
# setting -x in below publish:test will not leak any secrets as they are masked in github
if : ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
run : |
/bin/bash -x run.sh publish:test
env :
TEST_PYPI_TOKEN : ${{ secrets.TEST_PYPI_TOKEN }}
- name : Publish to Prod PyPI
if : ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
run : |
/bin/bash -x run.sh publish:prod
env :
PROD_PYPI_TOKEN : ${{ secrets.PROD_PYPI_TOKEN }}
- name : Push Tags
if : ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
run : |
git push origin --tags锁定要求
依赖缓存
每当GitHub操作在GitHub CI中执行时,每次在新鲜容器上运行时。因此,每次我们都必须一次又一次地从PIP下载和重新安装依赖。这不是很好的效果,并减慢了我们的工作流程。
因此,当工作流程首先运行时,我们希望安装所有依赖项,并每次运行新的worflow时使用它。
GitHub操作通过缓存依赖项来提供此功能,它存储已安装的依赖项( ~/.cache/pip ),并每次运行新的工作流程时下载。文档
- uses : actions/cache@v3
with :
path : ~/.cache/pip
key : ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys : |
${{ runner.os }}-pip-setup-python github动作 steps :
- uses : actions/checkout@v4
- uses : actions/setup-python@v5
with :
python-version : ' 3.9 '
cache : ' pip ' # caching pip dependencies
- run : pip install -r requirements.txt并行化
# See .github/workflows/publish.yaml
jobs :
check-verison-txt :
...
lint-format-and-static-code-checks :
....
build-wheel-and-sdist :
...
publish :
needs :
- check-verison-txt
- lint-format-and-static-code-checks
- build-wheel-and-sdist
...