標籤:
先看代碼:
def foo(a, b =[]): b.append(a) print bfoo(1)foo(2)
結果想象中是:
>>>
[1]
[2]
>>>
實際上是:
>>>
[1]
[1, 2]
>>>
查看官方文檔:https://docs.python.org/2.7/tutorial/controlflow.html#default-argument-values
The default values are evaluated at the point of function definition in the defining scope.
The default value is evaluated only once.
This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes.
大致翻譯下:
預設參數僅僅在def語句被執行的時候被賦值,而且就賦值一次。遇到可變類型的list或者dic,就可能出現一些想不到的不同呢。
在知乎某問題上:http://www.zhihu.com/question/21924859 提到了當python執行def的時候,會根據編譯好的位元組碼和命名空間建立一個函數,並且會計算預設參數的值。預設參數作為這個建立的函數的一個屬性(沒錯,函數也是一個對象),看下面:
>>> def foo(bar=[]):... return bar>>> foo.func_name'foo'>>> foo.func_defaults([],)
在陷阱!python參數預設值 文章提到一段代碼:
def a(): print 'a executed' return []def b(x=a()): print 'id(x):',id(x) x.append(5) print 'x:',xfor i in range(2): print '*'*20 b() print 'b.__defaults__:',b.__defaults__ print 'id(b.__defaults__[0]):',id(b.__defaults__[0])for i in range(2): print '*'*20 b(list()) print 'b.__defaults__:',b.__defaults__ print 'id(b.__defaults__[0]):',id(b.__defaults__[0])
結果:
>>> a executed********************id(x): 48161096x: [5]b.__defaults__: ([5],)id(b.__defaults__[0]): 48161096********************id(x): 48161096x: [5, 5]b.__defaults__: ([5, 5],)id(b.__defaults__[0]): 48161096********************id(x): 48033160x: [5]b.__defaults__: ([5, 5],)id(b.__defaults__[0]): 48161096********************id(x): 48032776x: [5]b.__defaults__: ([5, 5],)id(b.__defaults__[0]): 48161096>>>
分析下:
從“a executed”看出:在執行def之前,就已經把參數預設值給計算出來了。
一共四次調用b(),前兩次沒有傳參,用的是預設參數。下面結果可以看出x的確是使用預設參數:
id(x): 48161096
id(b.__defaults__[0]): 48161096
第三次b(),傳參list(),所以x是自己的id,不需要用預設參數了:
id(x): 48033160
id(b.__defaults__[0]): 48161096
第四次b(),傳參list(),x又是一個新的id,不需要用預設參數了:
id(x): 48032776
id(b.__defaults__[0]): 48161096
總結下:
python的預設參數就在b.__defaults__這個列表中,遇到需要用到預設參數的就會調用它,它就安安靜靜呆在那裡,一個人。(突然畫風變了呢?)
回到題目最初的代碼:
def foo(a, b =[]): b.append(a) print bfoo(1)foo(2)
因為沒有傳入參數,所以就用預設參數,預設的b是個list啊,所以才越append越多。
官方文檔中的修改措施:
def foo(a, b=None): if b is None: b = [] b.append(a) print bfoo(1)foo(2)
=========================增加於20150808===========================
看到一段代碼,在class中的def中,預設參數的id也是同一個嗎?
如果我有很多個class的執行個體obj,每個obj的id應該是不一樣的吧?obj中的函數的預設參數的id應該是不一樣的吧。
class demo_list: def __init__(self, l=[]): self.l = l print 'id(self.l):',id(self.l) def add(self, ele): self.l.append(ele)def appender(ele): obj = demo_list() print 'id(obj):',id(obj) obj.add(ele) print obj.lif __name__ == "__main__": for i in range(5): appender(i)
結果是:
id(self.l): 49415560id(obj): 49357000[0]id(self.l): 49415560id(obj): 49277192[0, 1]id(self.l): 49415560id(obj): 49277192[0, 1, 2]id(self.l): 49415560id(obj): 49277192[0, 1, 2, 3]id(self.l): 49415560id(obj): 49277192[0, 1, 2, 3, 4]
除了第一個obj,每個obj都是指向同一個id。
每個obj的預設參數都是指向同一個id,好吧,預設參數l是同一個對象。
我們先把關注點放到預設參數上,如果我指定參數呢?
把obj = demo_list() 改成:obj = demo_list(list())
結果是:
id(self.l): 48242888id(obj): 48243400[0]id(self.l): 48163080id(obj): 48243400[1]id(self.l): 48243400id(obj): 48246024[2]id(self.l): 48246024id(obj): 48244296[3]id(self.l): 48244296id(obj): 48249800[4]
可以看到每個l都是不同的對象,並沒有用到預設參數了呢,恩,這才是對的。
然後id(obj)也大致不一樣。【此處有坑,待驗證】
關於obj的id問題,我猜是python的最佳化問題,對於小的東西比如int(-5~256)都是預分配了的,所以i=10和j=10都會最佳化成指向同一個id的對象。那麼我大膽猜測,當一個對象比較小的時候,對於相似的東西,python也是指向同一個id的對象。上述第一段代碼都指向同一個list,相似度高,同樣的obj的id就多,第二段代碼因為每個list的id都不一樣了,相似度低,obj有些id就一樣,有些就不一樣了呢。
根據上述我的猜測,我就做了一個實驗,把class demo_list變得稍微複雜一點,稍微大一點的東西,python就不容易做最佳化。
class demo_list: def __init__(self, l=[]): self.l = l print 'id(self.l):',id(self.l) def add(self, ele): self.l.append(ele) def a(self): pass def b(self): pass def c(self): pass def appender(ele): obj = demo_list(list()) print 'id(obj):',id(obj) obj.add(ele) print obj.lif __name__ == "__main__": for i in range(5): appender(i)
結果:
id(self.l): 48899656id(obj): 48898248[0]id(self.l): 48818440id(obj): 48819592[1]id(self.l): 48819592id(obj): 48901256[2]id(self.l): 48901256id(obj): 48901384[3]id(self.l): 48901384id(obj): 48898248[4]
結果看起來,果然多了三個函數a(),b(),c()後,每個obj的id都不一樣呢。
哎,這最佳化,也不知道該怎麼說它好呢。其實吧,理論上,每一個obj本來就應該不一樣id的說。
咦,那麼這個參數預設值不就可以當成一個靜態變數(雖然py沒聽過支援靜態變數的說)來使用咯?
參數預設值只被執行一次 == 靜態變數只被初始化一次。
貌似還真可以。
wow~ so cool~
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。
python 當list,dic作為預設參數的坑爹之處