Python 高級(jí) 11

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)做一些善后工作

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評(píng)論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,520評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 178,362評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,805評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,541評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,896評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評(píng)論 3 447
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 43,062評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,608評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,356評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,555評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,769評(píng)論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 35,175評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,489評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,289評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,516評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容