翻譯:《實用的Python編程》09_01_Packages
- 2021 年 4 月 16 日
- 筆記
- Python, 實用的Python編程
目錄| 上一節 (8.3 調試) | 下一節 (9.2 第三方包)
9.1 包
如果編寫一個較大的程序,我們並不真的想在頂層將其組織為一個個獨立文件的大型集合。本節對包(package)進行介紹。
模塊
任何一個 Python 源文件稱為一個模塊(module)。
# foo.py
def grok(a):
...
def spam(b):
...
一條 import
語句加載並執行 一個模塊。
# program.py
import foo
a = foo.grok(2)
b = foo.spam('Hello')
...
包 vs 模塊
對於較大的代碼集合,通常將模塊組織到包中。
# From this
pcost.py
report.py
fileparse.py
# To this
porty/
__init__.py
pcost.py
report.py
fileparse.py
首先,選擇一個名字並用該名字創建頂級目錄。如上述的 porty
(顯然,第一步最重要的是選擇名字)。
接着,添加 __init__.py
文件到該目錄中。__init__.py
文件可以是一個空文件。
最後,把源文件放到該目錄中。
使用包
包用作導入的命名空間。
這意味着現在有了多級導入。
import porty.report
port = porty.report.read_portfolio('port.csv')
導入語句還有其它變體:
from porty import report
port = report.read_portfolio('portfolio.csv')
from porty.report import read_portfolio
port = read_portfolio('portfolio.csv')
兩個問題
這種方法存在兩個主要的問題:
- 同一包內不同文件之間的導入無效。
- 包中的主腳本無效。
因此,基本上一切導入都是無效的,但是,除此之外,程序還是可以工作的。
問題:導入
現在,在導入的時候,同一包內的不同文件之間的導入必須包含包名。請記住這個結構:
porty/
__init__.py
pcost.py
report.py
fileparse.py
根據上述規則(同一包內的不同文件之間的導入必須包含包名)修改後的導入示例:
# report.py
from porty import fileparse
def read_portfolio(filename):
return fileparse.parse_csv(...)
所有的導入都是絕對的,而不是相對的。
# report.py
import fileparse # BREAKS. fileparse not found
...
相對導入
除了使用包名直接導入,還可以使用使用 .
引用當前的包。
# report.py
from . import fileparse
def read_portfolio(filename):
return fileparse.parse_csv(...)
語法:
from . import modname
使用上述語法使得重命名包變得容易。
問題:主腳本
將包內的子模塊作為主腳本運行會導致程序中斷:
bash $ python porty/pcost.py # BREAKS
...
原因:你正在運行單個腳本,而 Python 不知道包的其餘部分(sys.path
是錯誤的)。
所有的導入都會中斷。要想解決這個問題,需要以不同的方式運行程序,可以使用 -m
選項。
bash $ python -m porty.pcost # WORKS
...
__init__.py
文件
該文件的主要目的是將模塊組織在一起。
例如:
# porty/__init__.py
from .pcost import portfolio_cost
from .report import portfolio_report
這使得導入的時候名字出現在頂層。
from porty import portfolio_cost
portfolio_cost('portfolio.csv')
而不是使用多級導入:
from porty import pcost
pcost.portfolio_cost('portfolio.csv')
腳本的另一種解決方案
如前所述,需要使用 -m package.module
運行包內的腳本。
bash % python3 -m porty.pcost portfolio.csv
還有另一種選擇:編寫一個新的頂級腳本。
#!/usr/bin/env python3
# pcost.py
import porty.pcost
import sys
porty.pcost.main(sys.argv)
腳本位於包外面。目錄結構如下:
pcost.py # top-level-script
porty/ # package directory
__init__.py
pcost.py
...
應用結構
代碼組織和文件結構是應用程序可維護性的關鍵。
對於 Python 而言,沒有「放之四海而皆準」的方法,但是一個適用於多種問題的結構就是這樣:
porty-app/
README.txt
script.py # SCRIPT
porty/
# LIBRARY CODE
__init__.py
pcost.py
report.py
fileparse.py
頂級 porty-app
目錄是所有其他內容的容器——這些內容包括文檔,頂級腳本,用例等。
同樣,頂級腳本(如果有)需要放置在代碼包之外(包的上一層)。
#!/usr/bin/env python3
# porty-app/script.py
import sys
import porty
porty.report.main(sys.argv)
練習
此時,我們有了一個包含多個程序的目錄:
pcost.py # computes portfolio cost
report.py # Makes a report
ticker.py # Produce a real-time stock ticker
同時,還有許多具有各種功能的支持模塊:
stock.py # Stock class
portfolio.py # Portfolio class
fileparse.py # CSV parsing
tableformat.py # Formatted tables
follow.py # Follow a log file
typedproperty.py # Typed class properties
在本次練習中,我們將整理這些代碼並將它們放入一個通用包中。
練習 9.1:創建一個簡單的包
請創建一個名為 porty
的目錄並將上述所有的 Python 文件放入其中。另外,在 porty
目錄中創建一個空的 __init__.py
文件。最後,文件目錄看起來像這樣:
porty/
__init__.py
fileparse.py
follow.py
pcost.py
portfolio.py
report.py
stock.py
tableformat.py
ticker.py
typedproperty.py
請將 porty
目錄中的 __pycache__
目錄移除。該目錄包含了之前預編譯的 Python 模塊。我們想重新開始。
嘗試導入包中的幾個模塊:
>>> import porty.report
>>> import porty.pcost
>>> import porty.ticker
如果這些導入失敗,請進入到合適的文件中解決模塊導入問題,使其能夠包括相對導入。例如,import fileparse
語句可以像下面這樣進行修改:
# report.py
from . import fileparse
...
如果有類似於 from fileparse import parse_csv
這樣的語句,請像下面這樣修改代碼:
# report.py
from .fileparse import parse_csv
...
練習 9.2:創建應用目錄
對應用而言,將所有代碼放到「包」中通常是不夠的。有時,支持文件,文檔,腳本等文件需要放到 porty/
目錄之外。
請創建一個名為 porty-app
的新目錄。然後將我們在練習 9.1 中創建的 porty
目錄移動到 porty-app
目錄中。接着,複製測試文件 Data/portfolio.csv
和 Data/prices.csv
到 porty-app
目錄。另外,在 porty-app
目錄下創建一個 README.txt
文件,該文件包含一些有關自己的信息。現在,代碼的組織結構像下面這樣:
porty-app/
portfolio.csv
prices.csv
README.txt
porty/
__init__.py
fileparse.py
follow.py
pcost.py
portfolio.py
report.py
stock.py
tableformat.py
ticker.py
typedproperty.py
要運行代碼,需要確保你現在正在頂級目錄 porty-app/
下。例如,從終端運行:
shell % cd porty-app
shell % python3
>>> import porty.report
>>>
嘗試將之前的腳本作為主程序運行:
shell % cd porty-app
shell % python3 -m porty.report portfolio.csv prices.csv txt
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
shell %
練習 9.3:頂級腳本
使用 python -m
命令通常有點怪異。可能需要編寫一個頂級腳本來處理奇怪的包。請創建一個生成上述報告的腳本 print-report.py
:
#!/usr/bin/env python3
# print-report.py
import sys
from porty.report import main
main(sys.argv)
然後把腳本 print-report.py
放到頂級目錄 porty-app/
中。並確保可以在 porty-app/
目錄下運行它:
shell % cd porty-app
shell % python3 print-report.py portfolio.csv prices.csv txt
Name Shares Price Change
---------- ---------- ---------- ----------
AA 100 9.22 -22.98
IBM 50 106.28 15.18
CAT 150 35.46 -47.98
MSFT 200 20.89 -30.34
GE 95 13.48 -26.89
MSFT 50 20.89 -44.21
IBM 100 106.28 35.84
shell %
最後,代碼的組織結構應該下面這樣:
porty-app/
portfolio.csv
prices.csv
print-report.py
README.txt
porty/
__init__.py
fileparse.py
follow.py
pcost.py
portfolio.py
report.py
stock.py
tableformat.py
ticker.py
typedproperty.py
目錄| 上一節 (8.3 調試) | 下一節 (9.2 第三方包)
註:完整翻譯見 //github.com/codists/practical-python-zh