Contents
- setup.py vs requirements.txt
- Python庫
- Python應用
- 那麼,抽象和具體又有什麼關係呢?
- Setuptools的dependency_links
- 開發可複用的包與不重複自己
對於 setup.py
和 requirements.txt
的角色有很多誤解,很多人認為它們是兩個重複的事情,甚至創造了 工具 來處理 這種“重複”。
12.1. Python庫
這裡所說的Python庫是指那些被開發並且為了其他人來使用而釋出的東西,你可以在 PyPI 找到很多Python庫。為了更好的推廣和傳播 自己,Python庫會包含很多的資訊,比如它的名字,版本號,依賴等等。而 setup.py
就是用來提供這些資訊的:
1 2 3 4 5 6 7 8 9 10 11 |
from setuptools import setup setup( name="MyLibrary", version="1.0", install_requires=[ "requests", "bcrypt", ], # ... ) |
這樣很簡單地宣告瞭這個Python庫的一些資訊。但是,你並沒有看到你可以從哪裡獲取這些依賴庫。 這裡並沒有提供一個url或者一個檔案系統來獲取這些依賴,而只是告訴我們依賴是 requests
, bcrypt
,這很重要,我們可以稱這種宣告方式為“抽象的依賴”,它們只是以名字(或者加上了一個具體的版本號) 的方式來出現的。這就像我們說的“鴨子型別”,你不在乎它是什麼樣子的只要它看起來是requests
。
12.2. Python應用
這裡所講的Python應用是指你所要部署的一些東西,這是區別於我們之前所講的Python庫的。Python應用或許可以在 PyPI上找到,但是不像Python庫,它們並不是一種可以被開發者使用多次的工具性的東西。PyPI上的Python應用經常會 在這個應用的旁邊放置一個檔案用來宣告該應用部署的依賴。
一個應用經常會有很多依賴,或許會很複雜。這些依賴裡很多沒有一個名字,或者沒有我們說所的那些資訊。這便反映了 pip 的requirements檔案所做的事情了,一個典型的requirements檔案看起來是這樣的:
1 2 3 4 5 6 |
# This is an implicit value, here for clarity --index https://pypi.python.org/simple/ MyPackage==1.0 requests==1.2.0 bcrypt==1.0.2 |
這裡每個依賴都標明瞭準確的版本號,一般一個Python庫對依賴的版本比較寬鬆,而一個應用則會依賴比較具體的版本號。雖然也許跑其他 版本的 requests
並不會出錯,但是我們在本地測試順利後,我們就會希望線上上也跑相同的版本。
檔案的頭部有一個 --index https://pypi.python.org/simple/
,一般如果你不用宣告這項,除非你使用的不是PyPI。然而它卻是 requirements.txt
的一個重要部分, 這一行把一個抽象的依賴宣告 requests==1.2.0
轉變為一個具體的依賴宣告requests 1.2.0 from pypi.python.org/simple/
,這不像“鴨子型別”,倒像一次isinstance
檢查。
12.3. 那麼,抽象和具體又有什麼關係呢?
讀到這裡你或許會說,OK, 我已經知道 setup.py
是為可發行的Python庫那些包準備的,而 requirements.txt
是為那些不被經常作為工具利用的Python應用準備的,但是我已經把我的 requirements.txt
讀進來填充了我的 install_requires=[...]
啊(譯者注: 比如你在 setup.py
中把 requirements.txt
檔案讀取進來並切割成行列表,賦值給關鍵字 install_requires
),那我為何要在乎這個區別呢?
對於抽象依賴和具體依賴的區分是非常重要的,這點使我們的PyPI映象源正常工作,這點允許我們可以在公司裡搭建我們 私有的包索引服務,甚至這點允許了你去fork一個包並改造它。因為一個抽象的依賴只是一個名字和一個可選的版本號,你可以從 PyPi來安裝它,或者從你自己的檔案系統,你可以fork它並改造它,只要你指明瞭正確的名字和版本號你就可以一直這麼使用下去。
一個極端點的情況是,你在該使用抽象依賴的地方使用了具體的依賴,這在Go語言中可以看到
1 2 3 |
import ( "github.com/foo/bar" ) |
這裡我們指明瞭一個具體的url。現在如果我以這種指明url的方式使用了這個庫,而且現在我想要改造這個庫(比如它缺失了我想要的某個功能, 或者有一個討厭的bug)。我可能不僅僅需要fork bar
這個庫,還需要fork依賴這個庫的其他庫。(譯者注:也就是說,想要替換一個底層依賴的話,需要改動依賴這個庫的其他依賴對該庫的依賴宣告。)
12.4. Setuptools的dependency_links
Setuptools有一個功能叫做 dependency_links
1 2 3 4 5 6 7 8 9 |
from setuptools import setup setup( # ... dependency_links = [ "http://packages.example.com/snapshots/", "http://example2.com/p/bar-1.0.tar.gz", ], ) |
這一功能除去了依賴的抽象特性,直接把依賴的獲取url標在了setup.py裡。就像在Go語言中修改依賴包一樣,我們只需要修改依賴鏈中每個包的 dependency_links
。
12.5. 開發可複用的包與不重複自己
那麼我們寫依賴宣告的時候需要在 setup.py
中寫好抽象依賴,在requirements.txt
中寫好具體的依賴,但是我們並不想維護兩份依賴檔案,這樣會讓我們很難 做好同步。 requirements.txt
可以更好地處理這種情況,我們可以在有 setup.py
的目錄裡寫下一個這樣的 requirements.txt
1 2 3 |
--index https://pypi.python.org/simple/ -e . |
這樣
pip install -r requirements.txt
可以照常工作,它會先安裝該檔案路徑下的包,然後繼續開始解析抽象依賴,結合 --index
選項後轉換為具體依賴然後再安裝她們。
這個辦法可以讓我們解決一種類似這樣的情形:比如你有兩個或兩個以上的包在一起開發但是是分開發行的,或者說你有一個尚未釋出的包並把它分成了幾個部分。如果你的頂層的包 依然僅僅按照“名字”來依賴的話,我們依然可以使用requirements.txt
來安裝開發版本的依賴包:
1 2 3 4 |
--index https://pypi.python.org/simple/ -e https://github.com/foo/bar.git#egg=bar -e . |
這會首先從 https://github.com/foo/bar.git 來安裝包 bar
, 然後進行到第二行 -e .
,開始安裝 setup
中的抽象依賴,但是包 bar
已經安裝過了, 所以 pip 會跳過安裝,而是仍然使用github.com上安裝了的開發版本的包 bar
。