怎么將自己寫(xiě)的包打包上傳到PyPi供其他人下載
我們先裝好依賴(lài)的包
打包和上傳依賴(lài)wheel和twine這兩個(gè)包。
pip install whell
pip install twine
先準(zhǔn)備好項(xiàng)目
比方說(shuō)我們想寫(xiě)這樣一個(gè)包,這個(gè)包只有一個(gè)功能,那就是打印程序員佛祖字符畫(huà)。

# __init__.py
buddha = ...
def print_buddha():
print(buddha)
這個(gè)包就提供了一個(gè)佛祖字符畫(huà)和一個(gè)打印函數(shù)。
# __main__.py
from . import print_buddha
print_buddha()
__main__.py讓這個(gè)包可以被python -m直接運(yùn)行,而__main__.py其實(shí)也就只干了一件事,那就是調(diào)用print_buddha打印字符畫(huà)。
打包項(xiàng)目
打包項(xiàng)目你需要學(xué)習(xí)以下知識(shí)
- 創(chuàng)建
setup.py - 打包wheel
- 用twine上傳的PyPi
一個(gè)標(biāo)準(zhǔn)的包的目錄結(jié)構(gòu)
packaging_tutorial
├── LICENSE # 開(kāi)源協(xié)議證書(shū)
├── README.md # 說(shuō)明文件
├── * your_pkg1
│ └── __init__.py
├── * your_pkg2
│ └── __init__.py
├──*setup.py # 顧名思義了
└── tests # 單元測(cè)試
加星的是必須的,其他的可有可無(wú)不影響打包和上傳PyPi,就是不規(guī)范而已。
創(chuàng)建setup.py
setup.py是setuptools的構(gòu)建腳本。它告訴setuptools有關(guān)您的軟件包(例如名稱(chēng)和版本)以及要包括的代碼文件的信息。
最基本的setup.py
# -*- coding: utf-8 -*-
from setuptools import setup, find_packages
try:
long_description = open("README.md").read()
except IOError:
long_description = ""
setup(
name="Buddha",
version="0.1.0",
description="A pip package",
license="MIT",
author="你的名字",
packages=find_packages(),
long_description=long_description,
classifiers=[
"Programming Language :: Python",
"Programming Language :: Python :: 3.6",
]
)
然后我們?cè)囈幌麓虬?/p>
python setup.py sdist bdist_wheel
這是個(gè)組合命令,意思是先打sdist,再打bdist_wheel。

sdist打出來(lái)的是tar.gz格式壓縮包,里面就是包源文件,可以供直接解壓然后python setup.py install安裝。
bdist_wheel打出來(lái)的是whl格式文件,這是pip官方安裝包文件格式。
注意:egg格式已經(jīng)過(guò)時(shí)了,現(xiàn)在PyPi的包都是用whl格式。
什么是egg?
Wheel和Egg都是打包格式,旨在支持不需要構(gòu)建或編譯的安裝工件的用例,這在測(cè)試和生產(chǎn)工作流程中可能會(huì)耗費(fèi)大量成本。
python setup.py bdist_egg 就可以打出egg格式的包。
Egg格式由setuptools于2004年推出,而Wheel格式由PEP 427于2012年推出。
目前,Wheel被認(rèn)為是Python構(gòu)建和二進(jìn)制打包的標(biāo)準(zhǔn)。
這是Wheel和Egg之間重要區(qū)別的細(xì)分。
PiPy上傳規(guī)范是同時(shí)要tar.gz和whl兩個(gè)格式的包。
setup.py參數(shù)詳解
注意:打了星的是必須參數(shù)
描述性參數(shù) —— 提供包信息,供PiPy識(shí)別管理。
| 參數(shù) | 類(lèi)型 | 說(shuō)明 |
|---|---|---|
| *name | str | 包名稱(chēng) |
| *version | str | 包版本 |
| *author | str | 程序的作者,這個(gè)包從頭到尾都是你先開(kāi)發(fā)的,那么你就是原作者。 |
| *author_email | str | 程序的作者的郵箱地址 |
| maintainer[^maintainer] | str | 維護(hù)者,如果你不是原作者,這個(gè)包是你修改原作者的包,那么你就是維護(hù)者。 |
| maintainer_email | str | 維護(hù)者的郵箱地址 |
| *url | str | 程序的官網(wǎng)地址 |
| license | str | 程序的授權(quán)信息 |
| description | str | 程序的簡(jiǎn)單描述 |
| long_description | str | 程序的詳細(xì)描述,詳細(xì)描述在包上傳PyPi后會(huì)在包頁(yè)面下顯示,支持RST和MD兩種格式。 |
| platforms | str | 程序適用的軟件平臺(tái)列表 |
| classifiers | str | 程序的所屬分類(lèi)列表。影響上傳PyPi會(huì)被分類(lèi)到哪個(gè)類(lèi)別。 |
| keywords | str | 程序的關(guān)鍵字列表。同上。 |
| download_url | str | 程序的下載地址 |
描述性參數(shù)只是作為PKG_INFO用的,沒(méi)有什么特殊作用,就是描述用的,用來(lái)供pip和PyPi管理的,因此看上面就可以了。
包文件搜集 —— 設(shè)置包的哪些文件需要打包,怎么找這些文件。(很重要)
| 參數(shù) | 說(shuō)明 |
|---|---|
| *package_dir | 用來(lái)給setuptools指示packages里的包名要從哪些目錄下找,默認(rèn)是setup.py所在的根目錄下找packages。 |
| *packages | 打包的包目錄(通常為包含 __init__.py 的文件夾)。可以手動(dòng)一個(gè)個(gè)包名添加,也可以直接用find_package自動(dòng)找指定目錄下所有帶 __init__.py的文件夾。默認(rèn)只將文件夾內(nèi)所有的.py文件打包。如果文件夾內(nèi)有其他類(lèi)型文件,并且包依賴(lài)這些文件,需要通過(guò)設(shè)置package_data來(lái)聲明要打包的文件/類(lèi)型。搜集的文件夾會(huì)被安裝到site-packages目錄下。 |
| *package_data | 指定包內(nèi)需要包含的數(shù)據(jù)文件類(lèi)型。默認(rèn)packages只會(huì)把.py文件打包進(jìn)去,如果包依賴(lài)其他類(lèi)型文件就需要在package_data里聲明要打包進(jìn)去的文件類(lèi)型。 |
| include_package_data | 如果設(shè)置為T(mén)rue,這將告訴setuptools自動(dòng)將它找到的所有數(shù)據(jù)文件包含在MANIFEST.in文件指定的軟件包目錄中。MANIFEST.in可通過(guò)setuptool插件自動(dòng)跟蹤版本控制工具生成。 |
| exclude_package_data | 將包名稱(chēng)映射到應(yīng)該從包目錄中排除的全局模式列表的字典。您可以使用它來(lái)修剪include_package_data包含的所有多余文件。有關(guān)完整的描述和示例,請(qǐng)參見(jiàn)“包括數(shù)據(jù)文件”部分。這個(gè)參數(shù)服務(wù)于include_package_data。 |
| *py_modules | 需要打包的 Python 單文件列表。如果模塊只是一個(gè)py文件,那么添加到這里打包進(jìn)去。注意只需要寫(xiě)文件名,不要帶.py后綴。 |
| *data_files | 打包時(shí)需要打包的數(shù)據(jù)文件,如圖片,配置文件等。格式("路徑","文件"),路徑是PYTHON_HOME目錄下的相對(duì)路徑。 |
| scripts | 指定可執(zhí)行腳本,安裝時(shí)腳本會(huì)被安裝到系統(tǒng)PATH路徑下(PYTHON_HOME\Scripts)。注意,一般是指命令行腳本,只有這種腳本安裝到系統(tǒng)PATH路徑下可以直接在命令行里調(diào)用。 |
setup.py 需要提供以上參數(shù)數(shù)據(jù)用來(lái)搜集需要打到包里的文件:
注意:打了星的是重要參數(shù)
根據(jù)上面的說(shuō)明,我們來(lái)演練一下。首先項(xiàng)目要做些修改和添加一些功能。

- 我們把包的源碼都放到src目錄下了。
- 在
buddha模塊里增加一個(gè)buddha.txt的文本用來(lái)存我們的佛祖字符畫(huà),并將buddha模塊里寫(xiě)死的字符畫(huà)改為讀這個(gè)文件。 - 增加一個(gè)buddha.bat腳本,這個(gè)腳本里面就一行
python -m buddha,調(diào)用buddha模塊打印字符畫(huà),讓buddha包支持命令行直接調(diào)用,就省去輸入python -m的麻煩。 - 增加一個(gè)字符畫(huà)txt文本,里面就是佛祖字符畫(huà),這個(gè)文本就當(dāng)做是個(gè)文檔,我們會(huì)放到PYTHON_HOME的Doc文件夾里。
- 增加一個(gè)
single_module,試一下單文件模塊怎么打包到包里。
接著修改setup.py:
setup(
# 包信息
...
# 包搜集
package_dir={"":"src"},
packages=find_packages("src"),
package_data={"buddha":["*.txt"]},
py_modules=["single_module"],
package_data={"buddha":["*.txt"]}
data_files=[("Doc",["src/buddha_doc.txt"])],
scripts=["src/buddha.bat"],
)
-
find_packages需要指定從遍歷哪個(gè)文件夾下的包。你可能會(huì)有疑問(wèn),為什么設(shè)置了package_dir還需要指明,不是會(huì)默認(rèn)從src目錄下找嗎?其實(shí)是這樣的,find_package這個(gè)函數(shù)只是返回一個(gè)字符串?dāng)?shù)組,在我們的項(xiàng)目里返回的就是["buddha"]。如果你不用find_package那么就是手寫(xiě)["buddha"],find_package只是個(gè)工具函數(shù),省去你手寫(xiě)的麻煩,但是并不是setuptools內(nèi)部的機(jī)制,find_package不認(rèn)package_dir。setuptools最終會(huì)用packages的字符串?dāng)?shù)組到package_dir去找包,然后copy到build文件夾,最終一起打到dist目錄下生成二進(jìn)制whl文件。 -
packages默認(rèn)只搜集py文件,buddha.txt不會(huì)被打到包里,因此需要在package_data里指明。 - 我們的
single_module是一個(gè)文件模塊,packages只處理文件夾模塊。因此要用py_modules參數(shù)來(lái)添加。 - 我們有個(gè)
buddha_doc.txt字符畫(huà)文本,我們把它當(dāng)做文檔,希望包安裝的時(shí)候能夠被放到PYTHON_HOME目錄下的Doc文件夾里。image.png
(這本身沒(méi)什么意義,只是演示怎么將自己的文件安裝到自己希望的位置,如果你了解pth文件你就知道將文件安裝到自己想要的位置有什么意義。) - 我們有個(gè)
buddha.bat腳本,希望其能夠被安裝到PYTHON_HOME/Scripts目錄下,這樣命令行就能直接調(diào)用。
注意:data_files、scripts這兩個(gè)參數(shù)不享受package_dir的效果,必須指定根目錄下相對(duì)路徑。
打包看看:
python setup.py sdist bdist_wheel

試一下安裝:
pip isntall dist\buddha-0.1.0-py3-none-any.whl
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Processing c:\users\administrator\desktop\pylab\dist\buddha-0.1.0-py3-none-any.whl
Installing collected packages: buddha
Successfully installed buddha-0.1.0
我們?nèi)YTHON_HOME看看我們的包被安裝到哪里。




我們的包各種文件都被安裝到了我們想要的位置。
試一下命令行調(diào)用buddha.bat:

python里導(dǎo)入buddha模塊:

python里導(dǎo)入single_module模塊:

依賴(lài)
| 參數(shù) | 說(shuō)明 |
|---|---|
| ext_modules | 指定c擴(kuò)展模塊 |
| 指定依賴(lài)的其他包。你的包依賴(lài)了其他外部包就添加到這里,安裝你的包的時(shí)候會(huì)一并安裝。(已過(guò)時(shí),被install_requires代替了) | |
| install_requires | 安裝時(shí)需要安裝的依賴(lài)包,同上。 |
| setup_requires | 指定運(yùn)行 setup.py 文件本身所依賴(lài)的包。如果你在setup.py里面引用了依賴(lài)的包,就需要添加到這里。 |
| extras_require | 當(dāng)前包的高級(jí)/額外特性需要依賴(lài)的分發(fā)包。extras你可以理解為可選外部依賴(lài),比如你的包有導(dǎo)出數(shù)據(jù)功能,默認(rèn)支持csv格式,如果要導(dǎo)出excel格式則需要安裝openxlrd,那么openxlrd就是可選外部依賴(lài)。 |
| 指定可以為哪些模塊提供依賴(lài)。pip已經(jīng)忽略這個(gè)參數(shù)了。 | |
| dependency_links | 指定依賴(lài)包的下載地址。pip已經(jīng)不支持了。 |
命令行
| 參數(shù) | 類(lèi)型 | 說(shuō)明 |
|---|---|---|
| cmdclass | Dict[str, class] | 添加自定義命令到setup.py命令行界面。 |
| entry_points | Dict[str,str] | 動(dòng)態(tài)發(fā)現(xiàn)服務(wù)和插件 |
- cmdclass 比較少用到。我們知道setup.py有build install兩個(gè)子命令,sdist和bdist_wheel也是其子命令。cmdclass可以讓我們自己自定義setup.py的子命令。推薦文章《How To Add Custom Build Steps and Commands To setup.py》
- entry_points,某種意義上講,跟前面的
scripts參數(shù)很像,其作用就是為了公開(kāi)一個(gè)命令給控制臺(tái)。
例子:
entry_points={
"console_scripts": [
"print_buddha = buddha:print_buddha",
],
"gui_scripts": [
"single_module = sindle_module:a_module",
]
}
- print_buddha是要生成的命令名,調(diào)用pring_buddha會(huì)去執(zhí)行buddha模塊里的print_buddha函數(shù)。
- entry_point 格式是 命令名=模塊路徑(與import路徑一致):函數(shù)名。
- 包安裝后會(huì)在Scripts目錄下生成一個(gè)print_buddha.exe,這也是為什么我們可以直接在控制臺(tái)調(diào)用print_buddha的原因。
- console_scripts和gui_scripts的區(qū)別在于console_scripts下生成的exe調(diào)用的時(shí)候會(huì)用標(biāo)準(zhǔn)輸入輸出重定向到控制臺(tái),用起來(lái)就跟用命令一樣;而gui_scripts生成的exe則不會(huì)重定向到控制臺(tái),控制臺(tái)上調(diào)用了什么都不會(huì)打印,因此主要用來(lái)啟動(dòng)一個(gè)gui程序,例如用tkinter寫(xiě)的界面。
- entry_points的作用有兩個(gè),當(dāng)我們的入口函數(shù)不是模塊的main.py而是某個(gè)py文件里的函數(shù)的時(shí)候,entry_points可以那個(gè)函數(shù)直接暴露到命令行。另一個(gè)作用是公開(kāi)入口點(diǎn)給其他
壓縮選項(xiàng)
| 參數(shù) | 類(lèi)型 | 說(shuō)明 |
|---|---|---|
| zip_safe | bool | 不壓縮包,而是以目錄的形式安裝。這個(gè)選項(xiàng)在打egg格式的包的時(shí)候有用,現(xiàn)在都用whl不用egg了。 |
更加詳細(xì)的說(shuō)明可以去參考官方文檔《setuptools documeng: Building and Distributing Packages with Setuptools》
上傳包到PyPi
注冊(cè)PyPi賬號(hào)
到PyPi官網(wǎng)注冊(cè)一個(gè)賬號(hào),通過(guò)各種驗(yàn)證通。
上傳PyPi
python -m twine upload dist/*
將dist下的tar.gz和whl文件上傳到PyPi。
會(huì)提示輸入用戶(hù)名密碼,照著做就行。
上傳成功后就會(huì)出現(xiàn)在PyPi和你的項(xiàng)目頁(yè)里。
更新版本
你無(wú)法重新上傳覆蓋版本,所以如果你的包有bug或者有修改,你必須修改setup.py里的version,然后再打一次包上傳。
