這篇文章主要介紹了構建Python包的五個簡單准則簡介,在Github開源合作日趨主流的今天,健壯的Python包的構建成為開發者必須要考慮到的問題,本文提出了五項建議,需要的朋友可以參考下
創建一個軟件包(package)似乎已經足夠簡單了,也就是在文件目錄下搜集一些模塊,再加上一個__init__.py文件,對吧?我們很容易看出來,隨著時間的推移,通過對軟件包的越來越多的修改,一個設計很差的軟件包可能會出現循環依賴問題,或是可能變得不可移植和不可靠。
1. __init__.py 僅為導入服務
對於一個簡單的軟件包,你可能會忍不住把工具方法,工廠方法和異常處理都丟進__init__.py,千萬別這樣!
一個結構良好的__init__.py文件,僅為一個非常重要的目的來服務:從子模塊導入。你的__init__.py應該看起來像這個樣子:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# ORDER MATTERS HERE -- SOME MODULES ARE DEPENDANT ON OTHERS
# 導入順序要考慮——一些模塊會依賴另外的一些
from exceptions import FSQError, FSQEnvError, FSQEncodeError,
FSQTimeFmtError, FSQMalformedEntryError,
FSQCoerceError, FSQEnqueueError, FSQConfigError,
FSQPathError, FSQInstallError, FSQCannotLockError,
FSQWorkItemError, FSQTTLExpiredError,
FSQMaxTriesError, FSQScanError, FSQDownError,
FSQDoneError, FSQFailError, FSQTriggerPullError,
FSQHostsError, FSQReenqueueError, FSQPushError
# constants relies on: exceptions, internal
import constants
# const relies on: constants, exceptions, internal
from const import const, set_const
# has tests
# path relies on: exceptions, constants, internal
import path
# has tests
# lists relies on: path
from lists import hosts, queues
#...
2.使用__init__.py來限制導入順序
把方法和類置於軟件包的作用域中,這樣用戶就不需要深入軟件包的內部結構,使你的軟包變得易用。
作為調和導入順序的唯一地方。
使用得當的話,__init__.py 可以為你提供重新組織內部軟件包結構的靈活性,而不需要擔心由內部導入子模塊或是每個模塊導入順序所帶來的副作用。因為你是以一個特定的順序導入子模塊,你的__init__.py 對於他程序員來講應該簡單易懂,並且能夠明顯的表示該軟件包所能提供的全部功能。
文檔字符串,以及在軟件包層面對__all__屬性的賦值應當是__init__.py中唯一的與導入模塊不相關的代碼:
?
1
2
3
4
5
6
7
8
9
10__all__ = [ 'FSQError', 'FSQEnvError', 'FSQEncodeError', 'FSQTimeFmtError',
'FSQMalformedEntryError', 'FSQCoerceError', 'FSQEnqueueError',
'FSQConfigError', 'FSQCannotLock', 'FSQWorkItemError',
'FSQTTLExpiredError', 'FSQMaxTriesError', 'FSQScanError',
'FSQDownError', 'FSQDoneError', 'FSQFailError', 'FSQInstallError',
'FSQTriggerPullError', 'FSQCannotLockError', 'FSQPathError',
'path', 'constants', 'const', 'set_const', 'down', 'up',
# ...
]
3.使用一個模塊來定義所有的異常
你也許已經注意到了,__init__.py中的第一個導入語句從exceptions.py子模塊中導入了全部的異常。從這裡出發,你將看到,在大多數的軟件包中,異常被定義在引起它們的代碼附近。盡管這樣可以為一個模塊提供高度的完整性,一個足夠復雜的軟件包會通過如下兩種方式,使得這一模式出現問題。
通常一個模塊/程序需要從一個子模塊導入一個函數, 利用它導入代碼並拋出異常。為了捕獲異常並保持一定的粒度,你需要導入你需要的模塊,以及定義了異常的模塊(或者更糟,你要導入一系列的異常)。這一系列衍生出來的導入需求,是在你的軟件包中編織一張錯綜復雜的導入之網的始作俑者。你使用這種方式的次數越多,你的軟件包內部就變的越相互依賴,也更加容易出錯。
隨著異常數量的不斷增長,找到一個軟件包可能引發的全部異常變的越來越難。把所有的異常定義在一個單獨的模塊中,提供了一個方便的地方,在這裡,程序員可以審查並確定你的軟件包所能引發全部潛在錯誤狀態。
你應該為你的軟件包的異常定義一個基類:
?
1
2
3
4class APackageException(Exception):
'''root for APackage Exceptions, only used to except any APackage error, never raised'''
pass
然後確保你的軟件包在任何錯誤狀態下,只會引發這個基類異常的子類異常,這樣如果你需要的話,你就可以阻止全部的異常:
?
1
2
3
4
5
6try:
'''bunch of code from your package'''
except APackageException:
'''blanked condition to handle all errors from your package'''
對於一般的錯誤狀態,這裡有一些重要的異常處理已經被包括在標准庫中了(例如,TypeError, ValueError等)
靈活地定義異常處理並保持足夠的粒度:
?
1
2
3
4
5
6
7
8
9
10
11
12
13# from fsq
class FSQEnvError(FSQError):
'''An error if something cannot be loaded from env, or env has an invalid
value'''
pass
class FSQEncodeError(FSQError):
'''An error occured while encoding or decoding an argument'''
pass
# ... and 20 or so more
在你的異常處理中保持更大的粒度,有利於讓程序員們在一個try/except中包含越來越大的,互相不干涉的代碼段。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34# this
try:
item = fsq.senqueue('queue', 'str', 'arg', 'arg')
scanner = fsq.scan('queue')
except FSQScanError:
'''do something'''
except FSQEnqueueError:
'''do something else'''
# not this
try:
item = fsq.senqueue('queue', 'str', 'arg', 'arg')
except FSQEnqueueError:
'''do something else'''
try:
scanner = fsq.scan('queue')
except FSQScanError:
'''do something'''
# and definitely not
try:
item = fsq.senqueue('queue', 'str', 'arg', 'arg')
try:
scanner = fsq.scan('queue')
except FSQScanError:
'''do something'''
except FSQEnqueueError:
'''do something else'''
在異常定義時保持高度的粒度,會減少錯綜復雜的錯誤處理,並且允許你把正常執行指令和錯誤處理指令分別開來,使你的代碼更加易懂和更易維護。
4. 在軟件包內部只進行相對導入
在子模塊中你時常見到的一個簡單錯誤,就是使用軟件包的名字來導入軟件包。
?
1
2# within a sub-module
from a_package import APackageError
這樣做會導致兩個不好的結果:
子模塊只有當軟件包被安裝在 PYTHONPATH 內才能正確運行。
子模塊只有當這個軟件包的名字是 a_package 時才能正確運行。
盡管第一條看上去並不是什麼大問題,但是考慮一下,如果你在 PYTHONPATH 下的兩個目錄中,有兩個同名的軟件包。你的子模塊可能最終導入了另一個軟件包,你將無意間使得某個或某些對此毫無戒備的程序員(或是你自己)debug 到深夜。
?
1
2
3