python中常见的15中面试题
下面這些問題涉及了與Python相關(guān)的許多技能,問題的關(guān)注點(diǎn)主要是語言本身,不是某個(gè)特定的包或模塊。每一個(gè)問題都可以擴(kuò)充為一個(gè)教程,如果可能的話。某些問題甚至?xí)婕岸鄠€(gè)領(lǐng)域。
我之前還沒有出過和這些題目一樣難的面試題,如果你能輕松地回答出來的話,趕緊去找份工作吧!
問題1
到底什么是Python?你可以在回答中與其他技術(shù)進(jìn)行對比(也鼓勵這樣做)。
答案
下面是一些關(guān)鍵點(diǎn):
- Python是一種解釋型語言。這就是說,與C語言和C的衍生語言不同,Python代碼在運(yùn)行之前不需要編譯。其他解釋型語言還包括PHP和Ruby。
- Python是動態(tài)類型語言,指的是你在聲明變量時(shí),不需要說明變量的類型。你可以直接編寫類似x=111和x="I'm a string"這樣的代碼,程序不會報(bào)錯(cuò)。
- Python非常適合面向?qū)ο蟮木幊?#xff08;OOP),因?yàn)樗С滞ㄟ^組合(composition)與繼承(inheritance)的方式定義類(class)。Python中沒有訪問說明符(access specifier,類似C++中的public和private),這么設(shè)計(jì)的依據(jù)是“大家都是成年人了”。
- 在Python語言中,函數(shù)是第一類對象(first-class objects)。這指的是它們可以被指定給變量,函數(shù)既能返回函數(shù)類型,也可以接受函數(shù)作為輸入。類(class)也是第一類對象。
- Python代碼編寫快,但是運(yùn)行速度比編譯語言通常要慢。好在Python允許加入基于C語言編寫的擴(kuò)展,因此我們能夠優(yōu)化代碼,消除瓶頸,這點(diǎn)通常是可以實(shí)現(xiàn)的。numpy就是一個(gè)很好地例子,它的運(yùn)行速度真的非常快,因?yàn)楹芏嗨阈g(shù)運(yùn)算其實(shí)并不是通過Python實(shí)現(xiàn)的。
- Python用途非常廣泛——網(wǎng)絡(luò)應(yīng)用,自動化,科學(xué)建模,大數(shù)據(jù)應(yīng)用,等等。它也常被用作“膠水語言”,幫助其他語言和組件改善運(yùn)行狀況。
- Python讓困難的事情變得容易,因此程序員可以專注于算法和數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì),而不用處理底層的細(xì)節(jié)。
為什么提這個(gè)問題:
如果你應(yīng)聘的是一個(gè)Python開發(fā)崗位,你就應(yīng)該知道這是門什么樣的語言,以及它為什么這么酷。以及它哪里不好。
問題2
補(bǔ)充缺失的代碼
def print_directory_contents(sPath):""" 這個(gè)函數(shù)接受文件夾的名稱作為輸入?yún)?shù), 返回該文件夾中文件的路徑, 以及其包含文件夾中文件的路徑。 """ # 補(bǔ)充代碼答案
def print_directory_contents(sPath):import os for sChild in os.listdir(sPath): sChildPath = os.path.join(sPath,sChild) if os.path.isdir(sChildPath): print_directory_contents(sChildPath) else: print sChildPath特別要注意以下幾點(diǎn):
- 命名規(guī)范要統(tǒng)一。如果樣本代碼中能夠看出命名規(guī)范,遵循其已有的規(guī)范。
- 遞歸函數(shù)需要遞歸并終止。確保你明白其中的原理,否則你將面臨無休無止的調(diào)用棧(callstack)。
- 我們使用os模塊與操作系統(tǒng)進(jìn)行交互,同時(shí)做到交互方式是可以跨平臺的。你可以把代碼寫成sChildPath = sPath + '/' + sChild,但是這個(gè)在Windows系統(tǒng)上會出錯(cuò)。
- 熟悉基礎(chǔ)模塊是非常有價(jià)值的,但是別想破腦袋都背下來,記住Google是你工作中的良師益友。
- 如果你不明白代碼的預(yù)期功能,就大膽提問。
- 堅(jiān)持KISS原則!保持簡單,不過腦子就能懂!
為什么提這個(gè)問題:
- 說明面試者對與操作系統(tǒng)交互的基礎(chǔ)知識
- 遞歸真是太好用啦
問題3
閱讀下面的代碼,寫出A0,A1至An的最終值。
A0 = dict(zip(('a','b','c','d','e'),(1,2,3,4,5))) A1 = range(10) A2 = [i for i in A1 if i in A0] A3 = [A0[s] for s in A0] A4 = [i for i in A1 if i in A3] A5 = {i:i*i for i in A1} A6 = [[i,i*i] for i in A1]答案
A0 = {'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4} A1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] A2 = [] A3 = [1, 3, 2, 5, 4] A4 = [1, 2, 3, 4, 5] A5 = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81} A6 = [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]]為什么提這個(gè)問題:
- 列表解析(list comprehension)十分節(jié)約時(shí)間,對很多人來說也是一個(gè)大的學(xué)習(xí)障礙。
- 如果你讀懂了這些代碼,就很可能可以寫下正確地值。
- 其中部分代碼故意寫的怪怪的。因?yàn)槟愎彩碌娜酥幸矔泄秩恕?/li>
問題4
Python和多線程(multi-threading)。這是個(gè)好主意碼?列舉一些讓Python代碼以并行方式運(yùn)行的方法。
答案
Python并不支持真正意義上的多線程。Python中提供了多線程包,但是如果你想通過多線程提高代碼的速度,使用多線程包并不是個(gè)好主意。Python中有一個(gè)被稱為Global Interpreter Lock(GIL)的東西,它會確保任何時(shí)候你的多個(gè)線程中,只有一個(gè)被執(zhí)行。線程的執(zhí)行速度非常之快,會讓你誤以為線程是并行執(zhí)行的,但是實(shí)際上都是輪流執(zhí)行。經(jīng)過GIL這一道關(guān)卡處理,會增加執(zhí)行的開銷。這意味著,如果你想提高代碼的運(yùn)行速度,使用threading包并不是一個(gè)很好的方法。
不過還是有很多理由促使我們使用threading包的。如果你想同時(shí)執(zhí)行一些任務(wù),而且不考慮效率問題,那么使用這個(gè)包是完全沒問題的,而且也很方便。但是大部分情況下,并不是這么一回事,你會希望把多線程的部分外包給操作系統(tǒng)完成(通過開啟多個(gè)進(jìn)程),或者是某些調(diào)用你的Python代碼的外部程序(例如Spark或Hadoop),又或者是你的Python代碼調(diào)用的其他代碼(例如,你可以在Python中調(diào)用C函數(shù),用于處理開銷較大的多線程工作)。
為什么提這個(gè)問題
因?yàn)镚IL就是個(gè)混賬東西(A-hole)。很多人花費(fèi)大量的時(shí)間,試圖尋找自己多線程代碼中的瓶頸,直到他們明白GIL的存在。
問題5
你如何管理不同版本的代碼?
答案:
版本管理!被問到這個(gè)問題的時(shí)候,你應(yīng)該要表現(xiàn)得很興奮,甚至告訴他們你是如何使用Git(或是其他你最喜歡的工具)追蹤自己和奶奶的書信往來。我偏向于使用Git作為版本控制系統(tǒng)(VCS),但還有其他的選擇,比如subversion(SVN)。
為什么提這個(gè)問題:
因?yàn)闆]有版本控制的代碼,就像沒有杯子的咖啡。有時(shí)候我們需要寫一些一次性的、可以隨手扔掉的腳本,這種情況下不作版本控制沒關(guān)系。但是如果你面對的是大量的代碼,使用版本控制系統(tǒng)是有利的。版本控制能夠幫你追蹤誰對代碼庫做了什么操作;發(fā)現(xiàn)新引入了什么bug;管理你的軟件的不同版本和發(fā)行版;在團(tuán)隊(duì)成員中分享源代碼;部署及其他自動化處理。它能讓你回滾到出現(xiàn)問題之前的版本,單憑這點(diǎn)就特別棒了。還有其他的好功能。怎么一個(gè)棒字了得!
問題6
下面代碼會輸出什么:
def f(x,l=[]): for i in range(x): l.append(i*i) print l f(2) f(3,[3,2,1]) f(3)答案:
[0, 1] [3, 2, 1, 0, 1, 4] [0, 1, 0, 1, 4]呃?
第一個(gè)函數(shù)調(diào)用十分明顯,for循環(huán)先后將0和1添加至了空列表l中。l是變量的名字,指向內(nèi)存中存儲的一個(gè)列表。第二個(gè)函數(shù)調(diào)用在一塊新的內(nèi)存中創(chuàng)建了新的列表。l這時(shí)指向了新生成的列表。之后再往新列表中添加0、1、2和4。很棒吧。第三個(gè)函數(shù)調(diào)用的結(jié)果就有些奇怪了。它使用了之前內(nèi)存地址中存儲的舊列表。這就是為什么它的前兩個(gè)元素是0和1了。
不明白的話就試著運(yùn)行下面的代碼吧:
l_mem = []l = l_mem # the first call for i in range(2): l.append(i*i) print l # [0, 1] l = [3,2,1] # the second call for i in range(3): l.append(i*i) print l # [3, 2, 1, 0, 1, 4] l = l_mem # the third call for i in range(3): l.append(i*i) print l # [0, 1, 0, 1, 4]問題7
“猴子補(bǔ)丁”(monkey patching)指的是什么?這種做法好嗎?
答案:
“猴子補(bǔ)丁”就是指,在函數(shù)或?qū)ο笠呀?jīng)定義之后,再去改變它們的行為。
舉個(gè)例子:
import datetime datetime.datetime.now = lambda: datetime.datetime(2012, 12, 12)大部分情況下,這是種很不好的做法 - 因?yàn)楹瘮?shù)在代碼庫中的行為最好是都保持一致。打“猴子補(bǔ)丁”的原因可能是為了測試。mock包對實(shí)現(xiàn)這個(gè)目的很有幫助。
為什么提這個(gè)問題?
答對這個(gè)問題說明你對單元測試的方法有一定了解。你如果提到要避免“猴子補(bǔ)丁”,可以說明你不是那種喜歡花里胡哨代碼的程序員(公司里就有這種人,跟他們共事真是糟糕透了),而是更注重可維護(hù)性。還記得KISS原則碼?答對這個(gè)問題還說明你明白一些Python底層運(yùn)作的方式,函數(shù)實(shí)際是如何存儲、調(diào)用等等。
另外:如果你沒讀過mock模塊的話,真的值得花時(shí)間讀一讀。這個(gè)模塊非常有用。
問題8
這兩個(gè)參數(shù)是什么意思:*args,**kwargs?我們?yōu)槭裁匆褂盟鼈?#xff1f;
答案
如果我們不確定要往函數(shù)中傳入多少個(gè)參數(shù),或者我們想往函數(shù)中以列表和元組的形式傳參數(shù)時(shí),那就使要用*args;如果我們不知道要往函數(shù)中傳入多少個(gè)關(guān)鍵詞參數(shù),或者想傳入字典的值作為關(guān)鍵詞參數(shù)時(shí),那就要使用**kwargs。args和kwargs這兩個(gè)標(biāo)識符是約定俗成的用法,你當(dāng)然還可以用*bob和**billy,但是這樣就并不太妥。
下面是具體的示例:
def f(*args,**kwargs): print args, kwargs l = [1,2,3] t = (4,5,6) d = {'a':7,'b':8,'c':9} f() f(1,2,3) # (1, 2, 3) {} f(1,2,3,"groovy") # (1, 2, 3, 'groovy') {} f(a=1,b=2,c=3) # () {'a': 1, 'c': 3, 'b': 2} f(a=1,b=2,c=3,zzz="hi") # () {'a': 1, 'c': 3, 'b': 2, 'zzz': 'hi'} f(1,2,3,a=1,b=2,c=3) # (1, 2, 3) {'a': 1, 'c': 3, 'b': 2} f(*l,**d) # (1, 2, 3) {'a': 7, 'c': 9, 'b': 8} f(*t,**d) # (4, 5, 6) {'a': 7, 'c': 9, 'b': 8} f(1,2,*t) # (1, 2, 4, 5, 6) {} f(q="winning",**d) # () {'a': 7, 'q': 'winning', 'c': 9, 'b': 8} f(1,2,*t,q="winning",**d) # (1, 2, 4, 5, 6) {'a': 7, 'q': 'winning', 'c': 9, 'b': 8} def f2(arg1,arg2,*args,**kwargs): print arg1,arg2, args, kwargs f2(1,2,3) # 1 2 (3,) {} f2(1,2,3,"groovy") # 1 2 (3, 'groovy') {} f2(arg1=1,arg2=2,c=3) # 1 2 () {'c': 3} f2(arg1=1,arg2=2,c=3,zzz=