Python函数默认参数陷阱
閱讀目錄
- 可變對象與不可變對象
- 函數默認參數陷阱
- 默認參數原理
- 避免
- 修飾器方法
- 擴展
- 參考
請看如下一段程序:
def extend_list(v, li=[]): li.append(v) return li list1 = extend_list(10) list2 = extend_list(123, []) list3 = extend_list('a') print(list1) print(list2) print(list3) print(list1 is list3)請先猜想打印的結果:
是不是這樣:
[10] [123] [a] False?
但是,實際的打印效果
?
?請看如下解釋:
<!-- lang: python --> # 函數的定義相當于一次類型構造,默認值只在此時解析一次。 # 而函數調用時不會重新執行默認參數的構造。所以,如果使用了字典,列表這樣的可變類型。 # 而又要在函數體內修改它,可能會出現意想不到的效果. def a(b=[]): b.append('hi') print b In [11]: a() ['hi'] In [12]: a() ['hi', 'hi'] In [13]: a(['2']) ['2', 'hi'] In [14]: a() ['hi', 'hi', 'hi'] In [15]: a.func_defaults Out[15]: (['hi', 'hi', 'hi'],) # 解決方法:參數默認值使用None賦值 def(b = None): b = b or [] pass # 類屬性也有類似問題 class A(object): x = [] def __init__(self, c): self.x.append(c) # 這里的x搜索到類級別的x了而非實例的, # 因實例級別的x未事先定義 In [36]: a1, a2 = A(1), A(2) In [37]: a1.x, a2.x Out[37]: ([1, 2], [1, 2]) # 解決方法, 實例級別的屬性事先定義 class B(object): x = [] def __init__(self, c): self.x = [] # 此處實例屬性有x,所以先搜索到此 self.x.append(c) In [38]: b1, b2 = B(1), B(2) In [39]: b1.x, b2.x Out[39]: ([1], [2])?
?
python可變對象做默認參數陷阱
可變對象與不可變對象
python中,萬物皆對象。python中不存在所謂的傳值調用,一切傳遞的都是對象的引用,也可以認為是傳址。
python中,對象分為可變(mutable)和不可變(immutable)兩種類型。
元組(tuple)、數值型(number)、字符串(string)均為不可變對象,而字典型(dictionary)和列表型(list)的對象是可變對象。
對于可變對象來說,傳址是可以改變原對象的值的,對于不可變對象來說,傳址相當于多了一個指向該值(不可變)的指針
不可變對象
?
可變對象
?
?
函數默認參數陷阱
下面這一段程序
#! /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)?
輸出結果是多少?
[0] [0, 1] [0, 1, 2] [0, 1, 2, 3] [0, 1, 2, 3, 4]而不是想象的
[0] [1] [2] [3] [4]而如果想達到第二種效果,只需將obj = demo_list() 改為obj = demo_list(l=[]) 即可
默認參數原理
官方文檔中的一句話:
Default values are computed once, then re-used.
默認值是被重復使用的
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.
所以當默認參數值是可變對象的時候,那么每次使用該默認參數的時候,其實更改的是同一個變量
當python執行def語句時,它會根據編譯好的函數體字節碼和命名空間等信息新建一個函數對象,并且會計算默認參數的值。函數的所有構成要素均可通過它的屬性來訪問,比如可以用funcname屬性來查看函數的名稱。所有默認參數值則存儲在函數對象的defaults_屬性中,它的值為一個列表,列表中每一個元素均為一個默認參數的值
其中默認參數相當于函數的一個屬性
Functions in Python are first-class objects, and not only a piece of code.
我們可以這樣解讀:函數也是對象,因此定義的時候就被執行,默認參數是函數的屬性,它的值可能會隨著函數被調用而改變。其他對象不都是如此嗎?
避免
使用可變參數作為默認值可能導致意料之外的行為。為了防止出現這種情況,最好使用None值,并且在后面加上檢查代碼
def __init__(self, l=None): if not l: self.l = [] else: self.l = l?
在這里將None用作占位符來控制參數l的默認值。不過,有時候參數值可能是任意對象(包括None),這時候就不能將None作為占位符。你可以定義一個object對象作為占位符,如下面例子:
sentinel = object() def func(var=sentinel): if var is sentinel: pass else: print var?
修飾器方法
Python cookbook中也提到了這個方法,為了避免對每一個函數中每一個可能為None的對象進行一個if not l的判斷,使用可更優雅的修飾器方法
import copy def freshdefault(f): fdefaults = f.func_defaults def refresher(*args,**kwds): f.func_defaults = deepcopy(fdefaults) return f(*args,**kwds) return refresh?
這段代碼也再次認證了默認參數是函數的一個屬性這一事實
擴展
python中函數的默認值只會被執行一次,(和靜態變量一樣,靜態變量初始化也是被執行一次)。Python可以通過函數的默認值來實現靜態變量的功能。
轉載于:https://www.cnblogs.com/fmgao-technology/p/9105346.html
總結
以上是生活随笔為你收集整理的Python函数默认参数陷阱的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何在一台电脑上管理/切换多个githu
- 下一篇: PHP怎么读写XML?(四种方法)