閱讀988 返回首頁    go Python


Python麵試必須要看的15個問題

引言

想找一份Python開發工作嗎?那你很可能得證明自己知道如何使用Python。下麵這些問題涉及了與Python相關的許多技能,問題的關注點主要是語言本身,不是某個特定的包或模塊。每一個問題都可以擴充為一個教程,如果可能的話。某些問題甚至會涉及多個領域。

我之前還沒有出過和這些題目一樣難的麵試題,如果你能輕鬆地回答出來的話,趕緊去找份工作吧!

問題1

到底什麼是Python?你可以在回答中與其他技術進行對比(也鼓勵這樣做)。

答案

為什麼提這個問題:

如果你應聘的是一個Python開發崗位,你就應該知道這是門什麼樣的語言,以及它為什麼這麼酷。以及它哪裏不好。問題2

補充缺失的代碼

答案

特別要注意以下幾點:

為什麼提這個問題:

說明麵試者對與操作係統交互的基礎知識

遞歸真是太好用啦

問題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 =

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 =

A6 = [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]]

為什麼提這個問題:

列表解析(list comprehension)十分節約時間,對很多人來說也是一個大的學習障礙。

如果你讀懂了這些代碼,就很可能可以寫下正確地值。

其中部分代碼故意寫的怪怪的。因為你共事的人之中也會有怪人。

問題4

Python和多線程(multi-threading)。這是個好主意碼?列舉一些讓Python代碼以並行方式運行的方法。

答案

為什麼提這個問題

因為GIL就是個混賬東西(A-hole)。很多人花費大量的時間,試圖尋找自己多線程代碼中的瓶頸,直到他們明白GIL的存在。

問題5

你如何管理不同版本的代碼?

答案:

版本管理!被問到這個問題的時候,你應該要表現得很興奮,甚至告訴他們你是如何使用Git(或是其他你最喜歡的工具)追蹤自己和奶奶的書信往來。我偏向於使用Git作為版本控製係統(VCS),但還有其他的選擇,比如subversion(SVN)。

為什麼提這個問題:

因為沒有版本控製的代碼,就像沒有杯子的咖啡。有時候我們需要寫一些一次性的、可以隨手扔掉的腳本,這種情況下不作版本控製沒關係。但是如果你麵對的是大量的代碼,使用版本控製係統是有利的。版本控製能夠幫你追蹤誰對代碼庫做了什麼操作;發現新引入了什麼bug;管理你的軟件的不同版本和發行版;在團隊成員中分享源代碼;部署及其他自動化處理。它能讓你回滾到出現問題之前的版本,單憑這點就特別棒了。還有其他的好功能。怎麼一個棒字了得!

問題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]

第一個函數調用十分明顯,for循環先後將0和1添加至了空列表l中。l是變量的名字,指向內存中存儲的一個列表。第二個函數調用在一塊新的內存中創建了新的列表。l這時指向了新生成的列表。之後再往新列表中添加0、1、2和4。很棒吧。第三個函數調用的結果就有些奇怪了。它使用了之前內存地址中存儲的舊列表。這就是為什麼它的前兩個元素是0和1了。

不明白的話就試著運行下麵的代碼吧:

問題7

“猴子補丁”(monkey patching)指的是什麼?這種做法好嗎?

答案:

“猴子補丁”就是指,在函數或對象已經定義之後,再去改變它們的行為。

舉個例子:

import datetime

大部分情況下,這是種很不好的做法 - 因為函數在代碼庫中的行為最好是都保持一致。打“猴子補丁”的原因可能是為了測試。mock包對實現這個目的很有幫助。

為什麼提這個問題?

答對這個問題說明你對單元測試的方法有一定了解。你如果提到要避免“猴子補丁”,可以說明你不是那種喜歡花裏胡哨代碼的程序員(公司裏就有這種人,跟他們共事真是糟糕透了),而是更注重可維護性。還記得KISS原則碼?答對這個問題還說明你明白一些Python底層運作的方式,函數實際是如何存儲、調用等等。

另外:如果你沒讀過mock模塊的話,真的值得花時間讀一讀。這個模塊非常有用。

問題8

這兩個參數是什麼意思:*args,**kwargs?我們為什麼要使用它們?

答案

如果我們不確定要往函數中傳入多少個參數,或者我們想往函數中以列表和元組的形式傳參數時,那就使要用*args;如果我們不知道要往函數中傳入多少個關鍵詞參數,或者想傳入字典的值作為關鍵詞參數時,那就要使用**kwargs。args和kwargs這兩個標識符是約定俗成的用法,你當然還可以用*bob和**billy,但是這樣就並不太妥。

下麵是具體的示例:

為什麼提這個問題?

有時候,我們需要往函數中傳入未知個數的參數或關鍵詞參數。有時候,我們也希望把參數或關鍵詞參數儲存起來,以備以後使用。有時候,僅僅是為了節省時間。

問題9

下麵這些是什麼意思:@classmethod,@staticmethod,@property?

回答背景知識

這些都是裝飾器(decorator)。裝飾器是一種特殊的函數,要麼接受函數作為輸入參數,並返回一個函數,要麼接受一個類作為輸入參數,並返回一個類。@標記是語法糖(syntactic sugar),可以讓你以簡單易讀得方式裝飾目標對象。

真正的答案

@classmethod, @staticmethod和@property這三個裝飾器的使用對象是在類中定義的函數。下麵的例子展示了它們的用法和行為:

o = MyClass()

# 未裝飾的方法還是正常的行為方式,需要當前的類實例(self)作為第一個參數。

o.normal_method

#

o.normal_method()

# normal_method((,),{})

o.normal_method(1,2,x=3,y=4)

# normal_method((, 1, 2),{ y : 4, x : 3})

# 類方法的第一個參數永遠是該類

o.class_method

#

o.class_method()

# class_method((,),{})

o.class_method(1,2,x=3,y=4)

# class_method((, 1, 2),{ y : 4, x : 3})

# 靜態方法(static method)中除了你調用時傳入的參數以外,沒有其他的參數。

o.static_method

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(,(),{})

# properties are nice

# “屬性”是很好的功能

o.some_property()

# calling some_property getter(,(),{})

# Traceback (most recent call last):

# File "", line 1, in

# TypeError: str object is not callable

o.some_other_property

# calling some_other_property getter(,(),{})

# VERY nice

# o.some_other_property()

# calling some_other_property getter(,(),{})

# Traceback (most recent call last):

# File "", line 1, in

# TypeError: str object is not callable

o.some_property = "groovy"

# calling some_property setter(,( groovy ,),{})

o.some_property

# calling some_property getter(,(),{})

# groovy

o.some_other_property = "very groovy"

# Traceback (most recent call last):

# File "", line 1, in

# AttributeError: can t set attribute

o.some_other_property

# calling some_other_property getter(,(),{})

問題10

閱讀下麵的代碼,它的輸出結果是什麼?

答案

輸出結果以注釋的形式表示:

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

問題11

閱讀下麵的代碼,它的輸出結果是什麼?

class Node(object):

def __init__(self,sName):

self._lChildren = []

self.sName = sName

def __repr__(self):

return "".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()會打印下麵的結果:

oRoot.print_all_1()會打印下麵的結果:

為什麼提這個問題?

因為對象的精髓就在於組合(composition)與對象構造(object construction)。對象需要有組合成分構成,而且得以某種方式初始化。這裏也涉及到遞歸和生成器(generator)的使用。

生成器是很棒的數據類型。你可以隻通過構造一個很長的列表,然後打印列表的內容,就可以取得與print_all_2類似的功能。生成器還有一個好處,就是不用占據很多內存。

有一點還值得指出,就是print_all_1會以深度優先(depth-first)的方式遍曆樹(tree),而print_all_2則是寬度優先(width-first)。有時候,一種遍曆方式比另一種更合適。但這要看你的應用的具體情況。

問題12

簡要描述Python的垃圾回收機製(garbage collection)。

答案

這裏能說的很多。你應該提到下麵幾個主要的點:

Python在內存中存儲了每個對象的引用計數(reference count)。如果計數值變成0,那麼相應的對象就會小時,分配給該對象的內存就會釋放出來用作他用。

偶爾也會出現引用循環(reference cycle)。垃圾回收器會定時尋找這個循環,並將其回收。舉個例子,假設有兩個對象o1和o2,而且符合o1.x == o2和o2.x == o1這兩個條件。如果o1和o2沒有其他代碼引用,那麼它們就不應該繼續存在。但它們的引用計數都是1。

Python中使用了某些啟發式算法(heuristics)來加速垃圾回收。例如,越晚創建的對象更有可能被回收。對象被創建之後,垃圾回收器會分配它們所屬的代(generation)。每個對象都會被分配一個代,而被分配更年輕代的對象是優先被處理的。

問題13

將下麵的函數按照執行效率高低排序。它們都接受由0至1之間的數字構成的列表作為輸入。這個列表可以很長。一個輸入列表的示例如下:[random.random() for i in range(100000)]。你如何證明自己的答案是正確的。

答案

按執行效率從高到低排列:f2、f1和f3。要證明這個答案是對的,你應該知道如何分析自己代碼的性能。Python中有一個很好的程序分析包,可以滿足這個需求。

為了向大家進行完整地說明,下麵我們給出上述分析代碼的輸出結果:

>>> 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 :1(f1)

1 0.001 0.001 0.045 0.045 :1()

1 0.000 0.000 0.000 0.000

1 0.035 0.035 0.035 0.035

>>> 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 :1(f2)

1 0.001 0.001 0.024 0.024 :1()

1 0.000 0.000 0.000 0.000

1 0.016 0.016 0.016 0.016

>>> 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 :1(f3)

1 0.001 0.001 0.055 0.055 :1()

1 0.000 0.000 0.000 0.000

1 0.038 0.038 0.038 0.038

為什麼提這個問題?

定位並避免代碼瓶頸是非常有價值的技能。想要編寫許多高效的代碼,最終都要回答常識上來——在上麵的例子中,如果列表較小的話,很明顯是先進行排序更快,因此如果你可以在排序前先進行篩選,那通常都是比較好的做法。其他不顯而易見的問題仍然可以通過恰當的工具來定位。因此了解這些工具是有好處的。

問題14

你有過失敗的經曆嗎?

錯誤的答案

我從來沒有失敗過!

為什麼提這個問題?

恰當地回答這個問題說明你用於承認錯誤,為自己的錯誤負責,並且能夠從錯誤中學習。如果你想變得對別人有幫助的話,所有這些都是特別重要的。如果你真的是個完人,那就太糟了,回答這個問題的時候你可能都有點創意了。

問題15

你有實施過個人項目嗎?

真的?

如果做過個人項目,這說明從更新自己的技能水平方麵來看,你願意比最低要求付出更多的努力。如果你有維護的個人項目,工作之外也堅持編碼,那麼你的雇主就更可能把你視作為會增值的資產。即使他們不問這個問題,我也認為談談這個話題很有幫助。

結語

我給出的這些問題時,有意涉及了多個領域。而且答案也是特意寫的較為囉嗦。在編程麵試中,你需要展示你對語言的理解,如果你能簡要地說清楚,那請務必那樣做。我盡量在答案中提供了足夠的信息,即使是你之前從來沒有了解過這些領域,你也可以從答案中學到些東西。我希望本文能夠幫助你找到滿意的工作。

點讚+關注

感謝大家

最後更新:2017-10-08 15:52:01

  上一篇:go [Python3與SEO]計算頁麵相似度(jieba分詞+餘弦相似度公式)
  下一篇:go 自學 Python 學習中的問題