Python模塊和包 一、模塊 1. 什麼是模塊 常見的場景:一個模塊就是一個包含了Python定義和聲明的文件,文件名就是模塊名字加上.py的尾碼。 但其實import載入的模塊分為四個通用類別: 1 使用Python編寫的代碼(.py文件) 2 已被編譯為共用庫或DLL的C或C++擴展 3 包好 ...
Python模塊和包
一、模塊
什麼是模塊
常見的場景:一個模塊就是一個包含了Python定義和聲明的文件,文件名就是模塊名字加上.py的尾碼。
但其實import載入的模塊分為四個通用類別:
1 使用Python編寫的代碼(.py文件)
2 已被編譯為共用庫或DLL的C或C++擴展
3 包好一組模塊的包
4 使用C編寫並鏈接到Python解釋器的內置模塊
為什麼要使用模塊
如果你退出python解釋器然後重新進入,那麼你之前定義的函數或者變數都將丟失,因此我們通常將程式寫到文件中以便永久保存下來,需要時就通過python test.py方式去執行,此時test.py被稱為腳本script。
隨著程式的發展,功能越來越多,為了方便管理,我們通常將程式分成一個個的文件,這樣做程式的結構更清晰,方便管理。這時我們不僅僅可以把這些文件當做腳本去執行,還可以把他們當做模塊來導入到其他的模塊中,實現了功能的重覆利用。
如何使用模塊
import ...
# 自定義一個my_module.py文件(模塊) # 簡單的列印一句話 print("來導入我啊,來啊來啊來啊...") # 在同一個目錄下的另一個py文件中導入該模塊 import my_module print("小樣,看我把你弄過弄死你~") # 來導入我啊,來啊來啊來啊... # 小樣,看我把你弄過弄死你~
執行完之後發現執行了模塊中的語句,列印了兩句話,由此可以推出,導入模塊時會執行該模塊中的內容,那如果重覆導入模塊是不是會多次執行模塊中的內容呢?我們接下來試試:
# 自定義一個my_module.py文件(模塊) # 簡單的列印一句話 print("來導入我啊,來啊來啊來啊...") # 在同一個目錄下的另一個py文件中導入該模塊 import my_module print("小樣,看我把你弄過弄死你~") import my_module # 再次導入模塊 # 來導入我啊,來啊來啊來啊... # 小樣,看我把你弄過弄死你~ # 再次導入模塊之後發現結果並沒有發現改變。
模塊可以包含可執行的語句和函數的定義,這些語句的目的是初始化模塊,它們只在模塊名第一次遇到導入import語句時才執行(import語句是可以在程式中的任意位置使用的,且針對同一個模塊很import多次,為了防止你重覆導入,Python的優化手段是:第一次導入後就將模塊名載入到記憶體了,後續的import語句僅是對已經載入大記憶體中的模塊對象增加了一次引用,不會重新執行模塊內的語句)
總結:首次導入模塊my_module時會做三件事:
- 為源文件(my_module模塊)創建新的名稱空間,在my_module中定義的函數和方法若是使用到了global時訪問的就是這個名稱空間。
- 在新創建的命名空間中執行模塊中包含的代碼
- 創建名字my_module來引用該命名空間
from ... import ...
對比import my_module,會將源文件的名稱空間'my_module'帶到當前名稱空間中,使用時必須是my_module.名字的方式
而from 語句相當於import,也會創建新的名稱空間,但是將my_module中的名字直接導入到當前的名稱空間中,在當前名稱空間中,直接使用名字就可以了。
# my_module a = 10 print("來導入我啊,來啊來啊來啊...") # 執行文件 from my_module import a print("小樣,看我把你弄過弄死你~") print(a) # 來導入我啊,來啊來啊來啊... # 小樣,看我把你弄過弄死你~ # 10
如果當前有重名a,那麼會有覆蓋效果。
# my_module a = 10 print("來導入我啊,來啊來啊來啊...") # 執行文件 from my_module import a print("小樣,看我把你弄過弄死你~") a = 20 print(a) # 來導入我啊,來啊來啊來啊... # 小樣,看我把你弄過弄死你~ # 20
from ... import *
from my_module import * 把my_module中所有的不是以下劃線(_)開頭的名字都導入到當前位置,大部分情況下我們不應該使用這種導入方式,因為*你不知道你導入什麼名字,很有可能會覆蓋掉你之前已經定義的名字。而且可讀性極其的差,在互動式環境中導入時沒有問題。
# my_module a = 10 print("來導入我啊,來啊來啊來啊...") def func(): print("哈哈哈哈") # 執行文件 from my_module import * print(a) func() # 來導入我啊,來啊來啊來啊... # 10 # 哈哈哈哈
如果你想規定別人只能導入你模塊中指定的變數時,可以在模塊中使用__all__指定別人導入特定的變數
# my_module __all__ = ["func"] a = 10 print("來導入我啊,來啊來啊來啊...") def func(): print("哈哈哈哈") # 執行文件 from my_module import * func() # 來導入我啊,來啊來啊來啊... print(a) # NameError: name 'a' is not defined
模塊的別名
import my_module as m from my_module import func as f # 當對模塊進行重命名之後,指向模塊記憶體空間中的指針將會發生改變重新指向新命名的變數,即使用模塊只能用重命名之後的變數名 # 模塊也可以一行導入多個 import os, sys (不推薦) from my_module import a, func
模塊的迴圈引用問題
假如有兩個模塊a,b。我可不可以在a模塊中import b ,再在b模塊中import a?
# a模塊 import b def funca(): print("我是A") b.funcb() # b模塊 import a def funcb(): print("我是B") a.funca() # 1、當我在a模塊運行時 # AttributeError: module 'b' has no attribute 'funcb' # 2、當我在b模塊運行時 # AttributeError: module 'a' has no attribute 'funca'
為什麼相互調用時無論執行哪一個模塊都提示模塊中沒有可執行的變數呢?
分析:在上面第3步的時候,由於迴圈導入導致b.py並沒有載入完就接著回去執行了a.py,這個時候在a.py中執行b.funcb()就會告訴你沒有這個變數。同理如果限制性b.py,就找不到funca這個變數。
模塊的載入與修改
考慮到性能的原因,每個模塊只被導入一次,放入字典sys.modules中,如果你改變了模塊的內容,你必須重啟程式,python不支持重新載入或卸載之前導入的模塊。
如果只是你想交互測試的一個模塊,使用 importlib.reload(), e.g. import importlib; importlib.reload(modulename),這隻能用於測試環境。
把模塊當做腳本執行
我們可以通過模塊的全局變數__name__來查看模塊名:
當做腳本運行:
__name__ 等於'__main__'當做模塊導入:
__name__= 模塊名作用:用來控制.py文件在不同的應用場景下執行不同的邏輯
if __name__ == '__main__':def fib(n): a, b = 0, 1 while b < n: print(b, end=' ') a, b = b, a+b print() if __name__ == "__main__": print(__name__) num = input('num :') fib(int(num))
模塊搜索路徑
Python解釋器在啟動時會自動載入一些模塊,可以使用sys.modules查看
在第一次導入某個模塊時(比如my_module),會先檢查該模塊是否已經被載入到記憶體中(當前執行文件的名稱空間對應的記憶體),如果有則直接引用
如果沒有,解釋器則會查找同名的內建模塊,如果還沒有找到就從sys.path給出的目錄列表中依次尋找my_module.py文件。
所以總結模塊的查找順序是:記憶體中已經載入的模塊->內置模塊->sys.path路徑中包含的模塊
二、包
什麼是包
包是一種通過使用‘.模塊名’來組織Python模塊名稱空間的方式。
- 無論是import形式還是from...import形式,凡是在導入語句中(而不是在使用時)遇到帶點的,都要第一時間提高警覺:這是關於包才有的導入語法
- 包是目錄級的(文件夾級),文件夾是用來組成py文件(包的本質就是一個包含__init__.py文件的目錄)
- import導入文件時,產生名稱空間中的名字來源於文件,import 包,產生的名稱空間的名字同樣來源於文件,即包下的__init__.py,導入包本質就是在導入該文件
強調
- 在python3中,即使包下沒有__init__.py文件,import 包仍然不會報錯,而在python2中,包下一定要有該文件,否則import 包報錯
- 創建包的目的不是為了運行,而是被導入使用,記住,包只是模塊的一種形式而已,包即模塊
import
創建目錄代碼
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/models.py','w')) map(lambda f:f.close() ,l)
目錄結構
glance/ #Top-level package ├── __init__.py #Initialize the glance package ├── api #Subpackage for api │ ├── __init__.py │ ├── policy.py │ └── versions.py ├── cmd #Subpackage for cmd │ ├── __init__.py │ └── manage.py └── db #Subpackage for db ├── __init__.py └── models.py
文件內容
#文件內容 #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)
我們在與包glance同級別的文件中測試
import glance.db.models glance.db.models.register_models("mysql") # from models.py: mysql
from ... import ...
需要註意的是from後import導入的模塊,必須是明確的一個不能帶點,否則會有語法錯誤,如:from a import b.c是錯誤語法
我們在與包glance同級別的文件中測試
from glance.db import models from glance.api import policy models.register_models("mysql") # from models.py: mysql policy.get() # from policy.py
__init__.py文件
不管是哪種方式,只要是第一次導入包或者是包的任何其他部分,都會依次執行包下的__init__.py文件(我們可以在每個包的文件內都列印一行內容來驗證一下),這個文件可以為空,但是也可以存放一些初始化包的代碼。
from glance.api import *
此處是想從包api中導入所有,實際上該語句只會導入包api下__init__.py文件中定義的名字,我們可以在這個文件中定義__all___:
#在__init__.py中定義 x=10 def func(): print('from api.__init.py') __all__=['x','func','policy']
絕對導入和相對導入
我們的最頂級包glance是寫給別人用的,然後在glance包內部也會有彼此之間互相導入的需求,這時候就有絕對導入和相對導入兩種方式:
絕對導入:以glance作為起始
相對導入:用.或者..的方式最為起始(只能在一個包中使用,不能用於不同目錄內)
例如:我們在glance/api/version.py中想要導入glance/cmd/manage.py
# 在glance/api/version.py #絕對導入 from glance.cmd import manage manage.main() #相對導入 from ..cmd import manage manage.main()
特別需要註意的是:可以用import導入內置或者第三方模塊(已經在sys.path中),但是要絕對避免使用import來導入自定義包的子模塊(沒有在sys.path中),應該使用from... import ...的絕對或者相對導入,且包的相對導入只能用from的形式。
單獨導入包
單獨導入包名稱時不會導入包中所有包含的所有子模塊,如
# 在glace同級文件test下 import glance glance.api.policy.get() # AttributeError: module 'glance' has no attribute 'api'
解決辦法:
# 在glace包下的__init__文件中 from . import api # 在api包下的__init__文件中 from . import policy # 再次在glace同級文件test下執行 import glance glance.api.policy.get() # from policy.py