摘要:所有默認(rèn)參數(shù)值則存儲(chǔ)在函數(shù)對(duì)象的屬性中,它的值為一個(gè)列表,列表中每一個(gè)元素均為一個(gè)默認(rèn)參數(shù)的值。你可以定義一個(gè)對(duì)象作為占位符,如下面例子雖然應(yīng)該避免默認(rèn)參數(shù)值為可變對(duì)象,不過(guò)有時(shí)候使用可變對(duì)象作為默認(rèn)值會(huì)收到不錯(cuò)的效果。
原文地址
在stackoverflow上看到這樣一個(gè)程序:
#! /usr/bin/env python # -*- coding: utf-8 -*- class demo_list: def __init__(self, l=[]): self.l = l def add(self, ele): self.l.append(ele) def appender(ele): obj = demo_list() obj.add(ele) print obj.l if __name__ == "__main__": for i in range(5): appender(i)
輸出結(jié)果是
[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]
有點(diǎn)奇怪,難道輸出不應(yīng)該是像下面這樣嗎?
[0]
[1]
[2]
[3]
[4]
其實(shí)想要得到上面的輸出,只需要將obj = intlist()替換為obj = intlist(l=[])。
默認(rèn)參數(shù)工作機(jī)制上面怪異的輸出簡(jiǎn)單來(lái)說(shuō)是因?yàn)椋?/p>
Default values are computed once, then re-used.
因此每次調(diào)用__init__(),返回的是同一個(gè)list。為了驗(yàn)證這一點(diǎn),下面在__init__函數(shù)中添加一條語(yǔ)句,如下:
def __init__(self, l=[]): print id(l), self.l = l
輸出結(jié)果為:
4346933688 [0]
4346933688 [0, 1]
4346933688 [0, 1, 2]
4346933688 [0, 1, 2, 3]
4346933688 [0, 1, 2, 3, 4]
可以清晰看出每次調(diào)用__init__函數(shù)時(shí),默認(rèn)參數(shù)l都是同一個(gè)對(duì)象,其id為4346933688。
關(guān)于默認(rèn)參數(shù),文檔中是這樣說(shuō)的:
Default parameter values are evaluated when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call.
為了能夠更好地理解文檔內(nèi)容,再來(lái)看一個(gè)例子:
def a(): print "a executed" return [] def b(x=a()): print "id(x): ", id(x) x.append(5) print "x: ", x for i in range(2): print "-" * 15, "Call b()", "-" * 15 b() print b.__defaults__ print "id(b.__defaults__[0]): ", id(b.__defaults__[0]) for i in range(2): print "-" * 15, "Call b(list())", "-" * 15 b(list()) print b.__defaults__ print "id(b.__defaults__[0]): ", id(b.__defaults__[0])
注意,當(dāng)python執(zhí)行def語(yǔ)句時(shí),它會(huì)根據(jù)編譯好的函數(shù)體字節(jié)碼和命名空間等信息新建一個(gè)函數(shù)對(duì)象,并且會(huì)計(jì)算默認(rèn)參數(shù)的值。函數(shù)的所有構(gòu)成要素均可通過(guò)它的屬性來(lái)訪(fǎng)問(wèn),比如可以用func_name屬性來(lái)查看函數(shù)的名稱(chēng)。所有默認(rèn)參數(shù)值則存儲(chǔ)在函數(shù)對(duì)象的__defaults__屬性中,它的值為一個(gè)列表,列表中每一個(gè)元素均為一個(gè)默認(rèn)參數(shù)的值。
好了,你應(yīng)該已經(jīng)知道上面程序的輸出內(nèi)容了吧,一個(gè)可能的輸出如下(id值可能為不同):
a executed
--------------- Call b() ---------------
id(x): 4316528512
x: [5]
([5],)
id(b.__defaults__[0]): 4316528512
--------------- Call b() ---------------
id(x): 4316528512
x: [5, 5]
([5, 5],)
id(b.__defaults__[0]): 4316528512
--------------- Call b(list()) ---------------
id(x): 4316684872
x: [5]
([5, 5],)
id(b.__defaults__[0]): 4316528512
--------------- Call b(list()) ---------------
id(x): 4316684944
x: [5]
([5, 5],)
id(b.__defaults__[0]): 4316528512
我們看到,在定義函數(shù)b(也就是執(zhí)行def語(yǔ)句)時(shí),已經(jīng)計(jì)算出默認(rèn)參數(shù)x的值,也就是執(zhí)行了a函數(shù),因此才會(huì)打印出a executed。之后,對(duì)b進(jìn)行了4次調(diào)用,下面簡(jiǎn)單分析一下:
第一次不提供默認(rèn)參數(shù)x的值進(jìn)行調(diào)用,此時(shí)使用函數(shù)b定義時(shí)計(jì)算出來(lái)的值作為x的值。所以id(x)和id(b.__defaults__[0])相等,x追加數(shù)字后,函數(shù)屬性中的默認(rèn)參數(shù)值也變?yōu)閇5];
第二次仍然沒(méi)有提供參數(shù)值,x的值為經(jīng)過(guò)第一次調(diào)用后的默認(rèn)參數(shù)值[5],然后對(duì)x進(jìn)行追加,同時(shí)也對(duì)函數(shù)屬性中的默認(rèn)參數(shù)值追加;
傳遞參數(shù)list()來(lái)調(diào)用b,此時(shí)新建一個(gè)列表作為x的值,所以id(x)不同于函數(shù)屬性中默認(rèn)參數(shù)的id值,追加5后x的值為[5];
再一次傳遞參數(shù)list()來(lái)調(diào)用b,仍然是新建列表作為x的值。
如果上面的內(nèi)容你已經(jīng)搞明白了,那么你可能會(huì)覺(jué)得默認(rèn)參數(shù)值的這種設(shè)計(jì)是python的設(shè)計(jì)缺陷,畢竟這也太不符合我們對(duì)默認(rèn)參數(shù)的認(rèn)知了。然而事實(shí)可能并非如此,更可能是因?yàn)椋?/p>
Functions in Python are first-class objects, and not only a piece of code.
我們可以這樣解讀:函數(shù)也是對(duì)象,因此定義的時(shí)候就被執(zhí)行,默認(rèn)參數(shù)是函數(shù)的屬性,它的值可能會(huì)隨著函數(shù)被調(diào)用而改變。其他對(duì)象不都是如此嗎?
可變對(duì)象作為參數(shù)默認(rèn)值?參數(shù)的默認(rèn)值為可變對(duì)象時(shí),多次調(diào)用將返回同一個(gè)可變對(duì)象,更改對(duì)象值可能會(huì)造成意外結(jié)果。參數(shù)的默認(rèn)值為不可變對(duì)象時(shí),雖然多次調(diào)用返回同一個(gè)對(duì)象,但更改對(duì)象值并不會(huì)造成意外結(jié)果。
因此,在代碼中我們應(yīng)該避免將參數(shù)的默認(rèn)值設(shè)為可變對(duì)象,上面例子中的初始化函數(shù)可以更改如下:
def __init__(self, l=None): if not l: self.l = [] else: self.l = l
在這里將None用作占位符來(lái)控制參數(shù)l的默認(rèn)值。不過(guò),有時(shí)候參數(shù)值可能是任意對(duì)象(包括None),這時(shí)候就不能將None作為占位符。你可以定義一個(gè)object對(duì)象作為占位符,如下面例子:
sentinel = object() def func(var=sentinel): if var is sentinel: pass else: print var
雖然應(yīng)該避免默認(rèn)參數(shù)值為可變對(duì)象,不過(guò)有時(shí)候使用可變對(duì)象作為默認(rèn)值會(huì)收到不錯(cuò)的效果。比如我們可以用可變對(duì)象作為參數(shù)默認(rèn)值來(lái)統(tǒng)計(jì)函數(shù)調(diào)用次數(shù),下面例子中使用collections.Counter()作為參數(shù)的默認(rèn)值來(lái)統(tǒng)計(jì)斐波那契數(shù)列中每一個(gè)值計(jì)算的次數(shù)。
def fib_direct(n, count=collections.Counter()): assert n > 0, "invalid n" count[n] += 1 if n < 3: return n else: return fib_direct(n - 1) + fib_direct(n - 2) print fib_direct(10) print fib_direct.__defaults__[0]
運(yùn)行結(jié)果如下:
89
Counter({2: 34, 1: 21, 3: 21, 4: 13, 5: 8, 6: 5, 7: 3, 8: 2, 9: 1, 10: 1})
我們還可以用默認(rèn)參數(shù)來(lái)做簡(jiǎn)單的緩存,仍然以斐波那契數(shù)列作為例子,如下:
def fib_direct(n, count=collections.Counter(), cache={}): assert n > 0, "invalid n" count[n] += 1 if n in cache: return cache[n] if n < 3: value = n else: value = fib_direct(n - 1) + fib_direct(n - 2) cache[n] = value return value print fib_direct(10) print fib_direct.__defaults__[0]
結(jié)果為:
89
Counter({2: 2, 3: 2, 4: 2, 5: 2, 6: 2, 7: 2, 8: 2, 1: 1, 9: 1, 10: 1})
這樣就快了太多了,fib_direct(n)調(diào)用次數(shù)為o(n),這里也可以用裝飾器來(lái)實(shí)現(xiàn)計(jì)數(shù)和緩存功能。
參考
Python instances and attributes: is this a bug or i got it totally wrong?
Default Parameter Values in Python
“Least Astonishment” in Python: The Mutable Default Argument
A few things to remember while coding in Python
Using Python"s mutable default arguments for fun and profit
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/37439.html
摘要:下面我們看看使用可變默認(rèn)參數(shù)時(shí)會(huì)出現(xiàn)什么莫名其妙的狀況。這就意味著如果你使用可變默認(rèn)參數(shù)并改變它,所有調(diào)用該函數(shù)的結(jié)果都是這個(gè)可變對(duì)象。同時(shí)也有注意,該函數(shù)要傳遞一個(gè)對(duì)象作為第二個(gè)參數(shù),因?yàn)楹瘮?shù)中對(duì)它進(jìn)行了操作。 showImg(https://segmentfault.com/img/bVbrFS3?w=762&h=505); 絕大多數(shù)情況下,Python是一個(gè)干凈具有一致性的語(yǔ)言。...
摘要:為了保證的可讀性,本文采用意譯而非直譯。對(duì)象的所有用法,都是上面的這種形式。其中用來(lái)生成實(shí)例,是表示所要攔截的對(duì)象,是用來(lái)定制攔截行為的對(duì)象。雖然不同的創(chuàng)建模式支持類(lèi)似的功能,但無(wú)法用隱式初始值包裝對(duì)象。 為了保證的可讀性,本文采用意譯而非直譯。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你! Proxy 介紹 使用Proxy,你可以將一只貓偽裝成一只老虎。下面大...
摘要:為了保證的可讀性,本文采用意譯而非直譯。對(duì)象的所有用法,都是上面的這種形式。其中用來(lái)生成實(shí)例,是表示所要攔截的對(duì)象,是用來(lái)定制攔截行為的對(duì)象。雖然不同的創(chuàng)建模式支持類(lèi)似的功能,但無(wú)法用隱式初始值包裝對(duì)象。 為了保證的可讀性,本文采用意譯而非直譯。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你! Proxy 介紹 使用Proxy,你可以將一只貓偽裝成一只老虎。下面大...
摘要:編程規(guī)范筆記上寫(xiě)在前面從語(yǔ)言開(kāi)始,自己陸續(xù)學(xué)習(xí)了,但是自從研究生做畢設(shè)接觸以來(lái),就愛(ài)不釋手,再也沒(méi)有動(dòng)力嘗試其他語(yǔ)言。一與的一大優(yōu)勢(shì)就是具備優(yōu)秀的可讀性,而這基于一套較為完整的公認(rèn)編程規(guī)范。如原本希望的結(jié)果是,結(jié)果卻完全一樣。 Python編程規(guī)范筆記(上) 寫(xiě)在前面: 從C語(yǔ)言開(kāi)始,自己陸續(xù)學(xué)習(xí)了C++/Java,但是自從研究生做畢設(shè)接觸Python以來(lái),就愛(ài)不釋手,再也沒(méi)有動(dòng)力嘗試...
摘要:一般使用或者調(diào)用外部腳本需要注意的是,這里的方向是相對(duì)于主程序的,所以就是子進(jìn)程的輸出,而是子進(jìn)程的輸入?;谕瑯拥脑?,假如調(diào)用了方法等待子進(jìn)程執(zhí)行完畢而沒(méi)有及時(shí)處理輸出的話(huà),就會(huì)造成死鎖。 最近有一項(xiàng)需求,要定時(shí)判斷任務(wù)執(zhí)行條件是否滿(mǎn)足并觸發(fā) Spark 任務(wù),平時(shí)編寫(xiě) Spark 任務(wù)時(shí)都是封裝為一個(gè) Jar 包,然后采用 Shell 腳本形式傳入所需參數(shù)執(zhí)行,考慮到本次判斷條件...
閱讀 2847·2021-09-28 09:45
閱讀 1511·2021-09-26 10:13
閱讀 913·2021-09-04 16:45
閱讀 3671·2021-08-18 10:21
閱讀 1099·2019-08-29 15:07
閱讀 2642·2019-08-29 14:10
閱讀 3154·2019-08-29 13:02
閱讀 2471·2019-08-29 12:31