Python筆記八(模塊和包)
一、模塊
1、什麽是模塊
常見的場景:一個模塊就是一個包含了python定義和聲明的文件,文件名就是模塊名字加上.py的後綴。
但其實import加載的模塊分為四個通用類別:
1 使用python編寫的代碼(.py文件)
2 已被編譯為共享庫或DLL的C或C++擴展
3 包好一組模塊的包
4 使用C編寫並鏈接到python解釋器的內置模塊
2、為何要使用模塊?
如果你退出python解釋器然後重新進入,那麽你之前定義的函數或者變量都將丟失,因此我們通常將程序寫到文件中以便永久保存下來,需要時就通過python test.py方式去執行,此時test.py被稱為腳本script。
隨著程序的發展,功能越來越多,為了方便管理,我們通常將程序分成一個個的文件,這樣做程序的結構更清晰,方便管理。這時我們不僅僅可以把這些文件當做腳本去執行,還可以把他們當做模塊來導入到其他的模塊中,實現了功能的重復利用。
3、如何使用模塊
1)import
示例文件:自定義模塊my_module.py,文件名my_module.py,模塊名my_module
# my_module.py print(‘from the my_module.py‘) money = 1000 def read1(): print(‘my_module->read1->money‘, money) def read2(): print(‘my_module->read2 calling read1‘) read1() def change(): global money money = 0
part1:
模塊可以包含可執行的語句和函數的定義,這些語句的目的是初始化模塊,它們只在模塊名第一次遇到導入import語句時才執行(import語句是可以在程序中的任意位置使用的,且針對同一個模塊很import多次,為了防止你重復導入,python的優化手段是:第一次導入後就將模塊名加載到內存了,後續的import語句僅是對已經加載大內存中的模塊對象增加了一次引用,不會重新執行模塊內的語句),如下 :
# demo.py import sys import my_module # 只在第一次導入時才執行my_module.py內代碼,此處的顯式效果是只打印一次‘from the my_module.py‘,當然其他的頂級代碼也都被執行了,只不過沒有顯示效果. import my_module import my_module import my_module print(sys.modules) ‘‘‘ 執行結果: from the my_module.py {‘builtins‘: <module ‘builtins‘ (built-in)>, ‘sys‘: <module ‘sys‘ (built-in)>, ‘_frozen_importlib‘: <module ‘_frozen_importlib‘ (frozen)>, ‘_imp‘: <module ‘_imp‘ (built-in)>, ‘_warnings‘: <module ‘_warnings‘ (built-in)>, ‘_thread‘: <module ‘_thread‘ (built-in)>, ‘_weakref‘: <module ‘_weakref‘ (built-in)>, ‘_frozen_importlib_external‘: <module ‘_frozen_importlib_external‘ (frozen)>, ‘_io‘: <module ‘io‘ (built-in)>, ‘marshal‘: <module ‘marshal‘ (built-in)>, ‘nt‘: <module ‘nt‘ (built-in)>, ‘winreg‘: <module ‘winreg‘ (built-in)>, ‘zipimport‘: <module ‘zipimport‘ (built-in)>, ‘encodings‘: <module ‘encodings‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\encodings\\__init__.py‘>, ‘codecs‘: <module ‘codecs‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\codecs.py‘>, ‘_codecs‘: <module ‘_codecs‘ (built-in)>, ‘encodings.aliases‘: <module ‘encodings.aliases‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\encodings\\aliases.py‘>, ‘encodings.utf_8‘: <module ‘encodings.utf_8‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\encodings\\utf_8.py‘>, ‘_signal‘: <module ‘_signal‘ (built-in)>, ‘__main__‘: <module ‘__main__‘ from ‘C:/Users/28163/PycharmProjects/python21期/day8/111.py‘>, ‘encodings.latin_1‘: <module ‘encodings.latin_1‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\encodings\\latin_1.py‘>, ‘io‘: <module ‘io‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\io.py‘>, ‘abc‘: <module ‘abc‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\abc.py‘>, ‘_weakrefset‘: <module ‘_weakrefset‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\_weakrefset.py‘>, ‘site‘: <module ‘site‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\site.py‘>, ‘os‘: <module ‘os‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\os.py‘>, ‘errno‘: <module ‘errno‘ (built-in)>, ‘stat‘: <module ‘stat‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\stat.py‘>, ‘_stat‘: <module ‘_stat‘ (built-in)>, ‘ntpath‘: <module ‘ntpath‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\ntpath.py‘>, ‘genericpath‘: <module ‘genericpath‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\genericpath.py‘>, ‘os.path‘: <module ‘ntpath‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\ntpath.py‘>, ‘_collections_abc‘: <module ‘_collections_abc‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\_collections_abc.py‘>, ‘_sitebuiltins‘: <module ‘_sitebuiltins‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\_sitebuiltins.py‘>, ‘_bootlocale‘: <module ‘_bootlocale‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\_bootlocale.py‘>, ‘_locale‘: <module ‘_locale‘ (built-in)>, ‘encodings.gbk‘: <module ‘encodings.gbk‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\encodings\\gbk.py‘>, ‘_codecs_cn‘: <module ‘_codecs_cn‘ (built-in)>, ‘_multibytecodec‘: <module ‘_multibytecodec‘ (built-in)>, ‘sysconfig‘: <module ‘sysconfig‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\sysconfig.py‘>, ‘encodings.cp437‘: <module ‘encodings.cp437‘ from ‘C:\\Users\\28163\\AppData\\Local\\Programs\\Python\\Python36\\lib\\encodings\\cp437.py‘>, ‘sitecustomize‘: <module ‘sitecustomize‘ from ‘C:\\Program Files\\JetBrains\\PyCharm 2017.3.4\\helpers\\pycharm_matplotlib_backend\\sitecustomize.py‘>, ‘my_module‘: <module ‘my_module‘ from ‘C:\\Users\\28163\\PycharmProjects\\python21期\\day8\\my_module.py‘>} ‘‘‘
【註意】我們可以從sys.modules中找到當前已經加載的模塊,sys.modules是一個字典,內部包含模塊名與模塊對象的映射,該字典決定了導入模塊時是否需要重新導入。
part2:
每個模塊都是一個獨立的名稱空間,定義在這個模塊中的函數,把這個模塊的名稱空間當做全局名稱空間,這樣我們在編寫自己的模塊時,就不用擔心我們定義在自己模塊中全局變量會在被導入時,與使用者的全局變量沖突。
# 測試一:money與my_module.money不沖突 # demo.py import my_module money = 10 print(my_module.money) ‘‘‘ 執行結果: from the my_module.py 1000 ‘‘‘ # 測試二:read1與my_module.read1不沖突 # demo.py import my_module def read1(): print(‘========‘) my_module.read1() ‘‘‘ 執行結果: from the my_module.py my_module->read1->money 1000 ‘‘‘ # 測試三:執行my_module.change()操作的全局變量money仍然是my_module中的 # demo.py import my_module money = 1 my_module.change() print(money) ‘‘‘ 執行結果: from the my_module.py 1 ‘‘‘
part3:
首次導入模塊my_module時會做三件事:
1.為源文件(my_module模塊)創建新的名稱空間,在my_module中定義的函數和方法若是使用到了global時訪問的就是這個名稱空間。
2.在新創建的命名空間中執行模塊中包含的代碼,見初始導入import my_module。
【註意】導入模塊時到底執行了什麽(事實上函數定義也是“被執行”的語句,模塊級別函數定義的執行將函數名放入模塊全局名稱空間表,用globals()可以查看)
3.創建名字my_module來引用該命名空間。
【註意】這個名字和變量名沒什麽區別,都是‘第一類的’,且使用my_module.名字的方式可以訪問my_module.py文件中定義的名字,my_module.名字與test.py中的名字來自兩個完全不同的地方。
part4:
為模塊起別名,語法如下:
import my_module as sm print(sm.money) ‘‘‘ 執行結果 from the my_module.py 1000 ‘‘‘
示範用法一:
有兩中sql模塊mysql和oracle,根據用戶的輸入,選擇不同的sql功能。
# mysql.py def sqlparse(): print(‘from mysql sqlparse‘) # oracle.py def sqlparse(): print(‘from oracle sqlparse‘) # test.py db_type = input(‘>>: ‘) if db_type == ‘mysql‘: import mysql as db elif db_type == ‘oracle‘: import oracle as db db.sqlparse()
示範用法二:
為已經導入的模塊起別名的方式對編寫可擴展的代碼很有用,假設有兩個模塊xmlreader.py和csvreader.py,它們都定義了函數read_data(filename):用來從文件中讀取一些數據,但采用不同的輸入格式。可以編寫代碼來選擇性地挑選讀取模塊。
if file_format == ‘xml‘: import xmlreader as reader elif file_format == ‘csv‘: import csvreader as reader data = reader.read_date(filename)
part5:
在一行導入多個模塊。
import sys, os, re
2)from ... import
part1:
對比import my_module,會將源文件的名稱空間‘my_module‘帶到當前名稱空間中,使用時必須是my_module.名字的方式。
而from 語句相當於import,也會創建新的名稱空間,但是將my_module中的名字直接導入到當前的名稱空間中,在當前名稱空間中,直接使用名字就可以了。
from my_module import read1,read2
這樣在當前位置直接使用read1和read2就好了,執行時,仍然以my_module.py文件全局名稱空間
#測試一:導入的函數read1,執行時仍然回到my_module.py中尋找全局變量money #demo.py from my_module import read1 money=100 read1() ‘‘‘ 執行結果: from the my_module.py spam->read1->money 1000 ‘‘‘ #測試二:導入的函數read2,執行時需要調用read1(),仍然回到my_module.py中找read1() #demo.py from my_module import read2 def read1(): print(‘==========‘) read2() ‘‘‘ 執行結果: from the my_module.py my_module->read2 calling read1 my_module->read1->money 1000 ‘‘‘
如果當前有重名read1或者read2,那麽會有覆蓋效果。
# 測試三:導入的函數read1,被當前位置定義的read1覆蓋掉了 # demo.py from my_module import read1 def read1(): print(‘==========‘) read1() ‘‘‘ 執行結果: from the my_module.py ========== ‘‘‘
需要特別強調的一點是:python中的變量賦值不是一種存儲操作,而只是一種綁定關系,如下:
from my_module import money, read1 money = 100 # 將當前位置的名字money綁定到了100 print(money) # 打印當前的名字 read1() # 讀取my_module.py中的名字money,仍然為1000 ‘‘‘ from the my_module.py 100 my_module->read1->money 1000 ‘‘‘
part2:
支持as與導入多行
#as用法 from my_module import read1 as read #導入多行 from my_module import (read1, read2, money)
part3:
from my_module import * 把my_module中所有的不是以下劃線(_)開頭的名字都導入到當前位置,大部分情況下我們的python程序不應該使用這種導入方式,因為*你不知道你導入什麽名字,很有可能會覆蓋掉你之前已經定義的名字。而且可讀性極其的差,在交互式環境中導入時沒有問題。
from my_module import * #將模塊my_module中所有的名字都導入到當前名稱空間 print(money) print(read1) print(read2) print(change) ‘‘‘ 執行結果: from the my_module.py 1000 <function read1 at 0x1012e8158> <function read2 at 0x1012e81e0> <function change at 0x1012e8268>
在my_module.py中新增一行
__all__=[‘money‘,‘read1‘] #這樣在另外一個文件中用from my_module import *就這能導入列表中規定的兩個名字
【註意】如果my_module.py中的名字前加_,即_money,則from my_module import *,則_money不能被導入
【思考】假如有兩個模塊a,b。我可不可以在a模塊中import b ,再在b模塊中import a?(不能)
part4:模塊的加載與修改
考慮到性能的原因,每個模塊只被導入一次,放入字典sys.modules中,如果你改變了模塊的內容,你必須重啟程序,python不支持重新加載或卸載之前導入的模塊。
有的同學可能會想到直接從sys.modules中刪除一個模塊不就可以卸載了嗎,註意了,你刪了sys.modules中的模塊對象仍然可能被其他程序的組件所引用,因而不會被清除。
特別的對於我們引用了這個模塊中的一個類,用這個類產生了很多對象,因而這些對象都有關於這個模塊的引用。
如果只是你想交互測試的一個模塊,使用 importlib.reload(), e.g. import importlib; importlib.reload(modulename),這只能用於測試環境。
#aa.py def func1(): print(‘func1‘) #test.py import time, importlib import aa time.sleep(20) importlib.reload(aa) aa.func1()
在20秒的等待時間裏,修改aa.py中func1的內容,等待test.py的結果。然後打開importlib註釋,重新測試。
3)把模塊當做腳本執行
我們可以通過模塊的全局變量__name__來查看模塊名:
if __name__ == ‘__main__‘:
if __name__ == ‘模塊名‘:
作用:用來控制.py文件在不同的應用場景下執行不同的邏輯
通俗的理解:
__name__ == ‘__main__‘
:假如你叫小明.py,在朋友眼中,你是小明(__name__ == ‘小明‘)
;在你自己眼中,你是你自己(__name__ == ‘__main__‘)
。
if __name__ == ‘__main__‘
的意思是:當.py文件被直接運行時,if __name__ == ‘__main__‘
之下的代碼塊將被運行;當.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))
4)模塊搜索路徑
python解釋器在啟動時會自動加載一些模塊,可以使用sys.modules查看。
在第一次導入某個模塊時(比如my_module),會先檢查該模塊是否已經被加載到內存中(當前執行文件的名稱空間對應的內存),如果有則直接引用
如果沒有,解釋器則會查找同名的內建模塊,如果還沒有找到就從sys.path給出的目錄列表中依次尋找my_module.py文件。
所以總結模塊的查找順序是:內存中已經加載的模塊->內置模塊->sys.path路徑中包含的模塊
【註意】我們自定義的模塊名不應該與系統內置模塊重名。
在初始化後,python程序可以修改sys.path,路徑放到前面的優先於標準庫被加載。
1 >>> import sys 2 >>> sys.path.append(‘/a/b/c/d‘) 3 >>> sys.path.insert(0,‘/x/y/z‘) #排在前的目錄,優先被搜索
註意:搜索時按照sys.path中從左到右的順序查找,位於前的優先被查找,sys.path中還可能包含.zip歸檔文件和.egg文件,python會把.zip歸檔文件當成一個目錄去處理。
#首先制作歸檔文件:zip module.zip foo.py bar.py import sys sys.path.append(‘module.zip‘) import foo,bar #也可以使用zip中目錄結構的具體位置 sys.path.append(‘module.zip/lib/python‘) #windows下的路徑不加r開頭,會語法錯誤 sys.path.insert(0,r‘C:\Users\Administrator\PycharmProjects\a‘)
至於.egg文件是由setuptools創建的包,這是按照第三方python庫和擴展時使用的一種常見格式,.egg文件實際上只是添加了額外元數據(如版本號,依賴項等)的.zip文件。
需要強調的一點是:只能從.zip文件中導入.py,.pyc等文件。使用C編寫的共享庫和擴展塊無法直接從.zip文件中加載(此時setuptools等打包系統有時能提供一種規避方法),且從.zip中加載文件不會創建.pyc或者.pyo文件,因此一定要事先創建他們,來避免加載模塊是性能下降。
#官網鏈接:https://docs.python.org/3/tutorial/modules.html#the-module-search-path 搜索路徑: 當一個命名為my_module的模塊被導入時 解釋器首先會從內建模塊中尋找該名字 找不到,則去sys.path中找該名字 sys.path從以下位置初始化 執行文件所在的當前目錄 PTYHONPATH(包含一系列目錄名,與shell變量PATH語法一樣) 依賴安裝時默認指定的 註意:在支持軟連接的文件系統中,執行腳本所在的目錄是在軟連接之後被計算的,換句話說,包含軟連接的目錄不會被添加到模塊的搜索路徑中 在初始化後,我們也可以在python程序中修改sys.path,執行文件所在的路徑默認是sys.path的第一個目錄,在所有標準庫路徑的前面。這意味著,當前目錄是優先於標準庫目錄的,需要強調的是:我們自定義的模塊名不要跟python標準庫的模塊名重復。官網解釋
5)編譯python文件
了提高加載模塊的速度,強調強調強調:提高的是加載速度而絕非運行速度。python解釋器會在__pycache__目錄中下緩存每個模塊編譯後的版本,格式為:module.version.pyc。通常會包含python的版本號。例如,在CPython3.3版本下,my_module.py模塊會被緩存成__pycache__/my_module.cpython-33.pyc。這種命名規範保證了編譯後的結果多版本共存。
Python檢查源文件的修改時間與編譯的版本進行對比,如果過期就需要重新編譯。這是完全自動的過程。並且編譯的模塊是平臺獨立的,所以相同的庫可以在不同的架構的系統之間共享,即pyc是一種跨平臺的字節碼,類似於JAVA或.NET,是由python虛擬機來執行的,但是pyc的內容跟python的版本相關,不同的版本編譯後的pyc文件不同,2.5編譯的pyc文件不能到3.5上執行,並且pyc文件是可以反編譯的,因而它的出現僅僅是用來提升模塊的加載速度的。
python解釋器在以下兩種情況下不檢測緩存:
1 如果是在命令行中被直接導入模塊,則按照這種方式,每次導入都會重新編譯,並且不會存儲編譯後的結果(python3.3以前的版本應該是這樣)
2 如果源文件不存在,那麽緩存的結果也不會被使用,如果想在沒有源文件的情況下來使用編譯後的結果,則編譯後的結果必須在源目錄下
【註意】
1.模塊名區分大小寫,foo.py與FOO.py代表的是兩個模塊
2.你可以使用-O或者-OO轉換python命令來減少編譯模塊的大小
3.在速度上從.pyc文件中讀指令來執行不會比從.py文件中讀指令執行更快,只有在模塊被加載時,.pyc文件才是更快的
4.只有使用import語句是才將文件自動編譯為.pyc文件,在命令行或標準輸入中指定運行腳本則不會生成這類文件,因而我們可以使用compieall模塊為一個目錄中的所有模塊創建.pyc文件
6、dir()函數
內建函數dir是用來查找模塊中定義的名字,返回一個有序字符串列表
import my_module print(dir(my_module)) ‘‘‘ 執行結果 from the my_module.py [‘__builtins__‘, ‘__cached__‘, ‘__doc__‘, ‘__file__‘, ‘__loader__‘, ‘__name__‘, ‘__package__‘, ‘__spec__‘, ‘change‘, ‘money‘, ‘read1‘, ‘read2‘] ‘‘‘
【註意】
1 如果沒有參數,dir()列舉出當前定義的名字;
2 dir()不會列舉出內建函數或者變量的名字,它們都被定義到了標準模塊builtin中,可以列舉出它們。
二、包
包是一種通過使用‘.模塊名’來組織python模塊名稱空間的方式。
1、import
2、from ... import
3、__init__.py文件
4、form glance.api import *
5、絕對導入和相對導入
6、單獨導入包
Python筆記八(模塊和包)