1.要解決的問題
Python在語義中存在著包、模組、類(當然還有函式)這幾個概念。
在編寫Python程式碼時,我們需要管理程式碼的檔案目錄結構。
這時候會遇到這樣一種情況:
1.由於Python一個檔案算一個模組,一個帶__init__.py的目錄算一個包。
2.而為了控制程式碼檔案不要過大,我們需要的是一個類(幾個類或加些許函式)分配一個檔案。
3.這時候會出現類似這樣的語句:
1 2 3 4 5 6 7 8 9 |
#第一種 import package_a.class_a_file as caf a = caf.ClassA() #第二種 form package_a.class_a_file import * a = ClassA() #另外幾種就不意義列舉了... |
以上除了直觀上可以看出import過長外,隱藏的另一點是我們是希望一個類用一個檔案,在使用多個相關類的時候就必須寫很多import。(注:我們可不想一堆程式碼扎堆,弄出一個超大程式碼檔案。)
2.解決方案要達到的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#檔案目錄結構 #|--demo.py #|--package_a # |--__init__.py # |--class_a.py #類ClassA # |--class_b.py #類ClassB和函式func_b() #demo.py中可以直接使用 from package_a import * a = ClassA() b = ClassB() func_b() |
由於Python裡一個帶__init__.py的目錄算一個包,所以利用這一機制,把類檔案放在包裡,用包來管理類。
注:在Python裡“包是模組,而模組不是包”。用system.modules可以取到的名字是包和模組都有的,而用__package__卻能很好的區分包和模組。也就是“包其實是一種特殊的模組”。
3.解決方案
這就是解決方案的檔案base.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 |
import sys _packet_ = {} #它是個裝飾器,item是類,或者函式 def export(item): #獲取item的模組物件 module = sys.modules[item.__module__] #由模組物件得到包物件 package = sys.modules[module.__package__] #把item新增到包的__dict__裡 package.__dict__[item.__name__] = item #生成所有使用該解決方案的包的__all__變數,並把匯出的item新增進去 if not package.__name__ in _packet_: _packet_[package.__name__] = [] _packet_[package.__name__].append(item.__name__) #原封不動地把item返回 return item #它是個函式,在包__init__.py裡用於獲取__all__ def packet(name): if not name in _packet_: _packet_[name] = [] return _packet_[name] |
程式碼用意我寫在註釋裡了,就是以裝飾器來把類新增到包的__dict__和__all__裡。__all__需要利用packet在包裡生成,不這麼做只會使得from package_name import * 後不能找到類,需要寫具體的類名from package_name import ClassA。
4.使用解決方案
先來看下使用解決方案後的目錄結構:
1 2 3 4 5 6 7 |
#檔案目錄結構(使用後結構只多了base.py) #|--base.py #|--demo.py #|--package_a # |--__init__.py # |--class_a.py #類ClassA # |--class_b.py #類ClassB和函式func_b() |
程式碼處就需要做到以下幾點:
1.關於被匯出的類檔案裡應該怎麼做,這裡以class_b.py為例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# ./package_a/class_b.py #1.需要匯入base import base #2.使用export裝飾器,裝飾要匯出的類或函式 @base.export class ClassB:pass #2.同樣的export可以匯出函式 @base.export def func_b(): print('func_b') |
2.使用了匯出功能的包要做什麼,這裡以package_a包為例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# ./package_a/__init__.py #1.匯入base import base #2.匯入將要匯出的子模組,需要具體模組名字的形式,from . import * 不可用 from . import class_a,class_b #3.用packet初始化__all__,這個可選,主要是看要不要支援 from 的用 * 匯入 __all__ = base.packet(__name__) #4.這個是可選的,因為如果用了__all__會影響from *。可以用export把__init__.py裡的項,加入__all__ @base.export def pafunc(): print('pafunc') |
5.總結
使用該解決方案可以歸納為兩點:
1.用@base.export標記要匯出的類或函式
2.在包__init__.py裡初始化__all__ = base.packet(__name__)
3.(說好的只有兩點呢?)其實第2點是可選的,不過最好加上。而在包的__init__.py裡匯入子模組才是真正的第2點。不然子模組不會被載入,也談不上匯出了。
最後,demo.py裡可以這麼寫,和預期的效果一樣:)
1 2 3 4 5 6 7 8 |
# ./demo.py from package_a import * a = ClassA() # 上面的例項沒有給出,不過假設有ClassA在class_a.py裡的 b = ClassB() func_b() |