1. 程式人生 > >Python函式預設引數陷阱,你知道嗎?

Python函式預設引數陷阱,你知道嗎?

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語句時,它會根據編譯好的函式體位元組碼和名稱空間等資訊新建一個函式物件,並且會計算預設引數的值。函式的所有構成要素均可通過它的屬性來訪問,比如可以用func name屬性來檢視函式的名稱。所有預設引數值則儲存在函式物件的 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可以通過函式的預設值來實現靜態變數的功能。

python學習交流群:125240963

參考

轉載至:http://www.cnblogs.com/JetpropelledSnake/p/9105286.html