IO密集型程序、深拷貝和淺拷貝、模塊導(dǎo)入、with 語(yǔ)句
1.1 GIL
學(xué)習(xí)目標(biāo)
? 1. 能夠說(shuō)出 GIL 是什么
? 2. 能夠說(shuō)出 GIL 和 線程互斥鎖的區(qū)別
? 3. 能夠說(shuō)出什么是計(jì)算密集型程序
? 4. 能夠說(shuō)出什么是IO密集型程序
? 5. 能夠說(shuō)出 GIL 對(duì)計(jì)算密集型程序程序有什么影響
? 6. 能夠說(shuō)出 GIL 對(duì)IO密集型程序程序有什么影響
? 7. 能夠說(shuō)出如何改善 GIL 對(duì)程序產(chǎn)生的影響
--------------------------------------------------------------------------------
Python語(yǔ)言和GIL沒(méi)有半毛錢關(guān)系。僅僅是由于歷史原因在Cpython虛擬機(jī)(解釋器),難以移除GIL。
GIL:全局解釋器鎖。每個(gè)線程在執(zhí)行的過(guò)程都需要先獲取GIL,保證同一時(shí)刻只有一個(gè)線程可以執(zhí)行代碼。
線程釋放GIL鎖的情況: 在IO操作等可能會(huì)引起阻塞的system call之前,可以暫時(shí)釋放GIL,但在執(zhí)行完畢后,
必須重新獲取GIL Python 3.x使用計(jì)時(shí)器(執(zhí)行時(shí)間達(dá)到閾值后,當(dāng)前線程釋放GIL)或Python 2.x,tickets計(jì)數(shù)
達(dá)到100
1.1.1 GIL 概述
GIL ( Global Interperter Lock ) 稱作全局解釋器鎖。首先需要明確一點(diǎn),我們所講的 GIL 并不是 Python 語(yǔ)言的特性,它是在實(shí)現(xiàn) Python 解釋器時(shí)引用的一個(gè)概念。GIL 只在CPython 解釋器上存在。
1.1.2 回顧線程互斥鎖
由于多線程非同步競(jìng)爭(zhēng)共享數(shù)據(jù)資源時(shí),導(dǎo)致問(wèn)題產(chǎn)生。可以使用線程互斥鎖來(lái)解決。
通過(guò)回顧互斥鎖,我們知道使用鎖的目的是為了解決多線程競(jìng)爭(zhēng)共享資源的問(wèn)題。
1.1.3 互斥鎖和 GIL 的區(qū)別
上面多線程程序的執(zhí)行流程如下圖:
由上圖分析得到結(jié)論如下:
? 1. Python 解釋器也是一個(gè)應(yīng)用程序
? 2. GIL 只在 CPython 解釋器中存在
? 3. 線程互斥鎖是 Python 代碼層面的鎖,解決 Python 程序中多線程共享資源的問(wèn)題
? 4. GIL 是 Python 解釋層面的鎖,解決解釋器中多個(gè)線程的競(jìng)爭(zhēng)資源問(wèn)題。
1.1.4 GIL 對(duì)程序的影響
下面對(duì)上圖做進(jìn)一步的分析,從CPU的角度來(lái)分析 GIL 鎖對(duì)程序產(chǎn)生什么影響
計(jì)算密集型程序
通過(guò)分析得到結(jié)論如下:
? 1. 在 Python 中同一時(shí)刻有且只有一個(gè)線程會(huì)執(zhí)行。
? 2. Python 中的多線程由于 GIL 鎖的存在無(wú)法利用多核 CPU
? 3. Python 中的多線程不適合計(jì)算密集型的程序。
? 4. 如果程序需要大量的計(jì)算,利用多核CPU資源,可以使用多進(jìn)程來(lái)解決
IO密集型程序(IO, input寫入,output讀取)
大部分的程序在運(yùn)行時(shí),都需要大量IO操作,比如網(wǎng)絡(luò)數(shù)據(jù)的收發(fā),大文件的讀寫,(磁盤讀取,web服務(wù))這樣的程序稱為IO密集型程序。
Python 3.x使用計(jì)時(shí)器(執(zhí)行時(shí)間達(dá)到閾值后,當(dāng)前線程釋放GIL)或Python 2.x,tickets計(jì)數(shù)達(dá)到100,這樣對(duì)CPU密集型程序更加友好
IO密集型程序在運(yùn)行時(shí),需要大量的時(shí)間進(jìn)行等待,那么這時(shí)如果IO操作不完成,程序就無(wú)法執(zhí)行后面的操作,導(dǎo)致CPU空閑。
在Python解釋器執(zhí)行程序時(shí),由于GIL的存在,導(dǎo)致同一時(shí)刻只能有一個(gè)線程執(zhí)行,那么程序執(zhí)行效率非常低,那么在程序進(jìn)行IO讀取時(shí),CPU實(shí)際并沒(méi)有做任何工作,為了提高CPU的使用率,那么Python解釋在程序執(zhí)行IO等待時(shí),會(huì)釋放 GIL 鎖,讓其它線程執(zhí)行,提高Python程序的執(zhí)行效率。
1.1.5 如何改善 GIL 產(chǎn)生的問(wèn)題
因?yàn)?GIL 鎖是解釋器層面的鎖,無(wú)法去除 GIL 鎖在執(zhí)行程序時(shí)帶來(lái)的問(wèn)題。只能去改善。
? 1. 更換更高版本的解釋器,比如3.6,從3.2版本開(kāi)始,Python對(duì)解釋做了優(yōu)化,但并不理想
? 2. 更換解釋器,比如 Jython,但是由于比較小眾,支持的模塊較少,導(dǎo)致開(kāi)發(fā)效率降低
? 3. Python為了解決程序使用多核的問(wèn)題,使用多進(jìn)程替代多線程
1.1.6 小結(jié)
? 1. GIL ( Global Interpreter Lock ) 全局解釋器鎖。
? 2. GIL 不是 Python 語(yǔ)言的特性,是CPython中的一個(gè)概念。
? 3. Python 解釋器也是一個(gè)應(yīng)用程序
? 4. 線程互斥鎖是 Python 代碼層面的鎖,解決 Python 程序中多線程共享資源的問(wèn)題
? 5. GIL 是 Python 解釋器層面的鎖,解決解釋器中多個(gè)線程的競(jìng)爭(zhēng)資源問(wèn)題。
? 6. 由于 GIL 的存在, Python程序中同一時(shí)刻有且只有一個(gè)線程會(huì)執(zhí)行。
? 7. Python 中的多線程由于 GIL 鎖的存在無(wú)法利用多核 CPU
? 8. Python 中的多線程不適合計(jì)算密集型的程序。
? 9. GIL 鎖在遇到IO等待時(shí),會(huì)釋放 GIL 鎖,可以提高Python中IO密集型程序的效率
? 10. 如果程序需要大量的計(jì)算,利用多核CPU資源,可以使用多進(jìn)程來(lái)解決
1.2 深拷貝和淺拷貝
學(xué)習(xí)目標(biāo)
? 1. 能夠說(shuō)出什么是對(duì)象引用
? 2. 能夠說(shuō)出什么是不可變對(duì)象
? 3. 能夠說(shuō)出什么是可變對(duì)象
? 4. 能夠說(shuō)出什么是引用賦值
? 5. 能夠說(shuō)出什么是淺拷貝
? 6. 能夠說(shuō)出什么是深拷貝
? 7. 能夠說(shuō)出淺拷貝對(duì)可變對(duì)象有什么影響
? 8. 能夠說(shuō)出深拷貝對(duì)可變對(duì)象有什么影響
? 9. 能夠說(shuō)出淺拷貝的幾種實(shí)現(xiàn)方式
? 10. 能夠說(shuō)出淺拷貝的優(yōu)點(diǎn)
--------------------------------------------------------------------------------
1.2.1 深拷貝和淺拷貝概述
在程序開(kāi)發(fā)過(guò)程中,經(jīng)常涉及到數(shù)據(jù)的傳遞,在數(shù)據(jù)傳遞使用過(guò)程中,可能會(huì)發(fā)生數(shù)據(jù)被修改的問(wèn)題。為了防止數(shù)據(jù)被修改,就需要在傳遞一個(gè)副本,即使副本被修改,也不會(huì)影響原數(shù)據(jù)的使用。為了生成這個(gè)副本,就產(chǎn)生了拷貝。
1.2.2 技術(shù)點(diǎn)回顧
? 一切皆對(duì)象
在 Python 中,所有的數(shù)據(jù)都被當(dāng)成對(duì)象來(lái)處理,無(wú)論是數(shù)字,字符串,還是函數(shù),甚至是模塊。
? 不可變對(duì)象
在 Python 中,int, str, tuple 等類型的數(shù)據(jù)都是不可變對(duì)象,不可變對(duì)象的特性是數(shù)字不可被修改。
? 可變對(duì)象
在 Python 中,list, set,dict 等類型的數(shù)據(jù)都是可變對(duì)象,相對(duì)于不可變對(duì)象而言,可變對(duì)象的數(shù)據(jù)可以被修改
? 引用
在 Python 程序中,每個(gè)對(duì)象都會(huì)在內(nèi)存中申請(qǐng)開(kāi)辟一塊空間來(lái)保存該對(duì)象,該對(duì)象在內(nèi)存中所在位置的地址被稱為引用
在開(kāi)發(fā)程序時(shí),所定義的變量名實(shí)際就對(duì)象的地址引用
引用實(shí)際就是內(nèi)存中的一個(gè)數(shù)字地址編號(hào),在使用對(duì)象時(shí),只要知道這個(gè)對(duì)象的地址,就可以操作這個(gè)對(duì)象,但是因?yàn)檫@個(gè)數(shù)字地址不方便在開(kāi)發(fā)時(shí)使用和記憶,所以使用變量名的形式來(lái)代替對(duì)象的數(shù)字地址。 在 Python 中,變量就是地址的一種表示形式,并不開(kāi)辟開(kāi)辟存儲(chǔ)空間。
就像 IP 地址,在訪問(wèn)網(wǎng)站時(shí),實(shí)際都是通過(guò) IP 地址來(lái)確定主機(jī),而 IP 地址不方便記憶,所以使用域名來(lái)代替 IP 地址,在使用域名訪問(wèn)網(wǎng)站時(shí),域名被解析成 IP 地址來(lái)使用。
# 使用 id() 函數(shù)可以查看對(duì)象的引用
1.2.2 引用賦值
賦值的本質(zhì)就是讓多個(gè)變量同時(shí)引用同一個(gè)對(duì)象的地址。? 那么在對(duì)數(shù)據(jù)修改時(shí)會(huì)發(fā)生什么問(wèn)題呢?
? 不可變對(duì)象的引用賦值
在不可變對(duì)象賦值時(shí),不可變對(duì)象不會(huì)被修改,而是會(huì)新開(kāi)辟一個(gè)空間
程序原理圖:
? 可變對(duì)象的引用賦值
在可變對(duì)象中,保存的并不真正的對(duì)象數(shù)據(jù),而對(duì)象的引用。 當(dāng)對(duì)可變對(duì)象進(jìn)行修改時(shí),只是將可變對(duì)象中保存的引用進(jìn)行更
程序原理圖:
函數(shù)在傳遞參數(shù)時(shí),實(shí)際上就是實(shí)參對(duì)形參的賦值,如果實(shí)參是可變對(duì)象,那么就可以在函數(shù)的內(nèi)部修改傳入的數(shù)據(jù)。
1.2.3 淺拷貝
為了解決函數(shù)傳遞后被修改的問(wèn)題,就需要拷貝一份副本,將副本傳遞給函數(shù)使用,就算是副本被修改,也不會(huì)影響原始數(shù)據(jù) 。
拷貝對(duì)象需要導(dǎo)入 copy 模塊。
import copy
使用 copy 模塊中的 copy 方法就可以拷貝對(duì)象了。
? 不可變對(duì)象的拷貝
因?yàn)椴豢勺儗?duì)象只有在修改時(shí)才會(huì)開(kāi)辟新空間,所以拷貝也相當(dāng)于讓多個(gè)引用同時(shí)引用了一個(gè)數(shù)據(jù),所以不可變對(duì)象的淺拷貝和賦值沒(méi)有區(qū)別
? 可變對(duì)象的拷貝
程序原理圖:
程序原理圖:
通過(guò)上圖發(fā)現(xiàn),copy() 函數(shù)在拷貝對(duì)象時(shí),只是將指定對(duì)象中的所有引用拷貝了一份,如果這些引用當(dāng)中包含了一個(gè)可變對(duì)象的話,那么數(shù)據(jù)還是會(huì)被改變。 這種拷貝方式,稱為淺拷貝。
1.2.4 深拷貝
相對(duì)于淺拷貝只拷貝頂層的引用外,copy模塊還提供了另外一個(gè)拷貝方法 deepcopy() 函數(shù),這個(gè)函數(shù)可以逐層進(jìn)行拷貝引用,直到所有的引用都是不可變引用為止。
程序原理圖:
但是大多數(shù)的情況下,我們并不希望這樣,反而希望數(shù)據(jù)可以被修改,以達(dá)在函數(shù)間共享數(shù)據(jù)的目的。
1.2.5 淺拷貝的幾種方式
? copy 模塊的 copy 方法
import copy
a = [1, 2]
b = [3, 4, a]
c = copy.copy(b)
? 對(duì)象本身的 copy 方法
a = [1, 2]
b = [3, 4, a]
c = b.copy()
? 工廠方法
? ? ? 通過(guò)類創(chuàng)建對(duì)象
a = [1, 2]
b = [3, 4, a]
c = list(b)
? 切片
a = [1, 2]
b = [3, 4, a]
c = b[0:]
1.2.6 淺拷貝的優(yōu)勢(shì)
? 時(shí)間角度,淺拷貝花費(fèi)時(shí)間更少
? 空間角度,淺拷貝花費(fèi)內(nèi)存更少
? 效率角度,淺拷貝只拷貝頂層數(shù)據(jù),一般情況下比深拷貝效率高。
1.2.7 小結(jié)
? 不可變對(duì)象在賦值時(shí)會(huì)開(kāi)辟新空間
? 可變對(duì)象在賦值時(shí),修改一個(gè)的值,另一個(gè)也會(huì)發(fā)生改變
? 深淺拷貝對(duì)不可變對(duì)象拷貝時(shí),不開(kāi)辟新空間,相當(dāng)于賦值操作
? 淺拷貝在拷貝時(shí),只拷貝第一層中的引用,如果元素是可變對(duì)象,并且被修改,那么拷貝的對(duì)象也會(huì)發(fā)生變化
? 深拷貝在拷貝時(shí),會(huì)逐層進(jìn)行拷貝,直到所有的引用都是不可變對(duì)象為止。
? Python 中有多種方式實(shí)現(xiàn)淺拷貝,copy模塊的 copy 函數(shù) ,對(duì)象的 copy 函數(shù) ,工廠方法,切片等。
? 大多數(shù)情況下,編寫程序時(shí),都是使用淺拷貝,除非有特定的需求
? 淺拷貝的優(yōu)點(diǎn):拷貝速度快,占用空間少,拷貝效率高
1.3 模塊導(dǎo)入
學(xué)習(xí)目標(biāo)
? 1. 能夠說(shuō)出模塊在加載時(shí)的搜索過(guò)程
? 2. 能夠說(shuō)出如何添加模塊搜索路徑
? 3. 能夠說(shuō)出如何動(dòng)態(tài)加載模塊
? 4. 能夠說(shuō)出 import 和 from-import 兩種導(dǎo)入模塊的區(qū)別
? 5. 能夠說(shuō)出循環(huán)導(dǎo)入會(huì)出現(xiàn)什么問(wèn)題
--------------------------------------------------------------------------------
1.3.1 模塊導(dǎo)入概述
在 Python 開(kāi)發(fā)過(guò)程中,需要使用大量的系統(tǒng)模塊,第三方模塊,自定義模塊。這些模塊以 Python 文件的形式進(jìn)行組織。
當(dāng)需要使用模塊中提供的功能時(shí),只需要將模塊導(dǎo)入到當(dāng)前文件中即可。
如果有多個(gè)模塊可以將這些模塊放在一個(gè)文件中,并創(chuàng)建一個(gè) __init__.py 的文件,這個(gè)文件夾稱為 package。
1.3.3 模塊導(dǎo)入方式
現(xiàn)有如圖中的模塊組織方式
? import module
import BB
BB.show()
? import package.module
import MyModules.AA
MyModules.AA.show()
? from module import 成員
from BB import show
show()
? from package import module
from MyModules import AA
AA.show()
? from package.module import 成員
from MyModules.AA import show
show()
1.3.4 模塊別名 as
在導(dǎo)入模塊時(shí),特別是在從包中導(dǎo)入模塊時(shí),如果包名和模塊名都特別長(zhǎng),在使用時(shí),非常不方便。
可以使用 as 給 模塊起一個(gè)別名,編寫代碼時(shí)就可以直接使用別名代替。
import MyModules.AA as MMAA
MMAA.show()
1.3.5 模塊搜索路徑
在導(dǎo)入模塊時(shí),程序是依據(jù)什么找到這些模塊的呢?
在 sys 模塊中有一個(gè) path 變量,記錄了程序在導(dǎo)入模塊時(shí)的查找位置,返回的是一個(gè)列表類型。
import sys
path_list = sys.path
print(path_list)
模塊的搜索順序是:
? 當(dāng)前程序所在目錄
? 當(dāng)前程序根目錄
? PYTHONPATH
? 標(biāo)準(zhǔn)庫(kù)目錄
? 第三方庫(kù)目錄site-packages目錄
如果導(dǎo)入的模塊不在 path 保存的路徑中,那么導(dǎo)入模塊時(shí)就會(huì)報(bào)錯(cuò)
ModuleNotFoundError: No module named 'CC'
可以在程序中向 path 變量中添加模塊所在的路徑。
假定在路徑 /Users/KG/Desktop 下有一個(gè) CC.py 模塊
在程序中將 /Users/KG/Desktop 路徑加到 path中去
import sys
sys.path.append('/Users/KG/Desktop')
print(sys.path)
import CC
CC.show()
程序運(yùn)行結(jié)果:
['/Users/KG/PycharmProjects/TestDay12', '/Users/KG/PycharmProjects/TestDay12', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages', '/Users/KG/Desktop']
CC-Show Run ...
path 變量本質(zhì)就是一個(gè)列表,使用 append() 方法可以將新路徑加入到 path 中
也可以使用 insert 方式添加
1.3.6 重新加載模塊
模塊導(dǎo)入成功后,在使用模塊過(guò)程中,如果被導(dǎo)入的模塊對(duì)數(shù)據(jù)進(jìn)行了修改,那么正在使用該模塊的程序并不會(huì)修改。
因?yàn)镻yCharm不能保持程序運(yùn)行,所以使用命令行驗(yàn)證
就算重新導(dǎo)入修改后的模塊也不行
因?yàn)?程序在導(dǎo)入模塊時(shí),會(huì)將模塊創(chuàng)建一個(gè)對(duì)象保存到一個(gè)字典中,如果之前導(dǎo)入過(guò)一次,再次導(dǎo)入時(shí)就不會(huì)再創(chuàng)建這個(gè)對(duì)象。(后面有圖示)
可以通過(guò) sys 模塊下的 modules 屬性來(lái)查看一個(gè)文件中導(dǎo)入過(guò)的模塊。sys.modules 返回一個(gè)字典
如果需要使用修改后的數(shù)據(jù) ,需要重新加載模塊。
重新加載模塊需要使用 imp 模塊下的 reload 函數(shù)(已經(jīng)被廢棄了)
重新加載模塊需要使用 importlib 模塊下的 reload 函數(shù)
from importlib import reload
1.3.7 import 和 from-import 導(dǎo)入的使用區(qū)別
? import 方式
import 方式導(dǎo)入時(shí),只是在當(dāng)前文件中建立了一個(gè)模塊的引用,通過(guò)模塊的引用來(lái)使用模塊內(nèi)的數(shù)據(jù) 。
使用這種方式導(dǎo)入時(shí),訪問(wèn)控制權(quán)限對(duì)文件內(nèi)級(jí)別的數(shù)據(jù)不起作用,通過(guò)模塊名都可以進(jìn)行訪問(wèn)。
相當(dāng)于將一個(gè)模塊中所有的內(nèi)容都導(dǎo)入到當(dāng)前文件中使用。
AA.py
x = 1
_y = 2
__z = 3
BB.py
import AA
print(AA.x)
print(AA._y)
print(AA.__z) # 雖然不提示,但是依然可以用
程序運(yùn)行結(jié)果
1
2
3
可以將 import 導(dǎo)入方式理解成淺拷貝,只是拷貝了模塊的一個(gè)引用。通過(guò)引用可以使用所有的數(shù)據(jù) 。
? from-import 方式
from-import 方式在導(dǎo)入數(shù)據(jù)時(shí),會(huì)將導(dǎo)入模塊中數(shù)據(jù)復(fù)制一份到當(dāng)前文件中,所以可以直接使用模塊中的變量,函數(shù),類等內(nèi)容。
在使用 from-import 方式導(dǎo)入時(shí),文件內(nèi)私有屬性 _xxx 形式的數(shù)據(jù)不會(huì)被導(dǎo)入。
在使用 from-import 方式導(dǎo)入時(shí),如果模塊內(nèi)和當(dāng)前文件中有標(biāo)識(shí)符命名重名,會(huì)引用命名沖突,當(dāng)前文件中的內(nèi)容會(huì)覆蓋模塊的數(shù)據(jù)
BB.py
from AA import *
print(x)
# print(_y) # 禁止導(dǎo)入,不能使用
# print(_AA.__z) #因?yàn)樵谖募?nèi),也不能導(dǎo)入
# 定義了一個(gè)和AA模塊中的x同名的函數(shù)
# 在當(dāng)前文件中 x 就不在代表 x 變量了,而是函數(shù)了
def x():
? ? print('x is function now!')
print(x)
x()
程序運(yùn)行結(jié)果:
1
<function x at 0x101f62e18>
x is function now!
from-import 方式可以理解成深拷貝,被導(dǎo)入模塊中的數(shù)據(jù)被拷貝了一份放在當(dāng)前文件中。
__all__ 魔法變量 在 Python 中還提供種方式來(lái)隱藏或公開(kāi)數(shù)據(jù) ,就是使用 __all__
__all__ 本質(zhì)是一個(gè)列表,在列表中以字符串形式加入要公開(kāi)的數(shù)據(jù)
在使用 from-import 導(dǎo)入模塊時(shí),如果模塊中存在這個(gè)變量,那么就按這個(gè)變量里的內(nèi)容進(jìn)行導(dǎo)入,沒(méi)有包含的不導(dǎo)入。 AA.py
__all__ = ['_y','__z']
x = 1
_y = 2
__z = 3
BB.py
from AA import *
print(_y)? ? #雖然是私有的,但是在 __all__中公開(kāi)了就可以導(dǎo)入
print(__z)
# print(x)? ? # 沒(méi)有公開(kāi),不能使用
# show()
程序運(yùn)行結(jié)果
2
3
小結(jié)
從使用便利的角度,使用from-import
從命名沖突的角度,使用 import
1.3.8 循環(huán)導(dǎo)入問(wèn)題
在開(kāi)發(fā)過(guò)程中,可能會(huì)遇到這種情況。兩個(gè)模塊相互之間進(jìn)行導(dǎo)入。這樣的話,會(huì)造成程序出現(xiàn)死循環(huán)。程序運(yùn)行時(shí)就會(huì)報(bào)錯(cuò)。
AA.py
from BB import *
def ashow():
? ? print('A - show')
bshow()
BB.py
from AA import *
def bshow():
? ? print('A - show')
ashow()
程序運(yùn)行結(jié)果:
NameError: name 'ashow' is not defined
這是因?yàn)槟K在導(dǎo)入時(shí)要經(jīng)過(guò)這么幾步:
? 在sys.modules 中去搜索導(dǎo)入的模塊對(duì)象
? 如果沒(méi)有找到就創(chuàng)建一個(gè)空模塊并加入到sys.modules中,如果找到就不在創(chuàng)建
? 然后讀取模塊中的數(shù)據(jù)對(duì)空模塊初始化
? 對(duì)存在的模塊直接建立引用在當(dāng)前文件中使用
循環(huán)引用出錯(cuò)的原因是創(chuàng)建完空模塊后,對(duì)模塊初始化時(shí),又遇到了另外一個(gè)模塊的導(dǎo)入。這時(shí)重復(fù)執(zhí)行創(chuàng)建空模塊初始化操作。 但是在第二個(gè)模塊中發(fā)現(xiàn)又是在導(dǎo)入模塊。但是這時(shí)會(huì)發(fā)現(xiàn),這個(gè)模塊以第一次的時(shí)候已經(jīng)創(chuàng)建過(guò)了,就不在創(chuàng)建。但是模塊并未初始化成功。 在使用時(shí)對(duì)一個(gè)空的模塊內(nèi)容進(jìn)行調(diào)用。最后報(bào)錯(cuò)。
可以通過(guò)下圖來(lái)理解循環(huán)導(dǎo)入出錯(cuò)的過(guò)程
下面的代碼嘗試去解決循環(huán)引用問(wèn)題: AA.py
def ashow():
? ? print('A - show')
import BB
BB.bshow()
BB.py
def bshow():
? ? print('B - show')
import AA
AA.ashow()
程序輸出結(jié)果:
A - show
B - show
A - show
結(jié)果還是有問(wèn)題
代碼中使用 import 替代了 from-impot 。 程序在執(zhí)行 BB.py ,由于要?jiǎng)?chuàng)建兩次相互導(dǎo)入時(shí)的模塊到 sys.modules 中,在初始化模塊過(guò)程中會(huì)執(zhí)行模塊內(nèi)的語(yǔ)句,所以輸出結(jié)果 多了前兩次。
循環(huán)導(dǎo)入不是語(yǔ)法知識(shí),也不止在 Python 中出現(xiàn)。這是在程序設(shè)計(jì)時(shí)的邏輯出現(xiàn)了問(wèn)題。 不要想出現(xiàn)邏輯錯(cuò)誤的時(shí)候怎么修改。而是要從根本上去避免不能出現(xiàn)這種設(shè)計(jì)邏輯。就像函數(shù)調(diào)用死循環(huán)一樣。 切記切記!!!
1.3.9 總結(jié)
? 在Python中,一個(gè)文件就是一個(gè)模塊
? 使用模塊時(shí),可以使用 import 或 from-import 來(lái)將模塊導(dǎo)入
? 導(dǎo)入模塊時(shí),程序到 sys.path 路徑中去搜索,如果路徑中沒(méi)有指定的模塊會(huì)報(bào)錯(cuò)
? 可以向 sys.path 中去添加搜索路徑
? 模塊導(dǎo)入后,在執(zhí)行過(guò)程中是不可更新的,如果模塊發(fā)生了變化,需要使用 imp 模塊中的reload 函數(shù)重新導(dǎo)入
? import 導(dǎo)入類似淺拷貝,使用模塊的引用操作模塊中的數(shù)據(jù)
? from-import 導(dǎo)入類似深拷貝,相當(dāng)于復(fù)制了一份模塊中的數(shù)據(jù)到當(dāng)前文件中,可能會(huì)命名沖突
? 循環(huán)導(dǎo)入模塊會(huì)出錯(cuò),這不是語(yǔ)法,是思想邏輯錯(cuò)誤,不要想著怎么改,要想怎么避免發(fā)生
1.4 with 語(yǔ)句
學(xué)習(xí)目標(biāo)
? 1. 能夠說(shuō)出with的執(zhí)行過(guò)程
? 2. 能夠說(shuō)出with的作用
? 3. 能夠說(shuō)出為什么使用 with
--------------------------------------------------------------------------------
1.4.1 with 概述
with 語(yǔ)句是 Pyhton 提供的一種簡(jiǎn)化語(yǔ)法,with 語(yǔ)句是從 Python 2.5 開(kāi)始引入的一種與異常處理相關(guān)的功能。
with 語(yǔ)句適用于對(duì)資源進(jìn)行訪問(wèn)的場(chǎng)合,確保不管使用過(guò)程中是否發(fā)生異常都會(huì)執(zhí)行必要的“清理”操作,釋放資源。
比如文件使用后自動(dòng)關(guān)閉、數(shù)據(jù)庫(kù)的打開(kāi)和自動(dòng)關(guān)閉等。
1.4.3 with 語(yǔ)句的使用
with open('test', 'w') as f:
? ? f.write('Python好')
通過(guò) with 語(yǔ)句在編寫代碼時(shí),會(huì)使代碼變得更加簡(jiǎn)潔。
在編寫代碼時(shí),不用再顯示的去關(guān)閉文件。
1.4.4 with 語(yǔ)句的執(zhí)行過(guò)程
? 在執(zhí)行 with 語(yǔ)句時(shí),首先執(zhí)行 with 后面的 open 代碼
? 執(zhí)行完代碼后,會(huì)將代碼的結(jié)果通過(guò) as 保存到 f 中
? 然后在下面實(shí)現(xiàn)真正要執(zhí)行的操作
? 在操作后面,并不需要寫文件的關(guān)閉操作,文件會(huì)在使用完后自動(dòng)關(guān)閉
1.4.5 with 語(yǔ)句的執(zhí)行原理
實(shí)際上,在文件操作時(shí),并不是不需要寫文件的關(guān)閉,而是文件的關(guān)閉操作在 with 的上下文管理器中的協(xié)議方法里已經(jīng)寫好了。
當(dāng)文件操作執(zhí)行完成后, with語(yǔ)句會(huì)自動(dòng)調(diào)用上下文管理器里的關(guān)閉語(yǔ)句來(lái)關(guān)閉文件資源。
上下文(環(huán)境)管理器
ContextManager ,上下文是 context(環(huán)境)直譯的叫法,在程序中用來(lái)表示代碼執(zhí)行過(guò)程中所處的前后環(huán)境。? 簡(jiǎn)單理解,在文件操作時(shí),需要打開(kāi),關(guān)閉文件,而在文件在進(jìn)行讀寫操作時(shí),就是處在文件操作的上下文中,也就是文件操作環(huán)境中。
說(shuō)明
很多計(jì)算機(jī)術(shù)語(yǔ)在由英文翻譯成中文的過(guò)程中,因?yàn)檎Z(yǔ)境或翻譯人的各人理解的關(guān)系,導(dǎo)致一些中文術(shù)語(yǔ)都晦澀難懂,大家只需要記住這個(gè)術(shù)語(yǔ),理解這個(gè)術(shù)語(yǔ)表示的意義即可。不需要在此糾結(jié)。 比如 file 大陸地區(qū)直接翻譯成文件,而臺(tái)灣地址則會(huì)翻譯成文檔或檔案。 個(gè)人理解:context 翻譯成環(huán)境可能會(huì)更貼切
with 語(yǔ)句在執(zhí)行時(shí),需要調(diào)用上下文管理器中的 __enter__ 和 __exit__ 兩個(gè)方法。
__enter__ 方法會(huì)在執(zhí)行 with 后面的語(yǔ)句時(shí)執(zhí)行,一般用來(lái)處理操作前的內(nèi)容。比如一些創(chuàng)建對(duì)象,初始化等。
__exit__ 方法會(huì)在 with 內(nèi)的代碼執(zhí)行完畢后執(zhí)行,一般用來(lái)處理一些善后收尾工作,比如文件的關(guān)閉,數(shù)據(jù)庫(kù)的關(guān)閉等。
1.4.6 自定義上下文管理器
在自定義上下文管理器時(shí),只需要在類中實(shí)現(xiàn) __enter__ 和 __exit__ 兩個(gè)方法即可。
模擬文件打開(kāi)過(guò)程:
import time
class MyOpen(object):
? ? def __init__(self,file, mode):
? ? ? ? self.__file = file
? ? ? ? self.__mode = mode
? ? def __enter__(self):
? ? ? ? print('__enter__ run ... 打開(kāi)文件')
? ? ? ? self.__handle = open(self.__file, self.__mode)
? ? ? ? return self.__handle
? ? def __exit__(self, exc_type, exc_val, exc_tb):
? ? ? ? print('__exit__... run ... 關(guān)閉文件')
? ? ? ? self.__handle.close()
with MyOpen('test','w') as f:
? ? f.write('Python 大法好')
? ? time.sleep(3)
print('over')
程序執(zhí)行結(jié)果:
__enter__ run ... 打開(kāi)文件
__exit__ run ... 關(guān)閉文件
over
1.4.8 __exit__ 方法的參數(shù)
__exit__ 方法中有三個(gè)參數(shù),用來(lái)接收處理異常,如果代碼在運(yùn)行時(shí)發(fā)生異常,異常會(huì)被保存到這里。
? exc_type : 異常類型
? exc_val : 異常值
? exc_tb : 異常回溯追蹤
class MyCount(object):
? ? def __init__(self,x, y):
? ? ? ? self.__x = x
? ? ? ? self.__y = y
? ? def __enter__(self):
? ? ? ? return self
? ? def __exit__(self, exc_type, exc_val, exc_tb):
? ? ? ? print('Type: ', exc_type)
? ? ? ? print('Value:', exc_val)
? ? ? ? print('TreacBack:', exc_tb)
? ? def div(self):
? ? ? ? return self.__x / self.__y
with MyCount(1, 0) as mc:
? ? ret = mc.div()
? ? print('ret = ', ret)
程序運(yùn)行結(jié)果:
Type:? <class 'ZeroDivisionError'>
Traceback (most recent call last):
Value: division by zero
TreacBack: <traceback object at 0x10410de08>
? File "/Users/KG/PycharmProjects/TestDay12/AA.py", line 18, in <module>
? ? ret = mc.div()
? File "/Users/KG/PycharmProjects/TestDay12/AA.py", line 14, in div
? ? return self.__x / self.__y
ZeroDivisionError: division by zero
因?yàn)槌绦虬l(fā)生了除零錯(cuò)誤,所以出現(xiàn)異常,異常信息被保存到這三個(gè)變量中。
Type:? <class 'ZeroDivisionError'>? ? ? ? ? ? ? # 異常類型
Value: division by zero? ? ? ? ? ? ? ? ? ? ? ? # 異常值
TreacBack: <traceback object at 0x10410de08>? ? # 異常追蹤對(duì)象
? 異常信息的處理
當(dāng)with中執(zhí)行的語(yǔ)句發(fā)生異常時(shí),異常信息會(huì)被發(fā)送到 __exit__ 方法的參數(shù)中,這時(shí)可以根據(jù)情況選擇如何處理異常。
class MyCount(object):
? ? def __init__(self, x, y):
? ? ? ? self.__x = x
? ? ? ? self.__y = y
? ? def __enter__(self):
? ? ? ? return self
? ? def __exit__(self, exc_type, exc_val, exc_tb):
? ? ? ? # 通過(guò) 參數(shù)接收到的值,來(lái)判斷程序執(zhí)行是否出現(xiàn)異常
? ? ? ? # 如果是 None ,說(shuō)明沒(méi)有異常
? ? ? ? if exc_type == None:
? ? ? ? ? ? print('計(jì)算正確執(zhí)行')
? ? ? ? else:
? ? ? ? ? ? # 否則出現(xiàn)異常,可以選擇怎么處理異常
? ? ? ? ? ? print(exc_type,exc_val)
? ? ? ? # 返回值決定了捕獲的異常是否繼續(xù)向外拋出
? ? ? ? # 如果是 False 那么就會(huì)繼續(xù)向外拋出,程序會(huì)看到系統(tǒng)提示的異常信息
? ? ? ? # 如果是 True 不會(huì)向外拋出,程序看不到系統(tǒng)提示信息,只能看到else中的輸出
? ? ? ? return True
? ? def div(self):
? ? ? ? print(self.__x / self.__y)
with MyCount(6, 0) as mc:
? ? mc.div()
在 __exit__函數(shù)執(zhí)行異常處理時(shí),會(huì)根據(jù)函數(shù)的返回值決定是否將系統(tǒng)拋出的異常繼續(xù)向外拋出。
如果返回值為 False 就會(huì)向外拋出,用戶就會(huì)看到。 如果返回值為 True 不會(huì)向外拋出,可以將異常顯示為更加友好的提示信息。
1.4.9 總結(jié)
? with 語(yǔ)句主要是為了簡(jiǎn)化代碼操作。
? with 在執(zhí)行過(guò)程中,會(huì)自動(dòng)調(diào)用上下文管理器中的 __enter__ 和 __exit__ 方法
? __enter__ 方法主要用來(lái)做一些準(zhǔn)備操作
? __exit__ 方法主要用來(lái)做一些善后工作