程式扎記: [ Python 考題 ] 5 个很好的 Python 面试题

標籤

2016年3月2日 星期三

[ Python 考題 ] 5 个很好的 Python 面试题

Source From Here 
問題一:以下的代碼的輸出將是什麼? 說出你的答案並解釋 
  1. class  Parent (object) :   
  2.     x = 1  
  3.   
  4. class  Child1 (Parent) :   
  5.     pass  
  6.   
  7. class  Child2 (Parent) :   
  8.     pass  
  9.   
  10. print Parent.x, Child1.x, Child2.x  
  11. Child1.x = 2   
  12. print Parent.x, Child1.x, Child2.x  
  13. Parent.x = 3   
  14. print Parent.x, Child1.x, Child2.x  
答案 
以上代碼的輸出是: 
1 1 1
1 2 1
3 2 3

使你困惑或是驚奇的是關於最後一行的輸出是 3 2 3 而不是 3 2 1。為什麼改變了 Parent.x 的值還會改變 Child2.x 的值,但是同時 [color=blue]Child1.x[/color] 值卻沒有改變?這個答案的關鍵是,在 Python中,類變量在內部是作為字典處理的。如果一個變量的名字沒有在當前類的字典中發現,將搜索祖先類比如父類直到被引用的變量名被找到如果這個被引用的變量名既沒有在自己所在的類又沒有在祖先類中找到,會引發一個 AttributeError 異常)。 

因此,在父類中設置 x = 1 會使得類變量 x 在引用該類和其任何子類中的值為1。這就是因為第一個 print 語句的輸出是 1 1 1; 隨後,如果任何它的子類重寫了該值(例如,我們執行語句 Child1.x = 2),然後,該值僅僅在子類中被改變。這就是為什麼第二個 print 語句的輸出是1 2 1; 最後,如果該值在父類中被改變(例如,我們執行語句 Parent.x = 3),這個改變會影響到任何未重寫該值的子類當中的值(在這個示例中被影響的子類是 Child2)。這就是為什麼第三個 print 輸出是3 2 3。 

問題二:以下的代碼的輸出將是什麼? 說出你的答案並解釋? 
  1. def  div1 (x,y) :   
  2.     print( "%s/%s = %s" % (x, y, x/y))  
  3.   
  4. def  div2 (x,y) :   
  5.     print( "%s//%s = %s" % (x, y, x//y))  
  6.   
  7. div1( 5 , 2 )  
  8. div1( 5. , 2 )  
  9. div2( 5 , 2 )  
  10. div2( 5. , 2. )  
答案 
這個答案實際依賴於你使用的是Python 2 還是 Python 3。在Python 3 中,期望的輸出是: 
5 / 2 = 2.5
5.0 / 2 = 2.5
5 //2 = 2
5.0 //2.0 = 2.0

在 Python 2 中,儘管如此,以上代碼的輸出將是: 
5 / 2 = 2
5.0 / 2 = 2.5
5 //2 = 2
5.0 //2.0 = 2.0

默認,如果兩個操作數都是整數,Python 2自動執行整型計算。結果,5/2 值為 2,然而 5./2 值為 '''2.5'''。注意,儘管如此,你可以在 Python 2 中重載這一行為(比如達到你想在Python 3 中的同樣結果),通過添加以下導入: 
  1. from __future__ import division  
也需要注意的是“雙劃線” 的 floor division(//)操作符將一直執行整除,而不管操作數的類型,這就是為什麼 5.0//2.0 值 為 2.0。 
Note. 
在 Python 3 中,/ 操作符是做浮點除法,而 // 是做整數除法(即商沒有餘數),比如 10 // 3 其結果就為 3,餘數會被截除掉,而 (-7 ) // 3 的結果卻是 -3。這個算法與其它很多編程語言不一樣,需要注意,它們的整除運算會向 0 的方向取值。而在 Python 2 中,/ 就是整除,即和 Python 3 中的 // 操作符一樣.

問題三:以下代碼將輸出什麼? 
  1. list = [ 'a' , 'b' , 'c' , 'd' , 'e' ]  
  2. print  list [ 10 :]  
答案 
>>> list = [ 'a' , 'b' , 'c' , 'd' , 'e' ]
>>> print list [10:]
[]
>>> print list[1:] // 列印從第一個元素到最後一個元素的 list
['b', 'c', 'd', 'e']
>>> list[10]
Traceback (most recent call last):
File "", line 1, in
IndexError: list index out of range

正如人們所期望的,試圖訪問一個超過列表索引值的成員將導致 IndexError比如訪問以上列表的 list[10])。儘管如此,試圖訪問一個列表的以超出列表成員數作為開始索引的切片將不會導致 IndexError,並且將僅僅返回一個空列表。 

問題四:以下的代碼的輸出將是什麼? 說出你的答案並解釋? 
  1. def  multipliers () :   
  2.     return [ lambda x : i * x for i in range( 4 )]  
  3.   
  4. print [m( 2 ) for m in multipliers()]  
答案 
以上代碼的輸出是 [6, 6, 6, 6](而不是 [0, 2, 4, 6])!!! 

這個的原因是 Python 的 閉包 (Closure的後期綁定導致的 late binding,這意味著在閉包中的變量是在內部函數被調用的時候被查找。所以結果是當任何 multipliers() 返回的函數被調用,在那時,i 的值是在它被調用時的周圍作用域中查找,到那時,無論哪個返回的函數被調用,for 循環都已經完成了,i 最後的值是 3,因此,每個返回的函數 multiplies 的值都是 3。因此一個等於 2 的值被傳遞進以上代碼,它們將返回一個值 6 (比如: 3 x 2)。順便說下,正如在 The Hitchhiker's Guide to Python] 中指出的,這裡有一點普遍的誤解,是關於 lambda 表達式的一些東西。一個 lambda 表達式創建的函數不是特殊的,和使用一個普通的 def 創建的函數展示的表現是一樣的。 

這裡有兩種方法解決這個問題。最普遍的解決方案是創建一個閉包,通過使用默認參數立即綁定它的參數。例如: 
  1. def  multipliers () :   
  2.     return [ lambda x, i=i : i * x for i in range( 4 )]  
另外一個選擇是,你可以使用 functools.partial 函數: 
  1. from functools import partial  
  2. from operator import mul  
  3.   
  4. def  multipliers () :   
  5.     return [partial(mul, i) for i in range( 4 )]  
問題五:以下的代碼的輸出將是什麼? 說出你的答案並解釋? 
  1. def  extendList (val, list=[]) :  
  2.     list.append(val)  
  3.     return list  
  4.   
  5. list1 = extendList( 10 )  
  6. list2 = extendList( 123 ,[])  
  7. list3 = extendList( 'a' )  
  8.   
  9. print  "list1 = %s" % list1  
  10. print  "list2 = %s" % list2  
  11. print  "list3 = %s" % list3  
以上代碼的輸出為: 
list1 = [10, 'a']
list2 = [123]
list3 = [10, 'a']
許多人會錯誤的認為 list1 應該等於 [10] 以及 list3 應該等於 ['a']。認為 list 的參數會在 extendList 每次被調用的時候會被設置成它的默認值 []。儘管如此,實際發生的事情是,新的默認列表僅僅只在函數被定義時創建一次。隨後當 extendList 沒有被指定的列表參數調用的時候,其使用的是同一個列表。簡單驗證如下: 
>>> def test(a, b=[]): // 定義測試函數, 每次被調用時列印出 b 的 id 值
... print("id of b=%s" % id(b))
... b.append(a)
... return b
...
>>> l1 = test(1)
id of b=139925205876680
>>> print(l1)
[1]
>>> l1.append(2) // 此時是加入元素到 b 的預設列表中!
>>> l2 = test(3)
id of b=139925205876680 // 只要 b 是使用預設值, id 值都會依樣
>>> l2
[1, 2, 3]
>>> l1
[1, 2, 3]
extendList 函數的定義可以做如下修改,但,當沒有新的 list 參數被指定的時候,會總是開始一個新列表,這更加可能是一直期望的行為: 
  1. def  extendList (val, list=None) :   
  2.     if list is  None :  
  3.         list = []  
  4.     list.append(val)  
  5.     return list  
使用這個改進的實現,輸出將是: 
list1 = [10]
list2 = [123]
list3 = ['a']


Supplement 
Python Doc - 9.9. operator — Standard operators as functions 
Python Doc - functools — Higher-order functions an...operations on callable objects

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!