:迭代協(xié)議、GIL與深淺拷貝詳解)
簡(jiǎn)介這份《Python 八股文.pdf》面向準(zhǔn)備技術(shù)面試的 Python 學(xué)習(xí)者與求職者系統(tǒng)梳理語(yǔ)言核心概念與高頻考點(diǎn)幫助讀者在有限時(shí)間內(nèi)建立完整的知識(shí)框架。內(nèi)容覆蓋內(nèi)置電池、生成器與迭代器、列表與元組、字典實(shí)現(xiàn)、GIL 全局解釋器鎖、Python 2 與 3 差異、可變與不可變對(duì)象、閉包、裝飾器及元類等主題并配有代碼示例與原理說明。資源包內(nèi)含 1 個(gè) PDF 文件大小約 304KB輕量便攜適合在電腦或移動(dòng)設(shè)備上隨時(shí)翻閱復(fù)習(xí)。目前已有 13672 人學(xué)習(xí)下載熱度較高說明其內(nèi)容對(duì)面試突擊與知識(shí)查漏補(bǔ)缺具有實(shí)際參考價(jià)值。讀者可借此快速回顧生成器 yield 機(jī)制、dict 哈希查找 O(1) 原理、GIL 對(duì)多線程的影響以及裝飾器在不修改源碼前提下擴(kuò)展函數(shù)功能等要點(diǎn)適合作為面試前的速查手冊(cè)與日常鞏固材料。1. 從一道“送分題”翻車說起Python 八股文到底考什么“生成器和迭代器有什么區(qū)別”——這道題在面試?yán)锍霈F(xiàn)頻率高到離譜但能完整答對(duì)的人不到三成。多數(shù)人背了“生成器是迭代器迭代器不一定是生成器”再往下問“那 list 為什么不是迭代器”“__iter__和__next__分別解決什么問題”就開始卡殼。Python 八股文的考點(diǎn)分布其實(shí)很集中可變與不可變類型、迭代協(xié)議、GIL 與多線程、深淺拷貝、裝飾器與閉包、元類、Py2/Py3 差異。分值上迭代協(xié)議和 GIL 幾乎每場(chǎng)必問深淺拷貝和裝飾器是區(qū)分候選人是“背過”還是“用過”的分水嶺。這份筆記適合正在準(zhǔn)備后端、數(shù)據(jù)、測(cè)試崗面試的人也適合想系統(tǒng)梳理 Python 底層機(jī)制的人。下面按考點(diǎn)模塊拆開講每道題都給答題思路和易錯(cuò)點(diǎn)。2. 高頻考點(diǎn)迭代協(xié)議與生成器的底層邏輯2.1 可迭代對(duì)象、迭代器、生成器的三層關(guān)系面試官問“l(fā)ist 和迭代器的區(qū)別”真正想聽的是你對(duì)迭代協(xié)議的理解。可迭代對(duì)象Iterable必須實(shí)現(xiàn)__iter__()調(diào)用后返回一個(gè)迭代器。迭代器Iterator必須同時(shí)實(shí)現(xiàn)__iter__()和__next__()__next__()每次返回一個(gè)值耗盡后拋StopIteration。生成器Generator是迭代器的一種但迭代器不一定是生成器——比如iter([1,2,3])返回的是 list_iterator不是生成器。from collections.abc import Iterable, Iterator lst [1, 2, 3] print(isinstance(lst, Iterable)) # Truelist 可迭代 print(isinstance(lst, Iterator)) # Falselist 本身不是迭代器 it iter(lst) # 通過 iter() 拿到迭代器 print(isinstance(it, Iterator)) # True print(next(it)) # 1 print(next(it)) # 2這段代碼的關(guān)鍵在iter(lst)這一步它調(diào)用了 list 的__iter__()返回一個(gè) list_iterator 對(duì)象。list 本身沒有__next__()所以不能直接next(lst)。面試時(shí)如果只答“l(fā)ist 可迭代但不是迭代器”面試官會(huì)追問“那 for 循環(huán)怎么工作的”——for 循環(huán)先調(diào)iter()拿到迭代器再反復(fù)調(diào)next()直到捕獲StopIteration。把這條鏈路說清楚這題才算過關(guān)。2.2 生成器的兩種創(chuàng)建方式與內(nèi)存優(yōu)勢(shì)生成器有兩種產(chǎn)生方式列表生成式加括號(hào)g1 (x for x in range(10))或者在函數(shù)里用yield。yield的核心是“暫停并保留狀態(tài)”函數(shù)執(zhí)行到y(tǒng)ield時(shí)返回值并掛起下次next()從掛起點(diǎn)繼續(xù)。def fib(max): n, a, b 0, 0, 1 while n max: yield b # 暫停在這里返回 b a, b b, a b # 下次 next() 從這里繼續(xù) n n 1 return done g fib(8) for val in g: print(val) # 1 1 2 3 5 8 13 21面試官最想聽的是“為什么生成器省內(nèi)存”列表會(huì)一次性把所有元素算出來放進(jìn)內(nèi)存生成器只在每次next()時(shí)計(jì)算當(dāng)前值用完即棄。處理大文件逐行讀取、無限序列、流式數(shù)據(jù)時(shí)生成器是標(biāo)準(zhǔn)答案。注意return done的值不會(huì)通過next()返回而是包在StopIteration異常里這是很多人翻車的細(xì)節(jié)。2.3 真題演練手寫一個(gè)生成器實(shí)現(xiàn)斐波那契面試題常要求“不用列表手寫斐波那契生成器”。答題思路定義函數(shù)用yield返回當(dāng)前值用變量交換更新狀態(tài)。上面fib函數(shù)就是標(biāo)準(zhǔn)寫法。如果面試官加一句“限制最大長(zhǎng)度”就在while里加n max條件。如果問“生成器能不能反向遍歷”答案是默認(rèn)不能因?yàn)樯善魇菃蜗虻男枰绒D(zhuǎn)成列表或自己實(shí)現(xiàn)緩存。提示面試時(shí)被問到“生成器和迭代器哪個(gè)更省內(nèi)存”不要只答“生成器”要補(bǔ)一句“迭代器本身不承諾省內(nèi)存比如 list_iterator 背后還是整個(gè)列表生成器才是惰性計(jì)算”。3. 高頻考點(diǎn)可變類型、深淺拷貝與參數(shù)傳遞3.1 可變與不可變對(duì)象的判定標(biāo)準(zhǔn)可變對(duì)象list、dict、set。不可變對(duì)象bool、int、float、tuple、str。判定標(biāo)準(zhǔn)不是“能不能改”而是“改的時(shí)候是不是原地修改”。對(duì)不可變對(duì)象做“修改”Python 會(huì)創(chuàng)建新對(duì)象對(duì)可變對(duì)象做修改內(nèi)存地址不變。a [1, 2, 3] print(id(a)) # 例如 140234... a.append(4) print(id(a)) # 地址不變?cè)匦薷?b hello print(id(b)) b b world # 創(chuàng)建了新字符串 print(id(b)) # 地址變了面試官問“tuple 里放 list 能不能改”答案是能改 list 里的元素但不能改 tuple 指向的 list 引用。這題考的是“不可變”的邊界tuple 的不可變是指它持有的引用不可變不是引用指向的對(duì)象不可變。3.2 賦值、淺拷貝、深拷貝的差異與代碼驗(yàn)證賦值只是創(chuàng)建新引用兩個(gè)變量指向同一個(gè)對(duì)象。淺拷貝copy.copy創(chuàng)建新對(duì)象但內(nèi)部元素還是原對(duì)象的引用。深拷貝copy.deepcopy遞歸復(fù)制所有層級(jí)完全獨(dú)立。import copy origin [[1, 2], [3, 4]] assign origin shallow copy.copy(origin) deep copy.deepcopy(origin) origin[0][0] 99 print(assign) # [[99, 2], [3, 4]] 跟著變 print(shallow) # [[99, 2], [3, 4]] 內(nèi)層還是同一個(gè)引用 print(deep) # [[1, 2], [3, 4]] 完全獨(dú)立這段代碼是面試白板題的高頻版本。答題時(shí)先畫引用關(guān)系assign和origin是同一個(gè)盒子shallow是新盒子但里面裝的是原盒子的內(nèi)層引用deep是新盒子加新內(nèi)層。面試官追問“什么時(shí)候用深拷貝”答“需要完全隔離修改、或者對(duì)象里有嵌套可變結(jié)構(gòu)且不想互相影響時(shí)”。3.3 真題演練函數(shù)默認(rèn)參數(shù)的坑def add_item(item, lst[]): lst.append(item) return lst print(add_item(1)) # [1] print(add_item(2)) # [1, 2] 不是 [2]默認(rèn)參數(shù)在函數(shù)定義時(shí)只求值一次所以lst[]是同一個(gè)列表。正確寫法是lstNone函數(shù)體內(nèi)判斷if lst is None: lst []。這題考的是“可變默認(rèn)參數(shù)”這個(gè)經(jīng)典失分點(diǎn)面試官想看你有沒有踩過這個(gè)坑。注意淺拷貝只復(fù)制最外層內(nèi)層嵌套的可變對(duì)象仍然共享。深拷貝遇到循環(huán)引用會(huì)遞歸處理但性能開銷大不是所有場(chǎng)景都該用。4. 高頻考點(diǎn)GIL、多線程與多進(jìn)程的選擇4.1 GIL 是什么、為什么存在GILGlobal Interpreter Lock是 CPython 解釋器層面的全局鎖確保同一時(shí)刻只有一個(gè)線程執(zhí)行 Python 字節(jié)碼。它不是 Python 語(yǔ)言特性而是 CPython 實(shí)現(xiàn)多線程時(shí)的同步機(jī)制。設(shè)計(jì)初衷是單核時(shí)代高效利用 CPU并保護(hù)解釋器內(nèi)部數(shù)據(jù)結(jié)構(gòu)如引用計(jì)數(shù)的線程安全。面試官問“GIL 加在哪一層”標(biāo)準(zhǔn)答案是“加在解釋器層不是語(yǔ)言層也不是操作系統(tǒng)層”。因?yàn)?Python 調(diào)用的原生線程由 C 語(yǔ)言控制Python 虛擬機(jī)無法直接管理只能在解釋器這一層加鎖。4.2 多線程、多進(jìn)程、IO 密集與 CPU 密集的對(duì)應(yīng)關(guān)系GIL 導(dǎo)致多線程無法利用多核做并行計(jì)算但 IO 操作時(shí)會(huì)釋放 GIL所以 IO 密集型任務(wù)用多線程仍然有效。CPU 密集型任務(wù)要用多進(jìn)程multiprocessing繞過 GIL。任務(wù)類型推薦方案原因IO 密集網(wǎng)絡(luò)、文件多線程 / asyncioIO 等待時(shí)釋放 GIL線程切換開銷小CPU 密集計(jì)算、圖像多進(jìn)程每個(gè)進(jìn)程獨(dú)立解釋器各自持有 GIL混合型多進(jìn)程 多線程進(jìn)程間并行計(jì)算進(jìn)程內(nèi)并發(fā) IOimport multiprocessing def cpu_task(n): return sum(i * i for i in range(n)) if __name__ __main__: with multiprocessing.Pool(4) as pool: results pool.map(cpu_task, [10**6] * 4) print(results)這段代碼用進(jìn)程池把四個(gè)計(jì)算任務(wù)分到四個(gè)進(jìn)程每個(gè)進(jìn)程有獨(dú)立的 GIL真正并行。注意if __name__ __main__在 Windows 和 macOS 上是必須的否則子進(jìn)程會(huì)重復(fù)導(dǎo)入主模塊導(dǎo)致遞歸創(chuàng)建進(jìn)程。4.3 真題演練Python 多線程能不能提速面試題“我有一個(gè)下載 100 個(gè)網(wǎng)頁(yè)的任務(wù)用多線程能提速嗎”答題思路能因?yàn)榫W(wǎng)絡(luò) IO 會(huì)釋放 GIL線程在等待響應(yīng)時(shí)其他線程可以繼續(xù)發(fā)起請(qǐng)求。但如果任務(wù)是“對(duì) 100 個(gè)大數(shù)組做排序”多線程不能提速要用多進(jìn)程。面試官最想聽的是“區(qū)分 IO 密集和 CPU 密集”這個(gè)判斷標(biāo)準(zhǔn)而不是背“GIL 讓多線程變雞肋”。提示Python 3.x 用計(jì)時(shí)器替代了 Python 2.x 的 ticks 計(jì)數(shù)器達(dá)到時(shí)間閾值后釋放 GIL。但無論 2.x 還是 3.xGIL 都沒有被移除多核并行仍然要靠多進(jìn)程。5. 易錯(cuò)點(diǎn)五道高頻翻車題與正確答法5.1 錯(cuò)把“可迭代”當(dāng)“迭代器”錯(cuò)在哪回答“l(fā)ist 是迭代器”。為什么錯(cuò)list 只有__iter__()沒有__next__()不滿足迭代器協(xié)議。怎么答對(duì)先給定義——可迭代對(duì)象實(shí)現(xiàn)__iter__迭代器同時(shí)實(shí)現(xiàn)__iter__和__next__再舉例子——list 是可迭代對(duì)象iter(list)返回的才是迭代器最后補(bǔ)一句“生成器是迭代器的一種”。5.2 混淆淺拷貝和深拷貝的適用場(chǎng)景錯(cuò)在哪認(rèn)為copy.copy能完全隔離修改。為什么錯(cuò)淺拷貝只復(fù)制最外層容器內(nèi)層嵌套對(duì)象仍是共享引用。怎么答對(duì)畫兩層結(jié)構(gòu)——外層新對(duì)象內(nèi)層舊引用深拷貝遞歸復(fù)制所有層級(jí)。面試時(shí)主動(dòng)說“如果對(duì)象只有一層淺拷貝和深拷貝效果一樣有嵌套時(shí)必須用 deepcopy”。5.3 默認(rèn)參數(shù)用可變對(duì)象錯(cuò)在哪def f(lst[])然后往里 append。為什么錯(cuò)默認(rèn)參數(shù)在函數(shù)定義時(shí)求值一次之后所有調(diào)用共享同一個(gè)列表。怎么答對(duì)默認(rèn)值用None函數(shù)體內(nèi)判斷并初始化同時(shí)說明“這是 Python 的常見陷阱不是 bug是設(shè)計(jì)如此”。5.4 認(rèn)為多線程一定能利用多核錯(cuò)在哪說“Python 多線程可以并行計(jì)算”。為什么錯(cuò)GIL 保證同一時(shí)刻只有一個(gè)線程執(zhí)行字節(jié)碼多核 CPU 上多線程計(jì)算任務(wù)反而因切換開銷變慢。怎么答對(duì)先區(qū)分 IO 密集和 CPU 密集IO 密集用多線程CPU 密集用多進(jìn)程再補(bǔ)一句“GIL 是 CPython 的實(shí)現(xiàn)細(xì)節(jié)不是 Python 語(yǔ)言規(guī)范”。5.5 裝飾器忘記保留原函數(shù)元信息錯(cuò)在哪寫裝飾器直接return inner導(dǎo)致原函數(shù)的__name__、__doc__丟失。為什么錯(cuò)inner覆蓋了原函數(shù)元信息沒有傳遞。怎么答對(duì)用functools.wraps裝飾inner保留原函數(shù)名稱和文檔字符串。import functools def log(func): functools.wraps(func) # 保留 func 的元信息 def inner(*args, **kwargs): print(fcall {func.__name__}) return func(*args, **kwargs) return inner log def greet(name): 打招呼 return fhello {name} print(greet.__name__) # greet不是 inner面試時(shí)如果被問到“裝飾器怎么寫”先寫閉包結(jié)構(gòu)再加functools.wraps最后說明“裝飾器本質(zhì)是接收函數(shù)返回函數(shù)的閉包”。6. 答題技巧從八股文到面試現(xiàn)場(chǎng)的轉(zhuǎn)化方法面試官問八股文不是要你背定義是要看你有沒有“用過”和“想過”。我自己的習(xí)慣是每道題先給一句話結(jié)論再給一個(gè)代碼或場(chǎng)景例子最后補(bǔ)一個(gè)易錯(cuò)點(diǎn)。比如被問“深拷貝淺拷貝”先說“淺拷貝只復(fù)制外層深拷貝遞歸復(fù)制”再寫三行代碼演示最后補(bǔ)“默認(rèn)參數(shù)用可變對(duì)象是同類坑”。這樣答面試官會(huì)覺得你不僅背過還踩過坑。另一個(gè)技巧是“主動(dòng)暴露邊界”。被問 GIL不要只答“多線程不能并行”要補(bǔ)“IO 密集仍然有效CPU 密集用多進(jìn)程GIL 是 CPython 實(shí)現(xiàn)不是語(yǔ)言規(guī)范”。被問生成器不要只答“省內(nèi)存”要補(bǔ)“生成器是迭代器但迭代器不一定是生成器return值包在StopIteration里”。面試官最想聽的是你知不知道“什么時(shí)候不適用”。押題方向上迭代協(xié)議、GIL、深淺拷貝、裝飾器、可變默認(rèn)參數(shù)這五個(gè)點(diǎn)覆蓋了八成以上的 Python 八股文面試。每個(gè)點(diǎn)準(zhǔn)備一個(gè)定義、一個(gè)代碼例子、一個(gè)易錯(cuò)點(diǎn)就能應(yīng)對(duì)大部分追問。如果面試官問“Py2 和 Py3 的區(qū)別”重點(diǎn)說print函數(shù)化、默認(rèn)編碼 UTF-8、range返回迭代器、除法返回 float、str是 unicode。這些差異背后是“Python 3 更一致、更少隱式行為”的設(shè)計(jì)取向。從那以后我每次準(zhǔn)備面試都強(qiáng)制走一遍“定義 代碼 易錯(cuò)點(diǎn)”的三段式把每個(gè)考點(diǎn)壓縮成三句話。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取