values - python default none




“最小的驚訝”和可變的默認論證 (20)

任何修補Python足夠長的人都被以下問題咬傷(或撕成碎片):

def foo(a=[]):
    a.append(5)
    return a

Python新手會期望這個函數總是返回一個只包含一個元素的列表: [5] 。 結果卻非常不同,而且非常驚人(對於新手來說):

>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()

我的一位經理曾經第一次遇到這個功能,並稱其為該語言的“戲劇性設計缺陷”。 我回答說這個行為有一個潛在的解釋,如果你不理解內部,那確實非常令人費解和意想不到。 但是,我無法回答(對自己)以下問題:在函數定義中綁定默認參數的原因是什麼,而不是在函數執行時? 我懷疑經驗豐富的行為是否具有實際用途(誰真的在C中使用靜態變量,沒有繁殖錯誤?)

編輯

Baczek做了一個有趣的例子。 再加上你的大部分評論和尤其是Utaal,我進一步闡述了:

>>> def a():
...     print("a executed")
...     return []
... 
>>>            
>>> def b(x=a()):
...     x.append(5)
...     print(x)
... 
a executed
>>> b()
[5]
>>> b()
[5, 5]

對我而言,似乎設計決策是相對於放置參數範圍的位置:在函數內部還是“與它一起”?

在函數內部進行綁定意味著當調用函數時, x有效地綁定到指定的默認值,而不是定義,這會產生一個深層次的缺陷: def線在綁定的一部分意義上是“混合”的(函數對象的)將在定義時發生,並且在函數調用時發生部分(默認參數的賦值)。

實際行為更加一致:執行該行時,該行的所有內容都會得到評估,這意味著在函數定義中。


Python:可變默認參數

在將函數編譯為函數對象時,將計算默認參數。當函數使用該函數多次時,它們是並保持相同的對象。

當它們是可變的時,當變異時(例如,通過向其添加元素),它們在連續調用時保持變異。

它們保持變異,因為它們每次都是同一個物體。

等效代碼:

由於列表在編譯和實例化函數對象時綁定到函數,因此:

def foo(mutable_default_argument=[]): # make a list the default argument
    """function that uses a list"""

幾乎完全等同於:

_a_list = [] # create a list in the globals

def foo(mutable_default_argument=_a_list): # make it the default argument
    """function that uses a list"""

del _a_list # remove globals name binding

示範

這是一個演示 - 您可以在每次引用它們時驗證它們是否是同一個對象

  • 看到在函數編譯成函數對象之前創建了列表,
  • 觀察每次引用列表時id是相同的,
  • 觀察當第二次調用使用它的函數時列表保持更改,
  • 觀察輸出源的輸入順序(我方便地為你編號):

example.py

print('1. Global scope being evaluated')

def create_list():
    '''noisily create a list for usage as a kwarg'''
    l = []
    print('3. list being created and returned, id: ' + str(id(l)))
    return l

print('2. example_function about to be compiled to an object')

def example_function(default_kwarg1=create_list()):
    print('appending "a" in default default_kwarg1')
    default_kwarg1.append("a")
    print('list with id: ' + str(id(default_kwarg1)) + 
          ' - is now: ' + repr(default_kwarg1))

print('4. example_function compiled: ' + repr(example_function))


if __name__ == '__main__':
    print('5. calling example_function twice!:')
    example_function()
    example_function()

並運行它python example.py

1. Global scope being evaluated
2. example_function about to be compiled to an object
3. list being created and returned, id: 140502758808032
4. example_function compiled: <function example_function at 0x7fc9590905f0>
5. calling example_function twice!:
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a']
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a', 'a']

這是否違反了“最不驚訝”的原則?

這種執行順序經常讓Python的新用戶感到困惑。如果您了解Python執行模型,那麼它就變得非常期待。

新Python用戶的常用指令:

但這就是為什麼對新用戶的通常指令是創建這樣的默認參數:

def example_function_2(default_kwarg=None):
    if default_kwarg is None:
        default_kwarg = []

這使用None singleton作為sentinel對象來告訴函數我們是否得到了除默認值之外的參數。如果我們沒有參數,那麼我們實際上想要使用一個新的空列表[],作為默認值。

正如關於控制流教程部分所說:

如果您不希望在後續調用之間共享默認值,則可以編寫如下函數:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

建築

在函數調用中分配默認值是代碼氣味。

def a(b=[]):
    pass

這是功能的簽名,沒有任何好處。不僅僅是因為其他答案所描述的問題。我不會在這裡談到這一點。

這個功能旨在做兩件事。創建一個新列表,並執行一個功能,最有可能在所述列表上。

當我們從乾淨的代碼實踐中學習時,做兩件事的函數是壞函數。

用多態來攻擊這個問題,我們會擴展python列表或在類中包裝一個,然後在它上面執行我們的函數。

但是等你說,我喜歡我的單行。

好吧,猜猜看。代碼不僅僅是一種控制硬件行為的方法。這是一種方式:

  • 與其他開發人員溝通,處理相同的代碼。

  • 能夠在出現新要求時改變硬件的行為。

  • 在兩年後再次拿起代碼進行上述更改後,能夠理解程序的流程。

不要為自己留下定時炸彈以便以後拿起。

將這個函數分成它所做的兩件事,我們需要一個類

class ListNeedsFives(object):
    def __init__(self, b=None):
        if b is None:
            b = []
        self.b = b

    def foo():
        self.b.append(5)

執行者

a = ListNeedsFives()
a.foo()
a.b

為什麼這比將上述所有代碼混合到一個函數中更好。

def dontdothis(b=None):
    if b is None:
        b = []
    b.append(5)
    return b

為什麼不這樣做?

除非您的項目失敗,否則您的代碼將繼續存在。很可能你的功能不僅僅是這個。製作可維護代碼的正確方法是將代碼分成具有適當限制範圍的原子部分。

對於任何完成面向對象編程的人來說,類的構造函數是一個非常普遍認可的組件。在構造函數中放置處理列表實例化的邏輯會使認知負載理解代碼的作用更小。

該方法foo()不返回列表,為什麼不呢?

在返回獨立列表時,您可以認為按照您的意願行事是安全的。但它可能不是,因為它也被對象共享a。強制用戶引用它以ab提醒他們列表所屬的位置。任何想要修改的新代碼ab自然會放在它所屬的類中。

def dontdothis(b=None):簽名功能沒有這些優勢。


你為什麼不反省?

我真的驚訝沒有人對可調用的Python( 23適用)提供了深刻的內省。

給定一個簡單的小函數func定義為:

>>> def func(a = []):
...    a.append(5)

當Python遇到它時,它要做的第一件事是編譯它以便為這個函數創建一個code對象。 在完成此編譯步驟時, Python會計算 *,然後在函數對象本身中存儲默認參數(此處為空列表[] 。 正如最常見的回答所述:列表a現在可以被視為函數func成員

所以,讓我們做一些內省,一個前後檢查列表如何在函數對像中擴展。 我正在使用Python 3.x ,對於Python 2同樣適用(在Python 2中使用__defaults__func_defaults ;是的,兩個名稱用於相同的事情)。

執行前的功能:

>>> def func(a = []):
...     a.append(5)
...     

在Python執行此定義之後,它將採用指定的任何默認參數(此處為a = [] )並將它們塞入函數對象的__defaults__屬性中 (相關部分:Callables):

>>> func.__defaults__
([],)

好的,所以一個空列表作為__defaults__的單個條目,正如預期的那樣。

執行後的功能:

現在讓我們執行這個函數:

>>> func()

現在,讓我們__defaults__一下__defaults__

>>> func.__defaults__
([5],)

驚訝? 對象內部的值發生了變化! 現在,對該函數的連續調用將簡單地附加到該嵌入式list對象:

>>> func(); func(); func()
>>> func.__defaults__
([5, 5, 5, 5],)

所以,你有它,這個'缺陷'發生的原因是因為默認參數是函數對象的一部分。 這裡沒有什麼奇怪的事情,這一切都有點令人驚訝。

解決此問題的常見解決方案是使用None作為默認值,然後在函數體中初始化:

def func(a = None):
    # or: a = [] if a is None else a
    if a is None:
        a = []

由於每次都重新執行函數體,如果沒有為a傳遞參數,則總是會得到一個全新的空列表。

要進一步驗證__defaults__中的列表與函數func中使用的列表相同,您只需更改函數以返回函數體內使用的列表的id 。 然後,將它與__defaults__的列表( __defaults__ position [0] )進行比較,您將看到它們是如何確實引用相同的列表實例的:

>>> def func(a = []): 
...     a.append(5)
...     return id(a)
>>>
>>> id(func.__defaults__[0]) == func()
True

一切都具有內省的力量!

*要驗證Python在編譯函數期間評估默認參數,請嘗試執行以下操作:

def bar(a=input('Did you just see me without calling the function?')): 
    pass  # use raw_input in Py2

正如您將注意到的那樣,在構建函數並將其綁定到名稱bar之前調用input()


AFAICS尚未發布documentation的相關部分:

執行函數定義時,將評估默認參數值。 這意味著在定義函數時,表達式被計算一次,並且每個調用使用相同的“預先計算”值。 這對於理解默認參數何時是可變對象(例如列表或字典)尤其重要:如果函數修改對象(例如,通過將項附加到列表),則默認值實際上被修改。 這通常不是預期的。 解決這個問題的方法是使用None作為默認值,並在函數體中顯式測試它[...]


你可以通過替換對象(因此與範圍的關係)來繞過這個:

def foo(a=[]):
    a = list(a)
    a.append(5)
    return a

醜陋,但它的工作原理。


你問的是為什麼這個:

def func(a=[], b = 2):
    pass

在內部不等同於:

def func(a=None, b = None):
    a_default = lambda: []
    b_default = lambda: 2
    def actual_func(a=None, b=None):
        if a is None: a = a_default()
        if b is None: b = b_default()
    return actual_func
func = func()

除了顯式調用func(None,None)的情況,我們將忽略它。

換句話說,為什麼不存儲它們中的每一個,而不是評估默認參數,並在調用函數時對它們進行評估?

答案可能就在那裡 - 它會有效地將每個具有默認參數的函數轉換為閉包。 即使它全部隱藏在解釋器中而不是完全關閉,數據也必須存儲在某個地方。 它會更慢並且使用更多內存。


假設您有以下代碼

fruits = ("apples", "bananas", "loganberries")

def eat(food=fruits):
    ...

當我看到吃的聲明時,最不令人驚訝的是認為如果沒有給出第一個參數,它將等於元組("apples", "bananas", "loganberries")

但是,假設後面的代碼,我會做類似的事情

def some_random_function():
    global fruits
    fruits = ("blueberries", "mangos")

然後,如果默認參數在函數執行而不是函數聲明中被綁定,那麼我會驚訝地發現水果已被改變(以非常糟糕的方式)。 這比發現上面的foo函數改變列表更令人驚訝的IMO。

真正的問題在於可變變量,並且所有語言都在某種程度上存在這個問題。 這是一個問題:假設在Java中我有以下代碼:

StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) );  // does this work?

現在,我的地圖在放入地圖時是否使用StringBuffer鍵的值,還是通過引用存儲鍵? 無論哪種方式,有人都感到驚訝; 嘗試使用與他們放置的對象相同的值從對像中取出對象的人,或者即使他們使用的鍵實際上是相同的也無法檢索對象的人用於將其放入映射的對象(這實際上是Python不允許將其可變內置數據類型用作字典鍵的原因)。

你的例子是一個很好的例子,Python新人會感到驚訝和被咬。 但是我認為,如果我們“修復”了這個問題,那麼這只會產生一種不同的情況,即他們會被咬住,而這種情況甚至會更不直觀。 而且,在處理可變變量時總是如此; 你總是遇到一些情況,根據他們正在編寫的代碼,某人可能直觀地期望一種或相反的行為。

我個人喜歡Python當前的方法:在定義函數時評估默認函數參數,並且該對象始終是默認值。 我想他們可以使用空列表進行特殊情況,但這種特殊的外殼會引起更多的驚訝,更不用說倒退不兼容了。


只需將功能更改為:

def notastonishinganymore(a = []): 
    '''The name is just a joke :)'''
    a = a[:]
    a.append(5)
    return a

嗯,原因很簡單,在執行代碼時完成綁定,並且執行函數定義,以及......定義函數時。

比較一下:

class BananaBunch:
    bananas = []

    def addBanana(self, banana):
        self.bananas.append(banana)

此代碼遭受完全相同的意外事件。 bananas是一個類屬性,因此,當您向其添加內容時,它會添加到該類的所有實例中。 原因完全一樣。

它只是“如何工作”,並且在功能案例中使其工作方式可能很複雜,並且在類的情況下可能不可能,或者至少減慢對象實例化的速度,因為你必須保持類代碼並在創建對象時執行它。

是的,這是出乎意料的。 但是一旦便士下降,它就完全適合Python的工作方式。 事實上,它是一個很好的教學輔助工具,一旦你理解了為什麼會發生這種情況,你就會更好地理解python。

這說它應該在任何優秀的Python教程中突出顯示。 因為正如你所提到的,每個人遲早都會遇到這個問題。


如果您考慮以下因素,這種行為就不足為奇了:

  1. 分配嘗試時只讀類屬性的行為,以及
  2. 函數是對象(在接受的答案中解釋得很好)。

(2)的作用已在本主題中廣泛涉及。(1)可能是引起驚訝的因素,因為這種行為在來自其他語言時並不“直觀”。

(1)關於類的Python 教程中進行了描述。嘗試將值分配給只讀類屬性:

...在最內層範圍之外找到的所有變量都是只讀的(嘗試寫入這樣的變量只會在最裡面的範圍內創建一個新的局部變量,而保持同名的外部變量不變)。

回顧原始示例並考慮以上幾點:

def foo(a=[]):
    a.append(5)
    return a

foo是一個對象,afoo(可用於foo.func_defs[0])的屬性。由於a是一個列表,a是可變的,因此是一個讀寫屬性foo。當函數被實例化時,它被初始化為由簽名指定的空列表,並且只要函數對象存在,它就可用於讀取和寫入。

foo不覆蓋默認值的情況下調用將使用該默認值foo.func_defs。在這種情況下,foo.func_defs[0]用於a函數對象的代碼範圍。對a更改的更改foo.func_defs[0],這是foo對象的一部分,並在執行代碼之間持續存在foo

現在,將此與模擬其他語言的默認參數行為的文檔中的示例進行比較,以便每次執行函數時都使用函數簽名默認值:

def foo(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L

考慮到(1)(2),可以看出為什麼這可以實現所需的行為:

  • foo函數對像被實例化時,foo.func_defs[0]被設置為None一個不可變對象。
  • 當使用默認值執行函數時(L在函數調用中未指定參數),foo.func_defs[0]None)在本地作用域中可用L
  • L = [],分配無法成功foo.func_defs[0],因為該屬性是只讀的。
  • Per (1)在本地範圍內創建一個也命名的新局部變量L並用於函數調用的其餘部分。foo.func_defs[0]因此未來的調用保持不變foo

已經很忙的主題,但從我在這裡讀到的內容,以下內容幫助我了解它是如何在內部工作的:

def bar(a=[]):
     print id(a)
     a = a + [1]
     print id(a)
     return a

>>> bar()
4484370232
4484524224
[1]
>>> bar()
4484370232
4484524152
[1]
>>> bar()
4484370232 # Never change, this is 'class property' of the function
4484523720 # Always a new object 
[1]
>>> id(bar.func_defaults[0])
4484370232

我將演示一個替代結構,將默認列表值傳遞給函數(它對字典同樣有效)。

正如其他人已經廣泛評論的那樣,list參數在定義時與函數綁定,而不是在執行時。由於列表和字典是可變的,因此對此參數的任何更改都將影響對此函數的其他調用。因此,對函數的後續調用將接收此共享列表,該列表可能已被該函數的任何其他調用更改。更糟糕的是,兩個參數同時使用此函數的共享參數,而忽略了另一個參數所做的更改。

錯誤的方法(可能......)

def foo(list_arg=[5]):
    return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
# The value of 6 appended to variable 'a' is now part of the list held by 'b'.
>>> b
[5, 6, 7]  

# Although 'a' is expecting to receive 6 (the last element it appended to the list),
# it actually receives the last element appended to the shared list.
# It thus receives the value 7 previously appended by 'b'.
>>> a.pop()             
7

您可以使用以下命令驗證它們是同一個對象id

>>> id(a)
5347866528

>>> id(b)
5347866528

Per Brett Slatkin的“有效的Python:編寫更好的Python的59種方法”,第20項:使用None和文檔字符串來指定動態默認參數p.48

在Python中實現所需結果的約定是提供默認值,None並記錄docstring中的實際行為。

此實現確保對函數的每次調用都接收默認列表或傳遞給函數的列表。

首選方法

def foo(list_arg=None):
   """
   :param list_arg:  A list of input values. 
                     If none provided, used a list with a default value of 5.
   """
   if not list_arg:
       list_arg = [5]
   return list_arg

a = foo()
a.append(6)
>>> a
[5, 6]

b = foo()
b.append(7)
>>> b
[5, 7]

c = foo([10])
c.append(11)
>>> c
[10, 11]

可能存在“錯誤方法”的合法用例,其中程序員希望共享默認列表參數,但這更可能是規則之外的例外。


我曾經認為在運行時創建對象將是更好的方法。 我現在不太確定,因為你確實失去了一些有用的功能,儘管它可能是值得的,不管只是為了防止新手混淆。 這樣做的缺點是:

1.表現

def foo(arg=something_expensive_to_compute())):
    ...

如果使用了調用時評估,則每次使用函數時都會調用昂貴的函數而不使用參數。 您要么為每次調用付出昂貴的代價,要么需要在外部手動緩存該值,污染您的命名空間並添加詳細程度。

2.強制綁定參數

一個有用的技巧是在創建lambda時將lambda的參數綁定到變量的當前綁定。 例如:

funcs = [ lambda i=i: i for i in range(10)]

這將返回分別返回0,1,2,3 ...的函數列表。 如果行為發生了變化,他們會將i綁定到i調用時間值,這樣你就可以得到一個返回9的函數列表。

否則實現此方法的唯一方法是使用i綁定創建進一步的閉包,即:

def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]

3.內省

考慮一下代碼:

def foo(a='test', b=100, c=[]):
   print a,b,c

我們可以使用inspect模塊獲取有關參數和默認值的信息

>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))

此信息對於文檔生成,元編程,裝飾器等非常有用。

現在,假設可以更改默認值的行為,以便這相當於:

_undefined = object()  # sentinel value

def foo(a=_undefined, b=_undefined, c=_undefined)
    if a is _undefined: a='test'
    if b is _undefined: b=100
    if c is _undefined: c=[]

但是,我們已經失去了內省的能力,並且看到了默認參數什麼。 因為沒有構造對象,所以我們不能在沒有實際調用函數的情況下抓住它們。 我們能做的最好的事情是存儲源代碼並將其作為字符串返回。


我有時會利用此行為替代以下模式:

singleton = None

def use_singleton():
    global singleton

    if singleton is None:
        singleton = _make_singleton()

    return singleton.use_me()

如果singleton僅使用use_singleton,我喜歡以下模式作為替代:

# _make_singleton() is called only once when the def is executed
def use_singleton(singleton=_make_singleton()):
    return singleton.use_me()

我已經將它用於實例化訪問外部資源的客戶端類,也用於創建用於memoization的dicts或列表。

由於我不認為這種模式是眾所周知的,所以我會做一個簡短的評論,以防止未來的誤解。


當我們這樣做時:

def foo(a=[]):
    ...

......我們的論點分配a到一個不願透露姓名的列表,如果主叫方沒有通過的值。

為了使討論更簡單,讓我們暫時給這個未命名的列表命名。怎麼樣pavlo

def foo(a=pavlo):
   ...

在任何時候,如果調用者沒有告訴我們什麼a,我們重用pavlo

如果pavlo是可變的(可修改的),並foo最終修改它,我們注意到下一次foo調用的效果而不指定a

所以這就是你所看到的(記住,pavlo初始化為[]):

 >>> foo()
 [5]

現在,pavlo是[5]。

foo()再次呼叫再次修改pavlo

>>> foo()
[5, 5]

指定a致電時foo(),確保pavlo沒有被觸及。

>>> ivan = [1, 2, 3, 4]
>>> foo(a=ivan)
[1, 2, 3, 4, 5]
>>> ivan
[1, 2, 3, 4, 5]

所以,pavlo還是[5, 5]

>>> foo()
[5, 5, 5]

這實際上與默認值無關,除了它在您使用可變默認值編寫函數時經常出現意外行為。

>>> def foo(a):
    a.append(5)
    print a

>>> a  = [5]
>>> foo(a)
[5, 5]
>>> foo(a)
[5, 5, 5]
>>> foo(a)
[5, 5, 5, 5]
>>> foo(a)
[5, 5, 5, 5, 5]

此代碼中沒有默認值,但您會得到完全相同的問題。

問題是,foo修改從主叫方傳遞一個可變變量,當主叫方不指望這個。像這樣的代碼如果函數被調用就好了append_5;然後調用者將調用該函數以修改它們傳入的值,並且可以預期行為。但是這樣的函數不太可能採用默認參數,並且可能不會返回列表(因為調用者已經具有對該列表的引用;它剛剛傳入的那個)。

foo具有默認參數的原始文件不應該修改a它是顯式傳入還是獲得默認值。您的代碼應該單獨留下可變參數,除非從context / name / documentation中清楚地知道參數應該被修改。使用作為參數傳遞的可變值作為本地臨時值是一個非常糟糕的主意,無論我們是否使用Python,是否涉及默認參數。

如果您需要在計算某些東西時破壞性地操縱本地臨時,並且您需要從參數值開始操作,則需要復制。


這裡的解決方案是:

  1. 使用None作為默認值(或隨機數object),以及交換機上,在運行時創建自己的價值觀;要么
  2. 使用a lambda作為默認參數,並在try塊中調用它以獲取默認值(這是lambda抽象的用途)。

第二個選項很好,因為函數的用戶可以傳入一個可調用的,可能已經存在(例如a type


1)所謂的“可變默認參數”問題通常是一個特殊的例子,它表明:
“所有具有此問題的函數也會受到類似實際參數的副作用問題的影響 ”,
這違反了函數式編程的規則,通常是不可思議的,應該固定在一起。

例:

def foo(a=[]):                 # the same problematic function
    a.append(5)
    return a

>>> somevar = [1, 2]           # an example without a default parameter
>>> foo(somevar)
[1, 2, 5]
>>> somevar
[1, 2, 5]                      # usually expected [1, 2]

解決方案:一個副本
的絕對安全解決方案是copydeepcopy輸入,然後再去做任何與複製對象。

def foo(a=[]):
    a = a[:]     # a copy
    a.append(5)
    return a     # or everything safe by one line: "return a + [5]"

許多內置可變類型有像拷貝的方法,some_dict.copy()或者some_set.copy(),也可以像簡單的複製somelist[:]list(some_list)。每個對像也可以通過copy.copy(any_object)或更徹底地複制copy.deepcopy()(如果可變對象由可變對象組成,則後者有用)。一些對象基本上是基於像“文件”對像這樣的副作用,並且不能通過複製有意義地再現。copying

類似SO問題的示例問題

class Test(object):            # the original problematic class
  def __init__(self, var1=[]):
    self._var1 = var1

somevar = [1, 2]               # an example without a default parameter
t1 = Test(somevar)
t2 = Test(somevar)
t1._var1.append([1])
print somevar                  # [1, 2, [1]] but usually expected [1, 2]
print t2._var1                 # [1, 2, [1]] but usually expected [1, 2]

它不應該既不保存在此函數返回的實例的任何公共屬性中。(假設實例的私有屬性不應該按照約定從這個類或子類的外部修改。即_var1是私有屬性)

結論:
輸入參數對像不應該就地修改(變異),也不應該綁定到函數返回的對像中。(如果我們優先編程沒有強烈推薦的副作用。請參閱Wiki關於“副作用”(前兩段在這方面是相關的。)。)

2)
只有當需要對實際參數產生副作用但在默認參數上不需要時,有用的解決方案才是def ...(var1=None): if var1 is None: var1 = [] More..

3)在某些情況下,默認參數的可變行為很有用


這個“蟲子”給了我很多加班時間!但我開始看到它的潛在用途(但我還是喜歡它在執行時,仍然)

我會給你我看到的一個有用的例子。

def example(errors=[]):
    # statements
    # Something went wrong
    mistake = True
    if mistake:
        tryToFixIt(errors)
        # Didn't work.. let's try again
        tryToFixItAnotherway(errors)
        # This time it worked
    return errors

def tryToFixIt(err):
    err.append('Attempt to fix it')

def tryToFixItAnotherway(err):
    err.append('Attempt to fix it by another way')

def main():
    for item in range(2):
        errors = example()
    print '\n'.join(errors)

main()

打印以下內容

Attempt to fix it
Attempt to fix it by another way
Attempt to fix it
Attempt to fix it by another way

這是一種性能優化。作為此功能的結果,您認為這兩個函數調用中的哪一個更快?

def print_tuple(some_tuple=(1,2,3)):
    print some_tuple

print_tuple()        #1
print_tuple((1,2,3)) #2

我會給你一個提示。這是反彙編(參見http://docs.python.org/library/dis.html):

# 1

0 LOAD_GLOBAL              0 (print_tuple)
3 CALL_FUNCTION            0
6 POP_TOP
7 LOAD_CONST               0 (None)
10 RETURN_VALUE

# 2

 0 LOAD_GLOBAL              0 (print_tuple)
 3 LOAD_CONST               4 ((1, 2, 3))
 6 CALL_FUNCTION            1
 9 POP_TOP
10 LOAD_CONST               0 (None)
13 RETURN_VALUE

我懷疑經驗豐富的行為是否具有實際用途(誰真的在C中使用靜態變量,沒有繁殖錯誤?)

正如你所看到的,用一成不變的默認參數時提高性能。如果它是一個經常調用的函數,或者默認參數需要很長時間來構造,這可能會有所不同。另外,請記住,Python不是C.在C中,你有非常自由的常量。在Python中,你沒有這個好處。







least-astonishment