一 包介紹
隨着模塊數目的增多,把所有模塊不加區分地放到一起也是極不合理的,於是Python爲我們提供了一種把模塊組織到一起的方法,即創建一個包。包就是一個含有__init__.py文件的文件夾,文件夾內可以組織子模塊或子包,例如
pool/ #頂級包
├── __init__.py
├── futures #子包
│ ├── __init__.py
│ ├── process.py
│ └── thread.py
└── versions.py #子模塊
需要強調的是
#1. 在python3中,即使包下沒有__init__.py文件,import 包仍然不會報錯,而在python2中,包下一定要有該文件,否則import 包報錯
#2. 創建包的目的不是爲了運行,而是被導入使用,記住,包只是模塊的一種形式而已,包的本質就是一種模塊
插圖:惡搞圖15
接下來我們就以包pool爲例來介紹包的使用,包內各文件內容如下
# process.py
class ProcessPoolExecutor:
def __init__(self,max_workers):
self.max_workers=max_workers
def submit(self):
print('ProcessPool submit')
# thread.py
class ThreadPoolExecutor:
def __init__(self, max_workers):
self.max_workers = max_workers
def submit(self):
print('ThreadPool submit')
# versions.py
def check():
print('check versions’)
# __init__.py文件內容均爲空
插圖:惡搞圖16
二 包的使用
2.1 導入包與__init__.py
包屬於模塊的一種,因而包以及包內的模塊均是用來被導入使用的,而絕非被直接執行,首次導入包(如import pool)同樣會做三件事:
1、執行包下的__init__.py文件
2、產生一個新的名稱空間用於存放__init__.py執行過程中產生的名字
3、在當前執行文件所在的名稱空間中得到一個名字pool,該名字指向__init__.py的名稱空間,例如pool.xxx和pool.yyy中的xxx和yyy都是來自於pool下的__init__.py,也就是說導入包時並不會導入包下所有的子模塊與子包
import pool
pool.versions.check() #拋出異常AttributeError
pool.futures.process.ProcessPoolExecutor(3) #拋出異常AttributeError
pool.versions.check()要求pool下有名字versions,進而pool.versions下有名字check。pool.versions下已經有名字check了,所以問題出在pool下沒有名字versions,這就需要在pool下的__init__.py中導入模塊versions
插圖:惡搞圖17
強調
1.關於包相關的導入語句也分爲import和from ... import ...兩種,但是無論哪種,無論在什麼位置,在導入時都必須遵循一個原則:凡是在導入時帶點的,點的左邊都必須是一個包,否則非法。可以帶有一連串的點,如import 頂級包.子包.子模塊,但都必須遵循這個原則。但對於導入後,在使用時就沒有這種限制了,點的左邊可以是包,模塊,函數,類(它們都可以用點的方式調用自己的屬性)。
2、包A和包B下有同名模塊也不會衝突,如A.a與B.a來自倆個命名空間
3、import導入文件時,產生名稱空間中的名字來源於文件,import 包,產生的名稱空間的名字同樣來源於文件,即包下的__init__.py,導入包本質就是在導入該文件
2.2 絕對導入與相對導入
針對包內的模塊之間互相導入,導入的方式有兩種
1、絕對導入:以頂級包爲起始
#pool下的__init__.py
from pool import versions
2、相對導入:.代表當前文件所在的目錄,..代表當前目錄的上一級目錄,依此類推
#pool下的__init__.py
from . import versions
同理,針對pool.futures.process.ProcessPoolExecutor(3),則需要
#操作pool下的__init__.py,保證pool.futures
from . import futures #或from pool import futures
#操作futrues下的__init__.py,保證pool.futures.process
from . import process #或from pool.futures import process
在包內使用相對導入還可以跨目錄導入模塊,比如thread.py中想引用versions.py的名字check
import也能使用絕對導入,導入過程中同樣會依次執行包下的__init__.py,只是基於import導入的結果,使用時必須加上該前綴
插圖:惡搞圖18
例1:
import pool.futures #拿到名字pool.futures指向futures下的__init__.py
pool.futures.xxx #要求futures下的__init__.py中必須有名字xxx
例2:
import pool.futures.thread #拿到名字pool.futures.thread指向thread.py
thread_pool=pool.futures.thread.ThreadPoolExecutor(3)
thread_pool.submit()
相對導入只能用from module import symbol的形式,import ..versions語法是不對的,且symbol只能是一個明確的名字
from pool import futures.process #語法錯誤
from pool.futures import process #語法正確
針對包內部模塊之間的相互導入推薦使用相對導入,需要特別強調:
1、相對導入只能在包內部使用,用相對導入不同目錄下的模塊是非法的
2、無論是import還是from-import,但凡是在導入時帶點的,點的左邊必須是包,否則語法錯誤
插圖:惡搞圖20
2.3 from 包 import *
在使用包時同樣支持from pool.futures import * ,毫無疑問代表的是futures下__init__.py中所有的名字,通用是用變量__all__來控制\代表的意思
#futures下的__init__.py
__all__=['process','thread']
最後說明一點,包內部的目錄結構通常是包的開發者爲了方便自己管理和維護代碼而創建的,這種目錄結構對包的使用者往往是無用的,此時通過操作__init__.py可以“隱藏”包內部的目錄結構,降低使用難度,比如想要讓使用者直接使用
import pool
pool.check()
pool.ProcessPoolExecutor(3)
pool.ThreadPoolExecutor(3)
需要操作pool下的__init__.py
from .versions import check
from .futures.process import ProcessPoolExecutor
from .futures.thread import ThreadPoolExecutor
插圖:惡搞圖21