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