Python 庫打包分發簡易指南


Python 庫打包分發(setup.py 編寫)簡易指南

Python 有非常豐富的第三方庫可以使用,很多開發者會向 pypi 上提交自己的 Python 包。要想向 pypi 包倉庫提交自己開發的包,首先要將自己的代碼打包,才能上傳分發。

distutils 簡介

distutils 是標准庫中負責建立 Python 第三方庫的安裝器,使用它能夠進行 Python 模塊的安裝和發布。distutils 對於簡單的分發很有用,但功能缺少。大部分Python用戶會使用更先進的setuptools模塊

setuptools 簡介

setuptools 是 distutils 增強版,不包括在標准庫中。其擴展了很多功能,能夠幫助開發者更好的創建和分發 Python 包。大部分 Python 用戶都會使用更先進的 setuptools 模塊。

Setuptools 有一個 fork 分支是 distribute。它們共享相同的命名空間,因此如果安裝了 distribute,import setuptools 時實際上將導入使用 distribute 創建的包。Distribute 已經合並回 setuptools。

還有一個大包分發工具是 distutils2,其試圖嘗試充分利用distutils,detuptools 和 distribute 並成為 Python 標准庫中的標准工具。但該計划並沒有達到預期的目的,且已經是一個廢棄的項目。

因此,setuptools 是一個優秀的,可靠的 Pthon 包安裝與分發工具。以下設計到包的安裝與分發均針對 setuptools,並不保證 distutils 可用。

包格式

Python 庫打包的格式包括 WheelEgg。Egg 格式是由 setuptools 在 2004 年引入,而 Wheel 格式是由 PEP427 在 2012 年定義。使用 WheelEgg 安裝都不需要重新構建和編譯,其在發布之前就應該完成測試和構建。

EggWheel 本質上都是一個 zip 格式包,Egg 文件使用 .egg 擴展名,Wheel 使用 .whl 擴展名。Wheel 的出現是為了替代 Egg,其現在被認為是 Python 的二進制包的標准格式。

以下是 Wheel 和 Egg 的主要區別:

  • Wheel 有一個官方的 PEP427 來定義,而 Egg 沒有 PEP 定義
  • Wheel 是一種分發格式,即打包格式。而 Egg 既是一種分發格式,也是一種運行時安裝的格式,並且是可以被直接 import
  • Wheel 文件不會包含 .pyc 文件
  • Wheel 使用和 PEP376 兼容的 .dist-info 目錄,而 Egg 使用 .egg-info 目錄
  • Wheel 有着更豐富的命名規則。
  • Wheel 是有版本的。每個 Wheel 文件都包含 wheel 規范的版本和打包的實現
  • Wheel 在內部被 sysconfig path type 管理,因此轉向其他格式也更容易

詳細描述可見:Wheel vs Egg

setup.py 文件

Python 庫打包分發的關鍵在於編寫 setup.py 文件。setup.py 文件編寫的規則是從 setuptools 或者 distuils 模塊導入 setup 函數,並傳入各類參數進行調用。

# coding:utf-8

from setuptools import setup
# or
# from distutils.core import setup  

setup(
        name='demo',     # 包名字
        version='1.0',   # 包版本
        description='This is a test of the setup',   # 簡單描述
        author='huoty',  # 作者
        author_email='sudohuoty@163.com',  # 作者郵箱
        url='https://www.konghy.com',      # 包的主頁
        packages=['demo'],                 # 包
)

參數概述

setup 函數常用的參數如下:

參數 說明
name 包名稱
version 包版本
author 程序的作者
author_email 程序的作者的郵箱地址
maintainer 維護者
maintainer_email 維護者的郵箱地址
url 程序的官網地址
license 程序的授權信息
description 程序的簡單描述
long_description 程序的詳細描述
platforms 程序適用的軟件平台列表
classifiers 程序的所屬分類列表
keywords 程序的關鍵字列表
packages 需要處理的包目錄(通常為包含 __init__.py 的文件夾)
py_modules 需要打包的 Python 單文件列表
download_url 程序的下載地址
cmdclass 添加自定義命令
package_data 指定包內需要包含的數據文件
include_package_data 自動包含包內所有受版本控制(cvs/svn/git)的數據文件
exclude_package_data 當 include_package_data 為 True 時該選項用於排除部分文件
data_files 打包時需要打包的數據文件,如圖片,配置文件等
ext_modules 指定擴展模塊
scripts 指定可執行腳本,安裝時腳本會被安裝到系統 PATH 路徑下
package_dir 指定哪些目錄下的文件被映射到哪個源碼包
requires 指定依賴的其他包
provides 指定可以為哪些模塊提供依賴
install_requires 安裝時需要安裝的依賴包
entry_points 動態發現服務和插件,下面詳細講
setup_requires 指定運行 setup.py 文件本身所依賴的包
dependency_links 指定依賴包的下載地址
extras_require 當前包的高級/額外特性需要依賴的分發包
zip_safe 不壓縮包,而是以目錄的形式安裝

更多參數可見:https://setuptools.readthedocs.io/en/latest/setuptools.html

find_packages

對於簡單工程來說,手動增加 packages 參數是容易。而對於復雜的工程來說,可能添加很多的包,這是手動添加就變得麻煩。Setuptools 模塊提供了一個 find_packages 函數,它默認在與 setup.py 文件同一目錄下搜索各個含有 __init__.py 的目錄做為要添加的包。

find_packages(where='.', exclude=(), include=('*',))

find_packages 函數的第一個參數用於指定在哪個目錄下搜索包,參數 exclude 用於指定排除哪些包,參數 include 指出要包含的包。

默認默認情況下 setup.py 文件只在其所在的目錄下搜索包。如果不用 find_packages,想要找到其他目錄下的包,也可以設置 package_dir 參數,其指定哪些目錄下的文件被映射到哪個源碼包,如: package_dir={'': 'src'} 表示 “root package” 中的模塊都在 src 目錄中。

包含數據文件

  • package_data:

該參數是一個從包名稱到 glob 模式列表的字典。如果數據文件包含在包的子目錄中,則 glob 可以包括子目錄名稱。其格式一般為 {'package_name': ['files']},比如:package_data={'mypkg': ['data/*.dat'],}

  • include_package_data:

該參數被設置為 True 時自動添加包中受版本控制的數據文件,可替代 package_data,同時,exclude_package_data 可以排除某些文件。注意當需要加入沒有被版本控制的文件時,還是仍然需要使用 package_data 參數才行。

  • data_files:

該參數通常用於包含不在包內的數據文件,即包的外部文件,如:配置文件,消息目錄,數據文件。其指定了一系列二元組,即(目的安裝目錄,源文件) ,表示哪些文件被安裝到哪些目錄中。如果目錄名是相對路徑,則相對於安裝前綴進行解釋。

  • manifest template:

manifest template 即編寫 MANIFEST.in 文件,文件內容就是需要包含在分發包中的文件。一個 MANIFEST.in 文件如下:

include *.txt
recursive-include examples *.txt *.py
prune examples/sample?/build

MANIFEST.in 文件的編寫規則可參考:https://docs.python.org/3.6/distutils/sourcedist.html

生成腳本

有兩個參數 scripts 參數或 console_scripts 可用於生成腳本。

entry_points 參數用來支持自動生成腳本,其值應該為是一個字典,從 entry_point 組名映射到一個表示 entry_point 的字符串或字符串列表,如:

setup(
    # other arguments here...
    entry_points={
        'console_scripts': [
            'foo=foo.entry:main',
            'bar=foo.entry:main',
        ],    
    }
)

scripts 參數是一個 list,安裝包時在該參數中列出的文件會被安裝到系統 PATH 路徑下。如:

scripts=['bin/foo.sh', 'bar.py']

用如下方法可以將腳本重命名,例如去掉腳本文件的擴展名(.py、.sh):

from setuptools.command.install_scripts import install_scripts

class InstallScripts(install_scripts):

    def run(self):
        setuptools.command.install_scripts.install_scripts.run(self)

        # Rename some script files
        for script in self.get_outputs():
            if basename.endswith(".py") or basename.endswith(".sh"):
                dest = script[:-3]
            else:
                continue
            print("moving %s to %s" % (script, dest))
            shutil.move(script, dest)

setup(
    # other arguments here...
    cmdclass={
        "install_scripts": InstallScripts
    }
)

其中,cmdclass 參數表示自定制命令,后文詳述。

ext_modules

ext_modules 參數用於構建 C 和 C++ 擴展擴展包。其是 Extension 實例的列表,每一個 Extension 實例描述了一個獨立的擴展模塊,擴展模塊可以設置擴展包名,頭文件、源文件、鏈接庫及其路徑、宏定義和編輯參數等。如:

setup(
    # other arguments here...
    ext_modules=[
        Extension('foo',
                  glob(path.join(here, 'src', '*.c')),
                  libraries = [ 'rt' ],
                  include_dirs=[numpy.get_include()])
    ]
)

詳細了解可參考:https://docs.python.org/3.6/distutils/setupscript.html#preprocessor-options

zip_safe

zip_safe 參數決定包是否作為一個 zip 壓縮后的 egg 文件安裝,還是作為一個以 .egg 結尾的目錄安裝。因為有些工具不支持 zip 壓縮文件,而且壓縮后的包也不方便調試,所以建議將其設為 False,即 zip_safe=False

自定義命令

Setup.py 文件有很多內置的的命令,可以使用 python setup.py --help-commands 查看。如果想要定制自己需要的命令,可以添加 cmdclass 參數,其值為一個 dict。實現自定義命名需要繼承 setuptools.Command 或者 distutils.core.Command 並重寫 run 方法。

from setuptools import setup, Command

class InstallCommand(Command):
    description = "Installs the foo."
    user_options = [
        ('foo=', None, 'Specify the foo to bar.'),
    ]
    def initialize_options(self):
        self.foo = None
    def finalize_options(self):
        assert self.foo in (None, 'myFoo', 'myFoo2'), 'Invalid foo!'
    def run(self):
        install_all_the_things()

setup(
    ...,
    cmdclass={
        'install': InstallCommand,
    }
)

依賴關系

如果包依賴其他的包,可以指定 install_requires 參數,其值為一個 list,如:

install_requires=[
    'requests>=1.0',
    'flask>=1.0'
]

指定該參數后,在安裝包時會自定從 pypi 倉庫中下載指定的依賴包安裝。

此外,還支持從指定鏈接下載依賴,即指定 dependency_links 參數,如:

dependency_links = [
    "http://packages.example.com/snapshots/foo-1.0.tar.gz",
    "http://example2.com/p/bar-1.0.tar.gz",
]

分類信息

classifiers 參數說明包的分類信息。所有支持的分類列表見:https://pypi.org/pypi?%3Aaction=list_classifiers

示例:

classifiers = [
    # 發展時期,常見的如下
    #   3 - Alpha
    #   4 - Beta
    #   5 - Production/Stable
    'Development Status :: 3 - Alpha',

    # 開發的目標用戶
    'Intended Audience :: Developers',

    # 屬於什么類型
    'Topic :: Software Development :: Build Tools',

    # 許可證信息
    'License :: OSI Approved :: MIT License',

    # 目標 Python 版本
    'Programming Language :: Python :: 2',
    'Programming Language :: Python :: 2.7',
    'Programming Language :: Python :: 3',
    'Programming Language :: Python :: 3.3',
    'Programming Language :: Python :: 3.4',
    'Programming Language :: Python :: 3.5',
]

setup.py 命令

setup.py 文件有很多內置命令可供使用,查看所有支持的命令:

python setup.py --help-commands

此處列舉一些常用命令:

  • build:

構建安裝時所需的所有內容

  • sdist:

構建源碼分發包,在 Windows 下為 zip 格式,Linux 下為 tag.gz 格式 。執行 sdist 命令時,默認會被打包的文件:

所有 py_modules 或 packages 指定的源碼文件
所有 ext_modules 指定的文件
所有 package_data 或 data_files 指定的文件
所有 scripts 指定的腳本文件
README、README.txt、setup.py 和 setup.cfg文件

該命令構建的包主要用於發布,例如上傳到 pypi 上。

  • bdist:

構建一個二進制的分發包。

  • bdist_egg:

構建一個 egg 分發包,經常用來替代基於 bdist 生成的模式

  • install:

安裝包到系統環境中。

  • develop:

以開發方式安裝包,該命名不會真正的安裝包,而是在系統環境中創建一個軟鏈接指向包實際所在目錄。這邊在修改包之后不用再安裝就能生效,便於調試。

  • register、upload:

用於包的上傳發布,后文詳述。

setup.cfg 文件

setup.cfg 文件用於提供 setup.py 的默認參數,詳細的書寫規則可參考:https://docs.python.org/3/distutils/configfile.html

版本命名

包版本的命名格式應為如下形式:

N.N[.N]+[{a|b|c|rc}N[.N]+][.postN][.devN]

從左向右做一個簡單的解釋:

  • "N.N": 必須的部分,兩個 "N" 分別代表了主版本和副版本號
  • "[.N]": 次要版本號,可以有零或多個
  • "{a|b|c|rc}": 階段代號,可選, a, b, c, rc 分別代表 alpha, beta, candidate 和 release candidate
  • "N[.N]": 階段版本號,如果提供,則至少有一位主版本號,后面可以加無限多位的副版本號
  • ".postN": 發行后更新版本號,可選
  • ".devN": 開發期間的發行版本號,可選

easy_install 與 pip

easy_insall 是 setuptool 包提供的第三方包安裝工具,而 pip 是 Python 中一個功能完備的包管理工具,是 easy_install 的改進版,提供更好的提示信息,刪除包等功能。

pip 相對於 easy_install 進行了以下幾個方面的改進:

  • 所有的包是在安裝之前就下載了,所以不可能出現只安裝了一部分的情況
  • 在終端上的輸出更加友好
  • 對於動作的原因進行持續的跟蹤。例如,如果一個包正在安裝,那么 pip 就會跟蹤為什么這個包會被安裝
  • 錯誤信息會非常有用
  • 代碼簡潔精悍可以很好的編程
  • 不必作為 egg 存檔,能扁平化安裝(仍然保存 egg 元數據)
  • 原生的支持其他版本控制系統(Git, Mercurial and Bazaar)
  • 加入卸載包功能
  • 可以簡單的定義修改一系列的安裝依賴,還可以可靠的賦值一系列依賴包

發布包

PyPI(Python Package Index) 是 Python 官方維護的第三方包倉庫,用於統一存儲和管理開發者發布的 Python 包。

如果要發布自己的包,需要先到 pypi 上注冊賬號。然后創建 ~/.pypirc 文件,此文件中配置 PyPI 訪問地址和賬號。如的.pypirc文件內容請根據自己的賬號來修改。

典型的 .pypirc 文件

[distutils]
index-servers = pypi

[pypi]
username:xxx
password:xxx

接着注冊項目:

python setup.py register

該命令在 PyPi 上注冊項目信息,成功注冊之后,可以在 PyPi 上看到項目信息。最后構建源碼包發布即可:

python setup.py sdist upload

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM