Python打包系統(tǒng)簡單入門

最近把pyenv、pipenv這種都研究了一下,然后我發(fā)現(xiàn)一個嚴(yán)重的問題:就是我雖然看了半天這些工具,但是我對Python自己的打包系統(tǒng)卻完全沒有了解。所以這篇文章就來研究一下Python自帶的打包系統(tǒng)。

pip

先來詳細(xì)介紹一下pip的用法,平時基本上我們用pip的時候也就是一個pip install。其實pip也有很多特性,在此先介紹一下常用的一些特性。此部分參考了pip文檔,想了解更多的話可以看原文。

安裝

最常用的命令就是安裝了,除此以外還可以指定版本號:

$ pip install SomePackage            # 不指定版本號,安裝最新版
$ pip install SomePackage==1.0.4     # 指定版本號
$ pip install 'SomePackage>=1.0.4'     # 指定最小版本號

$ pip install -r requirements.txt # 從需求文件安裝
$ pip install -e . # 從本地項目setup.py安裝

使用代理服務(wù)器

當(dāng)從官方的PyPI源安裝比較慢的時候,可以考慮使用代理服務(wù)器,指定代理服務(wù)器的方法有三種:

  • 使用--proxy參數(shù)在命令行指定,代理格式為[user:passwd@]proxy.server:port。
  • 在配置文件中指定。
  • 設(shè)置http_proxy, https_proxyno_proxy環(huán)境變量。

使用需求文件(requirements.txt)

在需要很多pip包的項目中,用pip一個個安裝包不是一個好辦法,這時候可以考慮使用需求文件。

如果要生成需求文件,用下面的命令。這會將當(dāng)前Python環(huán)境中的所有包的當(dāng)前版本狀態(tài)保存下來,將來安裝的時候會精確還原到凍結(jié)的那個狀態(tài)。

pip freeze > requirements.txt

要從需求文件中安裝,則是用下面的命令:

pip install -r requirements.txt

官方文檔還給出了一個帶注釋的實例需求文件:

#
####### example-requirements.txt #######
#
###### 沒有版本標(biāo)識符的包,會安裝最新版 ######
nose
nose-cov
beautifulsoup4
#
###### 帶版本標(biāo)識符的包 ######
#  版本標(biāo)識符的資料 https://www.python.org/dev/peps/pep-0440/#version-specifiers
docopt == 0.6.1             # Version Matching. Must be version 0.6.1
keyring >= 4.1.1            # Minimum version 4.1.1
coverage != 3.5             # Version Exclusion. Anything except version 3.5
Mopidy-Dirble ~= 1.1        # Compatible release. Same as >= 1.1, == 1.*
#
###### 還可以指定其他的需求文件 ######
-r other-requirements.txt
#
#
###### 還可以指定本地貨網(wǎng)絡(luò)上的特定包 ######
./downloads/numpy-1.9.2-cp34-none-win32.whl
http://wxpython.org/Phoenix/snapshot-builds/wxPython_Phoenix-3.0.3.dev1820+49a8884-cp34-none-win_amd64.whl
#
###### Additional Requirements without Version Specifiers ######
#   和第一部分一樣,這里這些部分沒有順序需求,可以隨意改變位置
rejected
green
#

版本標(biāo)識符用來指定包的版本,有以下幾個例子:

SomeProject
SomeProject == 1.3
SomeProject >=1.2,<.2.0
SomeProject[foo, bar]
SomeProject~=1.4.2

從6.0版本開始,pip也支持環(huán)境標(biāo)記(也就是分號后面跟Python版本或者系統(tǒng)類型):

SomeProject ==5.4 ; python_version < '2.7'
SomeProject; sys_platform == 'win32'

卸載

卸載某個包使用下面的命令:

$ pip uninstall SomePackage

列出包

要列出所有已安裝的包:

$ pip list
docutils (0.9.1)
Jinja2 (2.6)
Pygments (1.5)
Sphinx (1.1.2)

要列出過時的包:

$ pip list --outdated
docutils (Current: 0.9.1 Latest: 0.10)
Sphinx (Current: 1.1.2 Latest: 1.1.3)

要列出某個已安裝的包的詳細(xì)信息:

$ pip show sphinx
---
Name: Sphinx
Version: 1.1.3
Location: /my/env/lib/pythonx.x/site-packages
Requires: Pygments, Jinja2, docutils

搜索

要搜索一個包,用下面的命令,搜索結(jié)果可能有很多:

$ pip search "query"

更新

要更新一個包,使用-U或者--upgrade參數(shù):

pip install -U <pkg>

如果想更新所有的包,很遺憾,pip并沒有提供該功能,我在StackOverFlow上找到一個看起來比較簡單的解決辦法,就是在Python解釋器中執(zhí)行下面的代碼:

import pkg_resources
from subprocess import call

packages = [dist.project_name for dist in pkg_resources.working_set]
call("pip install --upgrade " + ' '.join(packages), shell=True)

以上就是pip的一些簡單用法,詳情可參考官方文檔。

打包項目

下面就進(jìn)入本文的正題,Python的打包系統(tǒng)上?;旧衔覀儾恍枰耆私獯虬到y(tǒng),只要學(xué)會簡單的幾個點就可以打包自己的類庫了。打包需要distutils、setuptools、wheel等類庫,不過基本上我們只需要寫好其中最重要的setup.py,就可以完成打包工作了。distutils是官方的類庫,在當(dāng)年有很廣泛的使用,不過到了現(xiàn)在很難用。distutuils類庫的核心就是setup函數(shù),我們需要將項目的各種信息作為參數(shù)傳遞給setup函數(shù),然后就可以用相關(guān)命令創(chuàng)建項目分發(fā)包了。關(guān)于distutils的用法,可以參考官方文檔。

當(dāng)然現(xiàn)在項目基本都不用distutils了,有更好用的第三方替代品,那就是setuptools,它可以算作是distutils的加強(qiáng)版,功能更加強(qiáng)大、使用更加簡單,這就是這里要介紹的。其實從文檔就可以看出來,distutils畢竟時間比較早,有些接口設(shè)計的不太合理甚至有些反人類,setuptools的文檔就簡單多了。

準(zhǔn)備項目

為了做演示,首先需要準(zhǔn)備一個項目,一個項目應(yīng)該包括README和LICENSE等文件,README文件是Markdown格式的文本文件,用于描述項目自身;LICENSE文件是授權(quán)文件,列出項目使用者應(yīng)該遵循的各種條款。下圖是我的項目結(jié)構(gòu)。

項目結(jié)構(gòu)

此外還可能存在幾個文件:

  • setup.cfg。對應(yīng)的配置文件,一般情況下可以不要。
  • MANIFEST.in。清單文件,當(dāng)項目中需要一些沒辦法自動包括到源代碼分發(fā)包的文件時,可能需要用到它。

具體文件內(nèi)容就不列出了。需要注意my_package/__init__.py文件中應(yīng)該有如下一行標(biāo)識包名:

name = 'yitian_first_package'

編寫setup.py文件

用setuptools來編寫setup.py文件是一件非常簡單的事情,而且有很多例子可供參考,我挑選了Kenneth Reitz(requests、pipenv等類庫的作者)寫的例子,做了一些修改并翻譯了一些注釋:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# 注意 如果要使用上傳功能,需要安裝twine包:
#   $ pip install twine

import io
import os
import sys
from shutil import rmtree

from setuptools import find_packages, setup, Command

# 包的元信息
NAME = 'yitian_first_package'
DESCRIPTION = '項目的簡短描述,不超過200字符'
URL = 'https://github.com/techstay/python-study'
EMAIL = 'lovery521@gmail.com'
AUTHOR = '易天'
REQUIRES_PYTHON = '>=3.6.0'
VERSION = '0.1.0'
KEYWORDS = 'sample setuptools development'

# 項目依賴,也就是必須安裝的包
REQUIRED = [
    'requests-html'
]

# 項目的可選依賴,可以不用安裝
EXTRAS = {
    # 'fancy feature': ['django'],
}

# 剩下部分不用怎么管 :)
# ------------------------------------------------
# 除了授權(quán)和授權(quán)文件標(biāo)識符!
# 如果你改了License, 記得也相應(yīng)修改Trove Classifier!

here = os.path.abspath(os.path.dirname(__file__))

# 導(dǎo)入README文件作為項目長描述.
# 注意 這需要README文件存在!
try:
    with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
        long_description = '\n' + f.read()
except FileNotFoundError:
    long_description = DESCRIPTION

# 當(dāng)前面沒指定版本號的時候,將包的 __version__.py 模塊加載進(jìn)來
about = {}
if not VERSION:
    with open(os.path.join(here, NAME, '__version__.py')) as f:
        exec(f.read(), about)
else:
    about['__version__'] = VERSION


class UploadCommand(Command):
    """上傳功能支持"""

    description = 'Build and publish the package.'
    user_options = []

    @staticmethod
    def status(s):
        """Prints things in bold."""
        print('\033[1m{0}\033[0m'.format(s))

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        try:
            self.status('Removing previous builds…')
            rmtree(os.path.join(here, 'dist'))
        except OSError:
            pass

        self.status('Building Source and Wheel (universal) distribution…')
        os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))

        self.status('Uploading the package to PyPI via Twine…')
        os.system('twine upload dist/*')

        self.status('Pushing git tags…')
        os.system('git tag v{0}'.format(about['__version__']))
        os.system('git push --tags')

        sys.exit()


# 神奇的操作,一個函數(shù)完事
setup(
    name=NAME,
    version=about['__version__'],
    description=DESCRIPTION,
    long_description=long_description,
    long_description_content_type='text/markdown',
    author=AUTHOR,
    author_email=EMAIL,
    python_requires=REQUIRES_PYTHON,
    url=URL,
    keywords=KEYWORDS,
    # 項目中要包括和要排除的文件,setuptools可以自動搜索__init__.py文件來找到包
    packages=find_packages(exclude=('tests',)),
    # 如果項目中包含任何不在包中的單文件模塊,需要添加py_modules讓setuptools能找到它們:
    # py_modules=['yitian_first_package'],

    # entry_points={
    #     'console_scripts': ['mycli=mymodule:cli'],
    # },
    install_requires=REQUIRED,
    extras_require=EXTRAS,
    # 老舊的distutils需要手動添加項目中需要的非代碼文件,setuptools可以用下面參數(shù)自動添加(僅限包目錄下)
    include_package_data=True,
    # 如果是包的子目錄下,則需要手動添加
    package_data={
        'yitian_first_package': ['static/*.html']
    },
    license='MIT',
    classifiers=[
        # Trove classifiers
        # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: Implementation :: CPython',
        'Programming Language :: Python :: Implementation :: PyPy'
    ],
    # $ setup.py publish support.
    cmdclass={
        'upload': UploadCommand,
    },
)

下面再講一些在注釋里沒法詳細(xì)解釋的東西,官方文檔的內(nèi)容更豐富,有需要的可以查看。示例文件中其實還有幾個setup參數(shù)沒寫全,這里再補(bǔ)充一下。

project_urls

project_urls參數(shù)可以列出一些相關(guān)項目的URL。

project_urls={
    'Documentation': 'https://packaging.python.org/tutorials/distributing-packages/',
    'Funding': 'https://donate.pypi.org',
    'Say Thanks!': 'http://saythanks.io/to/example',
    'Source': 'https://github.com/pypa/sampleproject/',
    'Tracker': 'https://github.com/pypa/sampleproject/issues',
},

python_requires參數(shù)格式就是pip中指定包版本的標(biāo)識符,,指定我們項目支持的Python版本,這里再補(bǔ)充幾個例子。

# 大版本號大于等于3
python_requires='>=3',
# 版本號大于等于3.3,但是不能超過4
python_requires='~=3.3',
# 支持2.6 2.7以及所有以3.3開頭的Python 3版本
python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4',

package_data和data_file

package_data和data_file參數(shù)用于指定數(shù)據(jù)文件,也就是在項目中使用到的非代碼文件,一般情況下通過設(shè)置include_package_data=True自動搜索就夠用了,如果需要細(xì)粒度的控制,就要使用它們了,詳情見setuptools 文檔 - Including Data Files

package_data指定包括在包中的數(shù)據(jù)文件,也就是“包數(shù)據(jù)文件”,這些文件會復(fù)制到包的相應(yīng)目錄。

package_data={
    'package_name': ['package_data.dat'],
},

data_files指定放在包外的數(shù)據(jù)文件,這些文件會被復(fù)制到項目根目錄下指定的相對目錄中。

data_files=[('my_data', ['data/data_file'])],

entry_points

entry_points參數(shù)指定一些入口點,可以看做是項目提供的一些額外功能,其中最常見的就是console_scripts,用于注冊腳本接口。setuptools提供的工具鏈可以在安裝項目分發(fā)包的時候?qū)⑦@些接口轉(zhuǎn)化為真正的可執(zhí)行腳本,更多信息參考setuptools文檔 - Automatic Script Creation。

entry_points={
    'console_scripts': [
        'sample=sample:main',
    ],
},

版本號

下面是開發(fā)、A測、B測、發(fā)布候選、最終發(fā)布等情況的版本號實例。

1.2.0.dev1  # Development release
1.2.0a1     # Alpha Release
1.2.0b1     # Beta Release
1.2.0rc1    # Release Candidate
1.2.0       # Final Release
1.2.0.post1 # Post Release
15.10       # Date based release
23          # Serial release

開發(fā)模式

setup.py文件寫完之后,項目就算是可打包狀態(tài)了。當(dāng)然也可以繼續(xù)在項目上進(jìn)行工作,這時候一般希望項目既可以作為包來安裝,又希望項目是可以編輯的,這時候就可以進(jìn)入開發(fā)模式。這種情況下需要用下面的命令來安裝包,-e選項全稱是--editable,也就是可編輯的意思;.表示當(dāng)前目錄,也就是setup.py存在的那個目錄:

pip install -e .

該命令會安裝install_requires中指定的所有包,以及console_scripts部分指定的腳本。依賴項會作為普通包來安裝,而項目本身會以可編輯狀態(tài)來安裝。特別的,如果只希望安裝項目本身而不安裝所有依賴包,用下面的命令:

pip install -e . --no-deps

如果有需要的話,還可以安裝VCS或者本地目錄中保存的包來替代官方索引中的包。詳情請查看文檔

打包項目

終于到了觀看成果的時候了,項目可以被打包成各種類型的可分發(fā)包,這里只介紹幾種最常用的。

源碼分發(fā)包(sdist)

這是最低等級的一種,基本上就是復(fù)制源代碼,不過因此在安裝的時候有一個必須的構(gòu)建(可能包括編譯)過程來生成各種元信息,哪怕項目是純的Python項目。用下面的命令來生成:

python setup.py sdist

Wheels(輪子)

在編程界各種第三方包不是被形象地稱作輪子嗎(著名梗:不要重復(fù)造輪子),這里就是這個意思。輪子是一種二進(jìn)制分發(fā)包,是現(xiàn)在最推薦的分發(fā)包格式,輪子又可以分為好幾種輪子。當(dāng)然,在構(gòu)建輪子之前,還需要安裝wheel包來提供支持。

pip install wheel

通用輪子。也就是項目中只存在Python代碼,同時兼容Python 2和Python 3的輪子,用下面的命令生成。

python setup.py bdist_wheel --universal

當(dāng)然也可以在setup.cfg配置文件中指定:

[bdist_wheel]
universal=1

純Python輪子。和通用輪子差不多,不過只支持Python 2或者Python 3.

python setup.py bdist_wheel

平臺輪子。這種輪子中不僅有Python代碼,一般還包括但不限于C代碼寫成的擴(kuò)展等,因此它們只支持特定平臺。

python setup.py bdist_wheel

運行以上命令之后,會在dist文件夾中生成打包好的可發(fā)布包。

發(fā)布項目

項目打包完畢,生成可可分發(fā)包之后,最后一步就是發(fā)布項目了。幾乎所有的項目都被發(fā)布到了Python Package Index(簡稱PyPI)上了,當(dāng)然如果有需求的話還可以搭建自己的私人索引,不過這就是另一個話題了。

很有意思的是,Python官方還提供了一個測試索引,它是一個和PyPI完全一樣的測試網(wǎng)站,定期清理,可以讓我們方便的練習(xí)上傳項目,同時不用擔(dān)心會污染官方倉庫。使用方法很簡單,先注冊一個賬戶。

上傳項目需要用到另一個類庫twine:

pip install twine

然后用下面的命令將包上傳到測試索引中,該命令會提示輸入剛才注冊用的用戶名和密碼:

twine upload --repository-url https://test.pypi.org/legacy/ dist/*

稍等片刻,上傳應(yīng)該就完成了。然后就可以在測試索引中找到我的項目了。當(dāng)然由于測試索引會定期清理的緣故,可能過段時間項目和我的賬戶就都不存在了。

上傳完成

全部流程都熟悉之后,就可以在官方索引上注冊賬號,并將項目上傳上去,這樣一來,全世界的開發(fā)者都能用到你的項目了!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容