python的7個經典的面試題

http://michaelyou.github.io/2015/05/19/%E7%BF%BB%E8%AF%91-7%E4%B8%AA%E7%BB%8F%E5%85%B8python%E9%9D%A2%E8%AF%95%E9%A2%98/

下面的代碼輸出什麼?

list = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’]
print list[10:]
上面的代碼輸出[],並且不會導致IndexError錯誤

跟你想的一樣,當取列表元素的時候,如果索引值超過了元素的個數(例如在上面的列表中,取list[10])將會導致IndexError錯誤。但是,取一個列表的切片的時候,如果起始索引超過了元素個數,將不會引起IndexError錯誤,僅返回一個空列表。

這一特性將會導致一些非常難於追蹤的bug,因爲在運行時根本沒有錯誤產生。

下面的代碼在Python2中的輸出是什麼?解釋你的答案

def div1(x,y):
print “%s/%s = %s” % (x, y, x/y)

def div2(x,y):
print “%s//%s = %s” % (x, y, x//y)

div1(5,2)
div1(5.,2)
div2(5,2)
div2(5.,2.)
另外,在Python3中上面的代碼的輸出有何不同(假設代碼中的print語句都轉化成了Python3中的語法結構)?

在Python2中,代碼的輸出是:

5/2 = 2
5.0/2 = 2.5
5//2 = 2
5.0//2.0 = 2.0
默認情況下,如果兩個操作數都是整數,Python2默認執行整數運算。所以,5/2 結果是2,而5./2結果是2.5

注意你可以通過下面的import語句來覆蓋Python2中的這一行爲

from future import division
還要注意“雙斜槓”(//)操作符將會一直執行整除,忽略操作數的類型。這就是爲什麼5.0//2.0即使在Python2中結果也是2.0

但是在Python3並沒有這一行爲。兩個操作數都是整數時,也不執行整數運算。在Python3中,輸出如下:

5/2 = 2.5
5.0/2 = 2.5
5//2 = 2
5.0//2.0 = 2.0
下面代碼的輸出是什麼?請解釋你的答案

def extendList(val, list=[]):
list.append(val)
return list

list1 = extendList(10)
list2 = extendList(123,[])
list3 = extendList(‘a’)

print “list1 = %s” % list1
print “list2 = %s” % list2
print “list3 = %s” % list3
如何修改函數ExtendList的定義才能產生我們希望的行爲?

輸出爲:

list1 = [10, ‘a’]
list2 = [123]
list3 = [10, ‘a’]
很多人會錯誤地預計list1等於[10],list3等於[‘a’],認爲extendList函數的list參數在每一次函數被調用時都會被設置爲默認值[]

但是,真實的情況是,默認的list只在函數定義的時候被創建一次。之後不指定list參數地調用extendList函數時,使用的都是同一個list。這是因爲帶默認參數的表達式是在函數定義的時候被計算的,而不是在函數調用時。

question:既然python的默認參數都是在定義時計算的,下次調用時會調用統一個list,那最後一個怎麼不得24呢???

所以,list1和list3都是在操作同一個默認list,而list2是在操作它自己創建的一個獨立的list(將自己的空list作爲參數傳遞過去)

extendlist的定義可以這樣定義來達到我們預期的效果:

def extendList(val, list=None):
if list is None:
list = []
list.append(val)
return list
調用修改後的函數,輸出是:

list1 = [10]
list2 = [123]
list3 = [‘a’]
下面代碼的輸出是什麼?請解釋你的答案

class Parent(object):
x = 1

class Child1(Parent):
pass

class Child2(Parent):
pass

print Parent.x, Child1.x, Child2.x
Child1.x = 2
print Parent.x, Child1.x, Child2.x
Parent.x = 3
print Parent.x, Child1.x, Child2.x
輸出爲:

1 1 1
1 2 1
3 2 3
讓很多人感到疑惑和驚訝的是,最後一行的輸出竟然不是3 2 1而是3 2 3. 爲什麼修改了Parent.X的值會影響到Child2.x,但是同時又沒有改變Child1.x的值呢?

這個問題的關鍵在於,在python中,類中的變量在內部被當作字典處理。如果一個變量名在當前類的字典中沒有被發現,系統將會在這個類的祖先(例如,它的父類)中繼續尋找,直到找到爲止(如果一個變量名在這個類和這個類的祖先中都沒有,那麼將會引發一個AttributeError錯誤)

因此,在父類中將變量x賦值爲1,那麼x變量將可以被當前類和所有這個類的子類引用。這就是爲什麼第一個print語句輸出爲1 1 1.

接下來,如果它的子類覆蓋了這個值(例如, 當我們執行Child1.x = 2),那麼這個變量的值僅僅在這個子類中發生了改變。這就是爲什麼第二個print語句輸出1 2 1

最後,如果父類改變了這個變量的值(例如,我們執行Parent.x = 3),所有沒有覆蓋這個參數值的子類(在這個例子中覆蓋了參數的就是Child2)都會受到影響,這就是爲什麼第三個print語句的輸出爲3 2 3

下面代碼的輸出是什麼?請解釋你的答案

def multipliers():
return [lambda x : i * x for i in range(4)]

print [m(2) for m in multipliers()]
怎麼修改multipliers的定義才能達到期望的效果?

上面代碼的輸出是[6, 6, 6, 6](不是[0, 2, 4, 6]).

原因是Python的閉包是延遲綁定(late binding)的。這表明在閉包中使用的變量直到內層函數被調用的時候纔會被查找。結果是,當調用multipliers()返回的函數時,i參數的值會在這時被在調用環境中查找。所以,無論調用返回的哪個函數,for循環此時已經結束,i等於它最終的值3。因此,所有返回的函數都要乘以傳遞過來的3,因爲上面的代碼傳遞了2作爲參數,所以他們都返回了6(即,3 * 2)

(順便提一句,正如在書《The Hitchhiker’s Guide to Python》中提出來的一樣, 有一種廣泛傳播的誤解認爲這個問題和lambda表達式有關,事實並非如此。通過labda表達式產生的函數並沒有什麼特別之處,使用普通的def定義的函數的行爲和lambda表達式產生的函數的行爲是一樣的.)

下面是一些可以繞過這個問題的方法。

方法一是像下面一樣使用Python的生成器(generator)

def multipliers():
for i in range(4): yield lambda x : i * x
另一個方法是創造一個閉包,通過使用一個默認參數來立即綁定它的參數

def multipliers():
return [lambda x, i=i : i * x for i in range(4)]
或者,你也可以使用functools.partial函數:

from functools import partial
from operator import mul

def multipliers():
return [partial(mul, i) for i in range(4)]
考慮下面的代碼片段:

  1. list = [ [ ] ] * 5
  2. list # output?
  3. list[0].append(10)
  4. list # output?
  5. list[1].append(20)
  6. list # output?
  7. list.append(30)
  8. list # output?
    第2,4,6,8行的輸出是什麼?解釋你的答案.

輸出如下:

[[], [], [], [], []]
[[10], [10], [10], [10], [10]]
[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20]]
[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20], 30]
下面是解釋:

第一行的輸出憑直覺就能知道,很容易理解。即:list = [ [ ] ] * 5創建了一個元素是5個列表的列表。

但是,這裏要理解的關鍵是,list = [ [ ] ] * 5並沒有創建一個包含5個不同列表的列表。創建的這個列表裏的5個列表,是對同一個列表的引用(a a list of 5 references to the same list)。理解了這些,你就能更好地理解餘下的輸出。

list[0].append(10)將數字10添加到第一個列表。但是由於5個列表是對同一個列表的引用,所以輸出是[[10], [10], [10], [10], [10]]。

同樣的,list[1].append(20)將20追加到第二個列表。但是同樣,由於這5個列表引用同一個列表,所以輸出:[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20]].

相比之下, list.append(30)是將一個全新的元素追加到“外層”的列表,所以產生了這樣的輸出:[[10, 20], [10, 20], [10, 20], [10, 20], [10, 20], 30].

有一個擁有N個元素的列表,用一個列表解析式生成一個新的列表,元素的值同時滿足以下條件:

(a) 偶數,以及
(b) 在原列表中,索引爲偶數
例如,如果list[2]的值是偶數,那麼這個元素應該也被包含在新列表中,因爲它在原列表中的索引也是偶數(即 2). 但是, 如果list[3]是偶數,那這個值不應該被包含在新列表中,因爲它在原列表中的索引是一個奇數。

一個簡單的解法如下:

[x for x in list[::2] if x%2 == 0]
例如,給出下面的列表:

0 1 2 3 4 5 6 7 8

list = [ 1 , 3 , 5 , 8 , 10 , 13 , 18 , 36 , 78 ]
列表解析式[x for x in list[::2] if x%2 == 0] 會生成:

[10, 18, 78]
這個表達式首先取列表中索引是偶數的數字,然後過濾掉所有的奇數.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章