想找一份Python開發工做嗎?那你極可能得證實本身知道如何使用Python。下面這些問題涉及了與Python相關的許多技能,問題的關注點主要是語言自己,不是某個特定的包或模塊。每個問題均可以擴充爲一個教程,若是可能的話。某些問題甚至會涉及多個領域。html
我以前尚未出過和這些題目同樣難的面試題,若是你能輕鬆地回答出來的話,趕忙去找份工做吧!python
到底什麼是Python?你能夠在回答中與其餘技術進行對比(也鼓勵這樣作)。程序員
答案面試
下面是一些關鍵點:算法
x=111
和x="I'm a string"
這樣的代碼,程序不會報錯。public
和private
),這麼設計的依據是「你們都是成年人了」。numpy
就是一個很好地例子,它的運行速度真的很是快,由於不少算術運算其實並非經過Python實現的。爲何提這個問題:編程
若是你應聘的是一個Python開發崗位,你就應該知道這是門什麼樣的語言,以及它爲何這麼酷。以及它哪裏很差。網絡
補充缺失的代碼數據結構
def print_directory_contents(sPath): """ 這個函數接受文件夾的名稱做爲輸入參數, 返回該文件夾中文件的路徑, 以及其包含文件夾中文件的路徑。 """ # 補充代碼
答案多線程
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
特別要注意如下幾點:app
os
模塊與操做系統進行交互,同時作到交互方式是能夠跨平臺的。你能夠把代碼寫成sChildPath = sPath + '/' + sChild
,可是這個在Windows系統上會出錯。爲何提這個問題:
閱讀下面的代碼,寫出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]]
爲何提這個問題:
Python和多線程(multi-threading)。這是個好主意碼?列舉一些讓Python代碼以並行方式運行的方法。
答案
Python並不支持真正意義上的多線程。Python中提供了多線程包,可是若是你想經過多線程提升代碼的速度,使用多線程包並非個好主意。Python中有一個被稱爲Global Interpreter Lock(GIL)的東西,它會確保任什麼時候候你的多個線程中,只有一個被執行。線程的執行速度很是之快,會讓你誤覺得線程是並行執行的,可是實際上都是輪流執行。通過GIL這一道關卡處理,會增長執行的開銷。這意味着,若是你想提升代碼的運行速度,使用threading
包並非一個很好的方法。
不過仍是有不少理由促使咱們使用threading
包的。若是你想同時執行一些任務,並且不考慮效率問題,那麼使用這個包是徹底沒問題的,並且也很方便。可是大部分狀況下,並非這麼一回事,你會但願把多線程的部分外包給操做系統完成(經過開啓多個進程),或者是某些調用你的Python代碼的外部程序(例如Spark或Hadoop),又或者是你的Python代碼調用的其餘代碼(例如,你能夠在Python中調用C函數,用於處理開銷較大的多線程工做)。
爲何提這個問題
由於GIL就是個混帳東西(A-hole)。不少人花費大量的時間,試圖尋找本身多線程代碼中的瓶頸,直到他們明白GIL的存在。
你如何管理不一樣版本的代碼?
答案:
版本管理!被問到這個問題的時候,你應該要表現得很興奮,甚至告訴他們你是如何使用Git(或是其餘你最喜歡的工具)追蹤本身和奶奶的書信往來。我偏向於使用Git做爲版本控制系統(VCS),但還有其餘的選擇,好比subversion(SVN)。
爲何提這個問題:
由於沒有版本控制的代碼,就像沒有杯子的咖啡。有時候咱們須要寫一些一次性的、能夠隨手扔掉的腳本,這種狀況下不做版本控制不要緊。可是若是你面對的是大量的代碼,使用版本控制系統是有利的。版本控制可以幫你追蹤誰對代碼庫作了什麼操做;發現新引入了什麼bug;管理你的軟件的不一樣版本和發行版;在團隊成員中分享源代碼;部署及其餘自動化處理。它能讓你回滾到出現問題以前的版本,單憑這點就特別棒了。還有其餘的好功能。怎麼一個棒字了得!
下面代碼會輸出什麼:
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]
呃?
第一個函數調用十分明顯,for循環前後將0和1添加至了空列表l
中。l
是變量的名字,指向內存中存儲的一個列表。第二個函數調用在一塊新的內存中建立了新的列表。l
這時指向了新生成的列表。以後再往新列表中添加0、一、2和4。很棒吧。第三個函數調用的結果就有些奇怪了。它使用了以前內存地址中存儲的舊列表。這就是爲何它的前兩個元素是0和1了。
不明白的話就試着運行下面的代碼吧:
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]
「猴子補丁」(monkey patching)指的是什麼?這種作法好嗎?
答案:
「猴子補丁」就是指,在函數或對象已經定義以後,再去改變它們的行爲。
舉個例子:
import datetime datetime.datetime.now = lambda: datetime.datetime(2012, 12, 12)
大部分狀況下,這是種很很差的作法 - 由於函數在代碼庫中的行爲最好是都保持一致。打「猴子補丁」的緣由多是爲了測試。mock
包對實現這個目的頗有幫助。
爲何提這個問題?
答對這個問題說明你對單元測試的方法有必定了解。你若是提到要避免「猴子補丁」,能夠說明你不是那種喜歡花裏胡哨代碼的程序員(公司裏就有這種人,跟他們共事真是糟糕透了),而是更注重可維護性。還記得KISS原則碼?答對這個問題還說明你明白一些Python底層運做的方式,函數實際是如何存儲、調用等等。
另外:若是你沒讀過mock
模塊的話,真的值得花時間讀一讀。這個模塊很是有用。
這兩個參數是什麼意思:*args
,**kwargs
?咱們爲何要使用它們?
答案
若是咱們不肯定要往函數中傳入多少個參數,或者咱們想往函數中以列表和元組的形式傳參數時,那就使要用*args
;若是咱們不知道要往函數中傳入多少個關鍵詞參數,或者想傳入字典的值做爲關鍵詞參數時,那就要使用**kwargs
。args
和kwargs
這兩個標識符是約定俗成的用法,你固然還能夠用*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="hi") # 1 2 () {'c': 3, 'zzz': 'hi'} f2(1,2,3,a=1,b=2,c=3) # 1 2 (3,) {'a': 1, 'c': 3, 'b': 2} f2(*l,**d) # 1 2 (3,) {'a': 7, 'c': 9, 'b': 8} f2(*t,**d) # 4 5 (6,) {'a': 7, 'c': 9, 'b': 8} f2(1,2,*t) # 1 2 (4, 5, 6) {} f2(1,1,q="winning",**d) # 1 1 () {'a': 7, 'q': 'winning', 'c': 9, 'b': 8} f2(1,2,*t,q="winning",**d) # 1 2 (4, 5, 6) {'a': 7, 'q': 'winning', 'c': 9, 'b': 8}
爲何提這個問題?
有時候,咱們須要往函數中傳入未知個數的參數或關鍵詞參數。有時候,咱們也但願把參數或關鍵詞參數儲存起來,以備之後使用。有時候,僅僅是爲了節省時間。
下面這些是什麼意思:@classmethod
, @staticmethod
, @property
?
回答背景知識
這些都是裝飾器(decorator)。裝飾器是一種特殊的函數,要麼接受函數做爲輸入參數,並返回一個函數,要麼接受一個類做爲輸入參數,並返回一個類。@標記是語法糖(syntactic sugar),可讓你以簡單易讀得方式裝飾目標對象。
@my_decorator def my_func(stuff): do_things Is equivalent to def my_func(stuff): do_things my_func = my_decorator(my_func)
你能夠在本網站上找到介紹裝飾器工做原理的教材。
真正的答案
@classmethod
, @staticmethod
和@property
這三個裝飾器的使用對象是在類中定義的函數。下面的例子展現了它們的用法和行爲:
class MyClass(object): def __init__(self): self._some_property = "properties are nice" self._some_other_property = "VERY nice" def normal_method(*args,**kwargs): print "calling normal_method({0},{1})".format(args,kwargs) @classmethod def class_method(*args,**kwargs): print "calling class_method({0},{1})".format(args,kwargs) @staticmethod def static_method(*args,**kwargs): print "calling static_method({0},{1})".format(args,kwargs) @property def some_property(self,*args,**kwargs): print "calling some_property getter({0},{1},{2})".format(self,args,kwargs) return self._some_property @some_property.setter def some_property(self,*args,**kwargs): print "calling some_property setter({0},{1},{2})".format(self,args,kwargs) self._some_property = args[0] @property def some_other_property(self,*args,**kwargs): print "calling some_other_property getter({0},{1},{2})".format(self,args,kwargs) return self._some_other_property o = MyClass() # 未裝飾的方法仍是正常的行爲方式,須要當前的類實例(self)做爲第一個參數。 o.normal_method # <bound method MyClass.normal_method of <__main__.MyClass instance at 0x7fdd2537ea28>> o.normal_method() # normal_method((<__main__.MyClass instance at 0x7fdd2537ea28>,),{}) o.normal_method(1,2,x=3,y=4) # normal_method((<__main__.MyClass instance at 0x7fdd2537ea28>, 1, 2),{'y': 4, 'x': 3}) # 類方法的第一個參數永遠是該類 o.class_method # <bound method classobj.class_method of <class __main__.MyClass at 0x7fdd2536a390>> o.class_method() # class_method((<class __main__.MyClass at 0x7fdd2536a390>,),{}) o.class_method(1,2,x=3,y=4) # class_method((<class __main__.MyClass at 0x7fdd2536a390>, 1, 2),{'y': 4, 'x': 3}) # 靜態方法(static method)中除了你調用時傳入的參數之外,沒有其餘的參數。 o.static_method # <function static_method at 0x7fdd25375848> o.static_method() # static_method((),{}) o.static_method(1,2,x=3,y=4) # static_method((1, 2),{'y': 4, 'x': 3}) # @property是實現getter和setter方法的一種方式。直接調用它們是錯誤的。 # 「只讀」屬性能夠經過只定義getter方法,不定義setter方法實現。 o.some_property # 調用some_property的getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{}) # 'properties are nice' # 「屬性」是很好的功能 o.some_property() # calling some_property getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{}) # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # TypeError: 'str' object is not callable o.some_other_property # calling some_other_property getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{}) # 'VERY nice' # o.some_other_property() # calling some_other_property getter(<__main__.MyClass instance at 0x7fb2b70877e8>,(),{}) # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # TypeError: 'str' object is not callable o.some_property = "groovy" # calling some_property setter(<__main__.MyClass object at 0x7fb2b7077890>,('groovy',),{}) o.some_property # calling some_property getter(<__main__.MyClass object at 0x7fb2b7077890>,(),{}) # 'groovy' o.some_other_property = "very groovy" # Traceback (most recent call last): # File "<stdin>", line 1, in <module> # AttributeError: can't set attribute o.some_other_property # calling some_other_property getter(<__main__.MyClass object at 0x7fb2b7077890>,(),{})
閱讀下面的代碼,它的輸出結果是什麼?
class A(object): def go(self): print "go A go!" def stop(self): print "stop A stop!" def pause(self): raise Exception("Not Implemented") class B(A): def go(self): super(B, self).go() print "go B go!" class C(A): def go(self): super(C, self).go() print "go C go!" def stop(self): super(C, self).stop() print "stop C stop!" class D(B,C): def go(self): super(D, self).go() print "go D go!" def stop(self): super(D, self).stop() print "stop D stop!" def pause(self): print "wait D wait!" class E(B,C): pass a = A() b = B() c = C() d = D() e = E() # 說明下列代碼的輸出結果 a.go() b.go() c.go() d.go() e.go() a.stop() b.stop() c.stop() d.stop() e.stop() a.pause() b.pause() c.pause() d.pause() e.pause()
答案
輸出結果以註釋的形式表示:
a.go() # go A go! b.go() # go A go! # go B go! c.go() # go A go! # go C go! d.go() # go A go! # go C go! # go B go! # go D go! e.go() # go A go! # go C go! # go B go! a.stop() # stop A stop! b.stop() # stop A stop! c.stop() # stop A stop! # stop C stop! d.stop() # stop A stop! # stop C stop! # stop D stop! e.stop() # stop A stop! a.pause() # ... Exception: Not Implemented b.pause() # ... Exception: Not Implemented c.pause() # ... Exception: Not Implemented d.pause() # wait D wait! e.pause() # ...Exception: Not Implemented
爲何提這個問題?
由於面向對象的編程真的真的很重要。不騙你。答對這道問題說明你理解了繼承和Python中super
函數的用法。
閱讀下面的代碼,它的輸出結果是什麼?
class Node(object): def __init__(self,sName): self._lChildren = [] self.sName = sName def __repr__(self): return "<Node '{}'>".format(self.sName) def append(self,*args,**kwargs): self._lChildren.append(*args,**kwargs) def print_all_1(self): print self for oChild in self._lChildren: oChild.print_all_1() def print_all_2(self): def gen(o): lAll = [o,] while lAll: oNext = lAll.pop(0) lAll.extend(oNext._lChildren) yield oNext for oNode in gen(self): print oNode oRoot = Node("root") oChild1 = Node("child1") oChild2 = Node("child2") oChild3 = Node("child3") oChild4 = Node("child4") oChild5 = Node("child5") oChild6 = Node("child6") oChild7 = Node("child7") oChild8 = Node("child8") oChild9 = Node("child9") oChild10 = Node("child10") oRoot.append(oChild1) oRoot.append(oChild2) oRoot.append(oChild3) oChild1.append(oChild4) oChild1.append(oChild5) oChild2.append(oChild6) oChild4.append(oChild7) oChild3.append(oChild8) oChild3.append(oChild9) oChild6.append(oChild10) # 說明下面代碼的輸出結果 oRoot.print_all_1() oRoot.print_all_2()
答案
oRoot.print_all_1()
會打印下面的結果:
<Node 'root'> <Node 'child1'> <Node 'child4'> <Node 'child7'> <Node 'child5'> <Node 'child2'> <Node 'child6'> <Node 'child10'> <Node 'child3'> <Node 'child8'> <Node 'child9'>
oRoot.print_all_1()
會打印下面的結果:
<Node 'root'> <Node 'child1'> <Node 'child2'> <Node 'child3'> <Node 'child4'> <Node 'child5'> <Node 'child6'> <Node 'child8'> <Node 'child9'> <Node 'child7'> <Node 'child10'>
爲何提這個問題?
由於對象的精髓就在於組合(composition)與對象構造(object construction)。對象須要有組合成分構成,並且得以某種方式初始化。這裏也涉及到遞歸和生成器(generator)的使用。
生成器是很棒的數據類型。你能夠只經過構造一個很長的列表,而後打印列表的內容,就能夠取得與print_all_2
相似的功能。生成器還有一個好處,就是不用佔據不少內存。
有一點還值得指出,就是print_all_1
會以深度優先(depth-first)的方式遍歷樹(tree),而print_all_2
則是寬度優先(width-first)。有時候,一種遍歷方式比另外一種更合適。但這要看你的應用的具體狀況。
簡要描述Python的垃圾回收機制(garbage collection)。
答案
這裏能說的不少。你應該提到下面幾個主要的點:
引用循環
(reference cycle)。垃圾回收器會定時尋找這個循環,並將其回收。舉個例子,假設有兩個對象o1
和o2
,並且符合o1.x == o2
和o2.x == o1
這兩個條件。若是o1
和o2
沒有其餘代碼引用,那麼它們就不該該繼續存在。但它們的引用計數都是1。將下面的函數按照執行效率高低排序。它們都接受由0至1之間的數字構成的列表做爲輸入。這個列表能夠很長。一個輸入列表的示例以下:[random.random() for i in range(100000)]
。你如何證實本身的答案是正確的。
def f1(lIn): l1 = sorted(lIn) l2 = [i for i in l1 if i<0.5] return [i*i for i in l2] def f2(lIn): l1 = [i for i in lIn if i<0.5] l2 = sorted(l1) return [i*i for i in l2] def f3(lIn): l1 = [i*i for i in lIn] l2 = sorted(l1) return [i for i in l1 if i<(0.5*0.5)]
答案
按執行效率從高到低排列:f二、f1和f3。要證實這個答案是對的,你應該知道如何分析本身代碼的性能。Python中有一個很好的程序分析包,能夠知足這個需求。
import cProfile lIn = [random.random() for i in range(100000)] cProfile.run('f1(lIn)') cProfile.run('f2(lIn)') cProfile.run('f3(lIn)')
爲了向你們進行完整地說明,下面咱們給出上述分析代碼的輸出結果:
>>> cProfile.run('f1(lIn)') 4 function calls in 0.045 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.009 0.009 0.044 0.044 <stdin>:1(f1) 1 0.001 0.001 0.045 0.045 <string>:1(<module>) 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 1 0.035 0.035 0.035 0.035 {sorted} >>> cProfile.run('f2(lIn)') 4 function calls in 0.024 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.008 0.008 0.023 0.023 <stdin>:1(f2) 1 0.001 0.001 0.024 0.024 <string>:1(<module>) 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 1 0.016 0.016 0.016 0.016 {sorted} >>> cProfile.run('f3(lIn)') 4 function calls in 0.055 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.016 0.016 0.054 0.054 <stdin>:1(f3) 1 0.001 0.001 0.055 0.055 <string>:1(<module>) 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 1 0.038 0.038 0.038 0.038 {sorted}
爲何提這個問題?
定位並避免代碼瓶頸是很是有價值的技能。想要編寫許多高效的代碼,最終都要回答常識上來——在上面的例子中,若是列表較小的話,很明顯是先進行排序更快,所以若是你能夠在排序前先進行篩選,那一般都是比較好的作法。其餘不顯而易見的問題仍然能夠經過恰當的工具來定位。所以瞭解這些工具是有好處的。
你有過失敗的經歷嗎?
錯誤的答案
我歷來沒有失敗過!
爲何提這個問題?
恰當地回答這個問題說明你用於認可錯誤,爲本身的錯誤負責,而且可以從錯誤中學習。若是你想變得對別人有幫助的話,全部這些都是特別重要的。若是你真的是個完人,那就太糟了,回答這個問題的時候你可能都有點創意了。
你有實施過我的項目嗎?
真的?
若是作過我的項目,這說明從更新本身的技能水平方面來看,你願意比最低要求付出更多的努力。若是你有維護的我的項目,工做以外也堅持編碼,那麼你的僱主就更可能把你視做爲會增值的資產。即便他們不問這個問題,我也認爲談談這個話題頗有幫助。
我給出的這些問題時,有意涉及了多個領域。並且答案也是特地寫的較爲囉嗦。在編程面試中,你須要展現你對語言的理解,若是你能簡要地說清楚,那請務必那樣作。我儘可能在答案中提供了足夠的信息,即便是你以前歷來沒有了解過這些領域,你也能夠從答案中學到些東西。我但願本文可以幫助你找到滿意的工做。
加油!