python 當list,dic作為預設參數的坑爹之處

來源:互聯網
上載者:User

標籤:

先看代碼:

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作為預設參數的坑爹之處

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.