我們一起聊聊 Python 八股文
?同志們好,今天帶著大家一起來復(fù)習(xí)python中的基礎(chǔ)問題,我們都知道python屬于解釋性語言,效率也就相對其它語言來說較低一些,這個較低只是運行稍微低些,但是呢,在很多場景買這些都是微不足道的
憑借著語法的易于理解和學(xué)習(xí),可以在短時間內(nèi)完成更多工作,開發(fā)效率也會變得更高
同時,python自帶了各種現(xiàn)成的庫,供我們在開發(fā)程序中使用,python也比較容易維護(hù)
Python為我們提供了非常完善的基礎(chǔ)代碼庫,覆蓋了網(wǎng)絡(luò)、文件、GUI、數(shù)據(jù)庫、文本等大量內(nèi)容,被形象地稱作“內(nèi)置電池(batteries included)”。用Python開發(fā),許多功能不必從零編寫,直接使用現(xiàn)成的即可。
除了內(nèi)置的庫外,Python還有大量的第三方庫,也就是別人開發(fā)的,供你直接使用的東西。當(dāng)然,如果你開發(fā)的代碼通過很好的封裝,也可以作為第三方庫給別人使用。
什么是 Python 生成器?
generator,有兩種產(chǎn)生生成器對象的方式:一種是列表生成式加括號:
g1 = (x for x in range(10))
一種是在函數(shù)定義中包含yield關(guān)鍵字:
def fib(max):
n, a, b = 0, 0, 1
while n < max: yield b
a, b = b, a + b
n = n + 1
return 'done'g2 = fib(8)
對于generator對象g1和g2,可以通過next(g1)不斷獲得下一個元素的值,如果沒有更多的元素,就會報錯StopIteration
也可以通過for循環(huán)獲得元素的值。
生成器的好處是不用占用很多內(nèi)存,只需要在用的時候計算元素的值就行了。
什么是 Python 迭代器?
Python中可以用于for循環(huán)的,叫做可迭代Iterable,包括list/set/tuple/str/dict等數(shù)據(jù)結(jié)構(gòu)以及生成器;可以用以下語句判斷一個對象是否是可迭代的:
from collections import Iterableisinstance(x, Iterable)
迭代器Iterator,是指可以被next()函數(shù)調(diào)用并不斷返回下一個值,直到StopIteration;生成器都是Iterator,而列表等數(shù)據(jù)結(jié)構(gòu)不是;
可以通過以下語句將list變?yōu)镮terator:
iter([1,2,3,4,5])
生成器都是Iterator,但迭代器不一定是生成器。
list 和 tuple 有什么區(qū)別?
- list 長度可變,tuple不可變;
- list 中元素的值可以改變,tuple 不能改變;
- list 支持append; insert; remove; pop等方法,tuple 都不支持
Python 中的 list 和 dict 是怎么實現(xiàn)的?
List: 本質(zhì)是順序表,只不過每次表的擴容都是指數(shù)級,所以動態(tài)增刪數(shù)據(jù)時,表并不會頻繁改變物理結(jié)構(gòu),同時受益于順序表遍歷的高效性(通過角標(biāo)配合表頭物理地址,計算目標(biāo)元素的位置),使得python的list綜合性能比較優(yōu)秀
dict: 本質(zhì)上是順序表,不過每個元素存儲位置的角標(biāo),不是又插入順序決定的,而是由key經(jīng)過hash算法和其他機制,動態(tài)生成的,即key通過hash散列,生成value應(yīng)該存儲的位置,然后再去存儲這個value;所以dict的查詢時間復(fù)雜度是o(1);
因此,dict的key只能為可hash的對象,即不可變類型;
Python 中使用多線程可以達(dá)到多核CPU一起使用嗎?
Python中有一個被稱為Global Interpreter Lock(GIL)的東西,它會確保任何時候你的多個線程中,只有一個被執(zhí)行。
線程的執(zhí)行速度非常之快,會讓你誤以為線程是并行執(zhí)行的,但是實際上都是輪流執(zhí)行。經(jīng)過GIL這一道關(guān)卡處理,會增加執(zhí)行的開銷。
可以通過多進(jìn)程實現(xiàn)多核任務(wù)。
py3和py2的區(qū)別
print在py3里是一個函數(shù),在py2里只是一個關(guān)鍵字
py3文件的默認(rèn)編碼是utf8,py2文件的默認(rèn)編碼是ascii
py3的str是unicode字符串,而py2的str是bytes
py3的range()返回一個可迭代對象,py2的 range()返回一個列表,xrange()返回一個可迭代對象,py3的除法返回float,py2的除法返回int
可變對象與不可變對象
可變對象: list,dict,set
不可變對象: bool,int,float,tuple,str…
迭代器與可迭代對象的區(qū)別
可迭代對象類,必須自定義__iter__()魔法方法,range,list類的實例化對象都是可迭代對象
迭代器類,必須自定義__iter__()和__next__()魔法方法,用iter()函數(shù)可以創(chuàng)建可迭代對象的迭代器
閉包
閉包就是一個嵌套函數(shù),它的內(nèi)部函數(shù) 使用了 外部函數(shù)的變量或參數(shù),它的外部函數(shù) 返回了 內(nèi)部函數(shù)
可以保存外部函數(shù)內(nèi)的變量,不會隨著外部函數(shù)調(diào)用完而銷毀
什么是裝飾器?
裝飾器是一個接收函數(shù)作為參數(shù)的閉包函數(shù)
它可以在不修改函數(shù)內(nèi)部源代碼的情況下,給函數(shù)添加額外的功能
import time
def calc_time(func):
def inner():
t1 = time.time()
func()
t2 = time.time()
print('cost time: {}s'.format(t2-t1))
return inner
什么是元類? 使用場景
元類是創(chuàng)建類的類,type還有繼承自type的類都是元類
作用: 在類定義時(new, init)和 類實例化時(call) 可以添加自定義的功能
使用場景: ORM框架中創(chuàng)建一個類就代表數(shù)據(jù)庫中的一個表,但是定義這個類時為了統(tǒng)一需要把里面的類屬性全部改為小寫,這個時候就要用元類重寫new方法,把attrs字典里的key轉(zhuǎn)為小寫
GIL(Global Interpreter Lock)
全局解釋器鎖
全局解釋器鎖(Global Interpreter Lock)是計算機程序設(shè)計語言解釋器用于同步線程的一種機制,它使得任何時刻僅有一個線程在執(zhí)行。
即便在多核處理器上,使用 GIL 的解釋器也只允許同一時間執(zhí)行一個線程,常見的使用 GIL 的解釋器有CPython與Ruby MRI。可以看到GIL并不是Python獨有的特性,是解釋型語言處理多線程問題的一種機制而非語言特性。
GIL的設(shè)計初衷?
單核時代高效利用CPU, 針對解釋器級別的數(shù)據(jù)安全(不是thread-safe 線程安全)。首先需要明確的是GIL并不是Python的特性,它是在實現(xiàn)Python解析器(CPython)時所引入的一個概念。
當(dāng)Python虛擬機的線程想要調(diào)用C的原生線程需要知道線程的上下文,因為沒有辦法控制C的原生線程的執(zhí)行,所以只能把上下文關(guān)系傳給原生線程,同理獲取結(jié)果也是線 程在python虛擬機這邊等待。那么要執(zhí)行一次計算操作,就必須讓執(zhí)行程序的線程組串行執(zhí)行。
為什么要加在解釋器,而不是在其他層?
展開 GIL鎖加在解釋器一層,也就是說Python調(diào)用的Cython解釋器上加了GIL鎖,因為你python調(diào)用的所有線程都是原生線程。原生線程是通過C語言提供原生接口,相當(dāng)于C語言的一個函數(shù)。
你一調(diào)它,你就控制不了了它了,就必須等它給你返回結(jié)果。只要已通過python虛擬機 ,再往下就不受python控制了,就是C語言自己控制了。
加在Python虛擬機以下加不上去,只能加在Python解釋器這一層。
GIL的實現(xiàn)是線程不安全?為什么?
python2.x和3.x都是在執(zhí)行IO操作的時候,強制釋放GIL,使其他線程有機會執(zhí)行程序。
Python2.x Python使用計數(shù)器ticks計算字節(jié)碼,當(dāng)執(zhí)行100個字節(jié)碼的時候強制釋放GIL,其他線程獲取GIL繼續(xù)執(zhí)行。ticks可以看作是Python自己的計數(shù)器,專門作用于GIL,釋放后歸零,技術(shù)可以調(diào)整。
Python3.x Python使用計時器,執(zhí)行時間達(dá)到閾值后,當(dāng)前線程釋放GIL。總體來說比Python3.x對CPU密集型任務(wù)更好,但是依然沒有解決問題。
什么是 lambda 表達(dá)式?
簡單來說,lambda表達(dá)式通常是當(dāng)你需要使用一個函數(shù),但是又不想費腦袋去命名一個函數(shù)的時候使用,也就是通常所說的匿名函數(shù)。
lambda表達(dá)式一般的形式是:關(guān)鍵詞lambda后面緊接一個或多個參數(shù),緊接一個冒號“:”,緊接一個表達(dá)式
什么是深拷貝和淺拷貝?
賦值(=),就是創(chuàng)建了對象的一個新的引用,修改其中任意一個變量都會影響到另一個。
淺拷貝 copy.copy:創(chuàng)建一個新的對象,但它包含的是對原始對象中包含項的引用(如果用引用的方式修改其中一個對象,另外一個也會修改改變)
深拷貝:創(chuàng)建一個新的對象,并且遞歸的復(fù)制它所包含的對象(修改其中一個,另外一個不會改變){copy模塊的deep.deepcopy()函數(shù)}?