今天我們來聊聊模塊和包
一.模塊
首先,我們先看一個老生常談的問題,什么是模塊,模塊就是一個包含了python定義和聲明的文件,文件名就是模塊的名字加上.py后綴,歡聚話說我們目前寫的所有的py文件都可以看成是一個模塊但是我們import加載的模塊一共分成四個通用類別:
1. 使用pyhton編寫的py文件
2. 已被變異為共享庫或者DLL或C或者C++的擴展
3. 包好一組模塊的包.
4. 使用c編寫並連接到python解釋器的內置模塊
為什么要使用模塊? 為了我們寫的代碼可以重用,不至於把所有的代碼都寫在一個文件內. 當項目規模比較小的時候,完全可以使用一個py搞定整個項目的開發,但是如果是一個非常龐大的項目. 此時就必須要把相關的功能進行分離,以便我們的日常維護,以及新項目的開發.
如何使用模塊? 我們已用過很多模塊了,導入模塊有兩種方式
1. import 模塊
2. from xxx import xxxx
二.import
首先我們先看import, 在使用import的時候, 我們先創建一個yitian.py. 在該文件中創建一些武林前輩和一些打斗場景, 代碼如下:
print("片頭曲. 啊! 啊~ 啊! 啊. 啊啊啊啊啊啊啊...") main_person_man = "張無忌" main_person_woman = "趙敏" low_person_man_one = "成昆" low_person_man_two = "周芷若" def fight_on_light_top(): print("光明頂大戰", main_person_man, "破壞了", low_person_man_one, "的大陰謀") def fight_in_shaolin(): print("少林寺大戰", main_person_man, "破壞了", low_person_man_two, "的大陰 謀")
接下來,金庸上場
import yitian print(yitian.main_person_man) # 使用模塊中定義好的名字 print(yitian.low_person_man_one) yitian.fight_in_shaolin() # 調用模塊中的函數 yitian.fight_on_light_top()
此時我們在金庸模塊中引入了yitian模塊.
在Python中模塊是不能夠重復導入的,當重復導入模塊時,系統會根據sys.modules來判斷該模塊是否已經導入了,如果已經導入,則不會重復導入
import sys print(sys.modules.keys()) # 查看導入的模塊. import yitian # 導入模塊. 此時會默認執行該模塊中的代碼 import yitian # 該模塊已經導入過了,不會重復執行代碼 import yitian import yitian import yitian import yitian
導入模塊的時候都做了些什么? 首先,在導入模塊的一瞬間,python解釋器會先通過sys.modules來判斷該模塊是否已經導入了該模塊,如果已經導入了則不再導入,如果該模塊還未導入過,則系統會做三件事.
1. 為導入的模塊創立新的名稱空間
2. 在新創建的名稱空間中運行該模塊中的代碼
3. 創建模塊的名字,並使用該名稱作為該模塊在當前模塊中引用的名字.
我們可以使用globals來查看模塊的名稱空間
print(globals()) 打印結果: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10bbcf438>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/sylar/PycharmProjects/oldboy/模塊/模塊/⾦庸.py', '__cached__': None, 'yitian': <module 'yitian' from '/Users/sylar/PycharmProjects/oldboy/模塊/模 塊/yitian.py'>, 'sys': <module 'sys' (built-in)>}
注意,由於模塊在導入的時候會創建其自己的名稱空間,所以我們在使用模塊中的變量的時候一般是不會產生沖突的.
import yitian main_person_man = "胡一菲" def fight_in_shaolin(): print(main_person_man, "大戰曾小賢") print(yitian.main_person_man) # 張無忌 print(main_person_man) # 胡一菲 yitian.fight_in_shaolin() # 倚天屠龍記中的 fight_in_shaolin() # 自己的
注意,在模塊中使用global,我們之前說global表示把全局的內容引入到局部,但是這個全局指的是py文件,換句話說,global指向的是模塊內部,並不會改變外部模塊的內容
模塊 yitian 中: print("片頭曲. 啊! 啊~ 啊! 啊. 啊啊啊啊啊啊啊...") main_person_man = "張無忌" main_person_woman = "趙敏" low_person_man_one = "成昆" low_person_man_two = "周芷若" def fight_on_light_top(): print("光明頂大戰", main_person_man, "破壞了", low_person_man_one, "的大陰謀") def fight_in_shaolin(): global low_person_man_two # 注意看, 此時的global是當前模塊. 並不會影響 其他模塊 low_person_man_two = "戰五渣" print("少林寺大戰", main_person_man, "破壞了", low_person_man_two, "的大陰謀") 調用方: import yitian low_person_man_two = "劉海柱" yitian.fight_in_shaolin() print(yitian.low_person_man_two) # 戰五渣 print(low_person_man_two) # 劉海柱. 並沒有改變當前模塊中的內容. 所以模塊內部的 global只是用於模塊內部
特別特別要注意,如果我們在不同的模塊中引入了同一個模塊,並且在某一個模塊中改變了被引入模塊中的全局變量,則其他模塊看到的值也跟着變,原因是python的模塊只會引入一次,大家共享同一個名稱空間
金庸: import yitian yitian.main_person_man = "滅絕師太" 金庸二號: import yitian import 金庸 print(yitian.main_person_man) # 滅絕師太.
上述問題出現的原因:
1. 大家共享同一個模塊的名稱空間.
2. 在金庸里改變了主⻆的名字
如何解決呢?
首先, 我們不能去改python,因為python的規則不是我們定的,只能想辦法不要改變主⻆的名字,但是在金庸里我就有這樣的需求,那此時就出現了,在金庸被執行的時候要執行的代碼,在金庸被別人導入的時候我們不想執行這些代碼,此時我們就要利用一下__name__這個內置變量了. 在Python中,每個模塊都有自己的__name__ ,但是這個__name__的值是不定的,當我們把一個模塊作為程序運行的入口時,此時該模塊的__name__是"__main__" , 而如果我們把模塊導入時,此時模塊內部的__name__就是該模塊自身的名字
金庸: print(__name__) # 此時如果運行該文件,則__name__是__main__ 金庸二號: import 金庸 #此時打印的結果是"金庸"
我們可以利用這個特性來控制模塊內哪些代碼是在被加載的時候就運行的,哪些是在模塊被別人導入的時候就要執行的,也可以屏蔽掉一些不希望別人導入就運行的代碼,尤其是測試代碼.
if __name__ == '__main__': yitian.main_person_man = "滅絕師太" # 此時, 只有從該模塊作為入口運行的時候才 會把main_person_man設置成滅絕師太 print("哇哈哈哈哈哈") # 只有運行該模塊才會打印,import的時候是不會執行這里的代 碼的
我們還可以對導入的模塊進行重新命名:
import yitian as yt # 導入yitian. 但是名字被重新命名成了yt. 就好比變量賦值一樣. a = 1 b = a yt.fight_in_shaolin() # 此時可以正常運行 # yitian.fight_in_shaolin() # 此時程序報錯. 因為引入的yitian被重命名成了yt print(globals()) {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x103209438>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/sylar/PycharmProjects/oldboy/模塊/模塊/金庸.py', '__cached__': None, 'yt': <module 'yitian' from '/Users/sylar/PycharmProjects/oldboy/模塊/模 塊/yitian.py'>}
一次可以引入多個模塊
import time, random, json, yitian
正確的導入模塊的順序:
1. 所有的模塊導入都要寫在最上面,這是最基本的
2. 先引入內置模塊
3. 再引入擴展模塊
4. 最后引入你自己定義的模塊
三. from xxx import xxx
第一大塊關於import就說這么多,接下來我們來看from xxx import xxx這種導入模塊的效果,在使用from的時候, python也會給我們的模塊創建名稱空間,這一點和import是一樣的,但是from xxx import xxx的時候,我們是把這個空間中的一些變量引入過來了,說白了就是部分導入,當一個模塊中的內容過多的時候,我們可以選擇性的導入要使用的內容.
from yitian import fight_in_shaolin fight_in_shaolin()
此時是可以正常運行的,但是我們省略了之前的模塊.函數() 直接函數()就可以執行了, 並且from語句也支持一行語句導入多個內容
from yitian import fight_in_shaolin, fight_on_light_top, main_person_man fight_in_shaolin() fight_on_light_top() print(main_person_man)
同樣支持as
from yitian import fight_in_shaolin, fight_on_light_top, main_person_man as big_lao fight_in_shaolin() fight_on_light_top() print(big_lao)
最后看一下from的坑,當我們從一個模塊中引入一個變量的時候,如果當前文件中出現了重名的變量時,會覆蓋掉模塊引入的那個變量.
from yitian import main_person_man main_person_man = "超級大滅絕" print(main_person_man)
所以,不要重名,切記,不要重名! 不僅僅是變量名不要重復,我們自己創建的py文件的名字不要和系統內置的模塊重名,否則引入的模塊都是python內置的模塊. 切記, 切記.
我們現在知道可以使用import和from xxx import xxx來導入一個模塊中的內容,那有一種特殊的寫法: from xxx import * 我們說此時是把模塊中的所有內容都導入, 注意,如果模塊中沒有寫出__all__ 則默認所有內容都導入,如果寫了__all__ 此時導入的內容就是在__all__列表中列出來的所有名字.
# haha.py __all__ = ["money", "chi"] money = 100 def chi(): print("我是吃") def he(): print("我是呵呵") # test.py from haha import * chi() print(money) # he() # 報錯
四.包
包是一種通過 '.模塊名'來組織python模塊名稱空間的方式,那什么樣的東西是包呢? 我們創建的每個文件夾都可以被稱之為包,但是我們要注意, 在python2中規定,包內必須存在__init__.py文件,創建包的目的不是為了運行, 而是被導入使用,包只是一種形式而已,包的本質就是一種模塊
為何要使用包? 包的本質就是一個文件夾, 那么文件夾唯一的功能就是將文件組織起來,隨着功能越寫越多, 我們無法將所有功能都放在一個文件中, 於是我們使用模塊去組織功能隨着模塊越來越多, 我們就需要用文件夾將模塊文件組織起來, 以此來提高程序的結構性和可維護性
首先, 我們先創建一些包,用來作為接下來的學習,包很好創建,只要是一個文件夾, 有__init__.py就可以
import os os.makedirs('glance/api') os.makedirs('glance/cmd') os.makedirs('glance/db') l = [] l.append(open('glance/__init__.py','w')) l.append(open('glance/api/__init__.py','w')) l.append(open('glance/api/policy.py','w')) l.append(open('glance/api/versions.py','w')) l.append(open('glance/cmd/__init__.py','w')) l.append(open('glance/cmd/manage.py','w')) l.append(open('glance/db/__init__.py','w')) l.append(open('glance/db/models.py','w')) map(lambda f:f.close() ,l)
創建好目錄結構
我們接下來給每個文件中添加一些方法:
#policy.py def get(): print('from policy.py') #versions.py def create_resource(conf): print('from version.py: ',conf) #manage.py def main(): print('from manage.py') #models.py def register_models(engine): print('from models.py: ',engine)
接下來我們在test中使用包中的內容,並且我們導入包的時候可以使用import或者from xxx import xxx這種形式.
首先, 我們看import
import glance.db.models glance.db.models.register_models('mysql')
沒問題, 很簡單, 我們還可以使用from xxx import xxx 來導入包內的模塊
from glance.api.policy import get get()
也很簡單, 但是要注意,from xxx import xxx這種形式, import后面不可以出現"點" 也就是說from a.b import c是ok的,但是 from a import b.c 是錯誤的
好了, 到目前為止, 簡單的包已經可以使用了,那包里的__init__.py是什么鬼? 其實不論我們使用哪種方式導入一個包, 只要是第一次導入包或者是包的任何其他部分, 都會先執行__init__.py文件,這個文件可以是空的,但也可以存放一些初始化的代碼. (隨意在glance中的__init__.py都可以進行測試)
那我們之前用的from xxx import *還可以用么? 可以,我們要在__init__.py文件中給出_all__來確定* 導入的內容.
print("我是glance的__init__.py文件. ") x = 10 def hehe(): print("我是呵呵") def haha(): print("我是哈哈") __all__ = ['x', "hehe"]
test.py
from glance import * print(x) # OK hehe() # OK haha() # 報錯. __all__里沒有這個鬼東西
接下來, 我們來看一下絕對導入和相對導入, 我們的最頂級包glance是寫給別人用的,然后再glance包內部也會有彼此之間互相導入的需求, 這時候就有絕對導入和相對導入兩種方式了.
1. 絕對導入: 以glance作為起始
2. 相對導入: 用. 或者..作為起始
例如, 我們在glance/api/version.py中使用glance/cmd/manage.py
# 在glance/api/version.py #絕對導入 from glance.cmd import manage manage.main() #相對導入 # 這種情形不可以在versions中啟動程序. # attempted relative import beyond top-level package from ..cmd import manage manage.main()
測試的時候要注意,python包路徑跟運行腳本所在的目錄有關系,說白了就是你運行的py文件所在的目錄,在python中不允許你運行的程序導包的時候超過當前包的范圍(相對導入). 如果使用絕對導入,沒有這個問題,換個說法,如果你在包內使用了相對導入,那在使用該包內信息的時候,只能在包外面導入.
接下來我們來看一個大坑,比如,我們想在policy中使用verson中的內容.
# 在policy.py import versions
如果我們程序的入口是policy.py 那此時程序是沒有任何問題的,但是如果我們在glance外面import了glance中的policy就會報錯 ,原因是如果在外面訪問policy的時候,sys.path中的路徑就是外面, 所以根本就不能直接找到versions模塊,所以一定會報錯:
ModuleNotFoundError: No module named 'versions'
在導包出錯的時候,一定要先看sys.path 看一下是否真的能獲取到包的信息.
最后, 我們看一下如何單獨導入一個包.
# 在test.py中 import glance
此時導入的glance什么都做不了,因為在glance中的__init__.py中並沒有關於子包的加載,此時我們需要在__init__.py中分別去引入子包中的內容.
1. 使用絕對路徑
2. 使用相對路徑
包的注意事項:
1. 關於包相關的導入語句也分為import和from xxx import xxx兩種, 但無論使用哪種,無論在什么位置, 在導入時都必須遵循一個原則: 凡是在導入時d帶點的,點左邊都必須是一個包,否則報錯,可以帶一連串的點, 比如a.b.c
2. import導入文件時,產生名稱空間中的名字來源於文件, import 包, 產生的名稱空間中的名字同樣來源於文件, 即包下的__init__,py, 導入包本質就是在導入該文件
3. 包A和包B下有同名模塊也不會沖突, 如A.a和B.a來自兩個名稱空間