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
...