預設值的作用域

scm1911發表於2019-02-15

1 預設值的作用域

  • python中一切皆物件,python會把函式的預設值放在屬性中,這個屬性就伴隨著這個函式物件的整個生命週期
  • 也就是說,函式定義完之後,其預設值也就只生成一次,只要函式存在,其預設值就不變。
  • 函式的預設值和函式是否被呼叫沒有關係,只和函式的定義有關係,函式的預設值是在定義函式的時候一併定義的,同一個函式物件在記憶體中(堆中)只有一份,預設值也伴隨著只有一份,其實就是函式物件的一個屬性。
  • 文藝化的表述就是:函式調不呼叫,預設值都在那裡
  • 函式的呼叫和函式的定義是兩碼事
  • 可以通過foo.__defaults__ 檢視函式的預設屬性值
  • 函式的預設值的作用域是local作用域

1.1 演示1 (預設值的作用域僅僅是local)

定義兩個個函式

def foo(xyz=1):
    print(xyz)

foo()
foo()
print(xyz)
=====================
def foo(xyz=[]):
    xyz.append(1)
    print(xyz)

foo()
foo()
print(xyz)

執行結果

In [20]: def foo(xyz=1):
    ...:     print(xyz)
    ...:
In [21]: foo()
1
In [22]: foo()
1
In [23]: print(xyz)
#----------------------------------------------------------------------
NameError                            Traceback (most recent call last)
<ipython-input-23-b6499aa2e891> in <module>
----> 1 print(xyz)

NameError: name 'xyz' is not defined

In [24]:

================================

In [24]: def foo(xyz=[]):
    ...:     xyz.append(1)
    ...:     print(xyz)
    ...:
    ...: foo()
    ...: foo()
    ...: print(xyz)
[1]
[1, 1]
#----------------------------------------------------------------------
NameError                            Traceback (most recent call last)
<ipython-input-24-4726e79ae693> in <module>
      5 foo()
      6 foo()
----> 7 print(xyz)

NameError: name 'xyz' is not defined

1.2 演示2 (引用型別的預設值的示例)

def foo(xyz=[], u='abc', z=123):
   xyz.append(1)
   return xyz

print(foo(), id(foo))
print(foo.__defaults__) 
print(foo(), id(foo))    
print(foo.__defaults__)
In [43]: def foo(xyz=[], u='abc', z=123):
    ...:    xyz.append(1)
    ...:    return xyz
    ...:

In [44]: print(foo(), id(foo))
    ...: print(foo.__defaults__)
[1] 2386557664520
([1], 'abc', 123)

In [45]: print(foo(), id(foo))
    ...: print(foo.__defaults__)
[1, 1] 2386557664520
([1, 1], 'abc', 123)  # 引用型別xyz的內容發生了變化,但全域性函式foo物件並沒有變化,所以其屬性__defaults__也不會變化,其中的xyz的地址也沒有發生變化,只不過是在列表中追加了一個元素而已,列表物件還是原來的那個列表物件。

結論

  1. 函式地址並沒有變化,就是說函式這個物件並沒有變,呼叫它,它的屬性 defaults 中使用元組儲存預設值
  2. xyz的預設值是引用型別,應用型別的元素變動,並不是元組的變化。這和拷貝的原理是類似的。

1.3 演示3 (非引用型別的預設值的示例)

def foo(w, u='abc', z=123):
    u = 'xyz'
    z = 789
    print(w,u,z)

print(foo.__defaults__)
foo('test')
print(foo.__defaults__)
In [47]: def foo(w, u='abc', z=123):
    ...:     u = 'xyz'
    ...:     z = 789
    ...:     print(w,u,z)
    ...:

In [48]: print(foo.__defaults__)
('abc', 123)

In [49]: foo('test')
test xyz 789

In [50]: print(foo.__defaults__)
('abc', 123)   # 非引用型別的預設值並沒有變化

結論

  1. 屬性 defaults 中使用元組儲存所有位置引數預設值,它不會因為在函式體內使用了它而發生改變。

1.4 演示4 (關鍵字預設引數預設值的示例)

def foo(w, u='abc', *, z=123, zz=[4,5,6]):
    u = 'xyz'
    z = 789
    zz.append(8)
    print(w,u,z,zz)

print(foo.__defaults__)
print(foo.__kwdefaults__)
foo('test')
print(foo.__kwdefaults__)
In [64]: def foo(w, u='abc', *, z=123, zz=[4,5,6]):
    ...:     u = 'xyz'
    ...:     z = 789
    ...:     zz.append(8)
    ...:     print(w,u,z,zz)
    ...:

In [65]: print(foo.__defaults__)
('abc',)

In [66]: print(foo.__kwdefaults__)
{'z': 123, 'zz': [4, 5, 6]}

In [67]: foo('test')
test xyz 789 [4, 5, 6, 8]

In [68]: print(foo.__kwdefaults__)
{'z': 123, 'zz': [4, 5, 6, 8]}   # 引型別的關鍵字的預設值的發生了變化

結論

  1. 屬性 defaults 中使用元組儲存所有位置引數預設值,它不會因為在函式提內使用了它而發生改變。
  2. 屬性 kwdefaults 中使用字典儲存所有keyword-only引數預設值.(由於是字典,所以是可以修改的)
  3. * args 和 **kwargs 都是沒有預設值的,因為他們都可以可接收 0 個引數,就沒有必要設定預設值了。

keyword-only 是在 python3.0之後才有的,所以 kwdefaults 也是python3.0之後才有的。

2 預設值的修改

  • 使用可變型別作為預設值,就可能修改這個預設值
  • 有時候這個特性是好的,有的時候這種特性是不好的,有副作用

如何做到按需改變呢?看下面的2種方法

2.1 函式體內,不改變預設值(此方法會有記憶體拷貝)

xyz都是傳入引數或者預設引數的副本,如果就想修改原引數,無能為力

def foo(xyz=[], u='abc', z=123):
    xyz = xyz[:] # 影子拷貝
    xyz.append(1)
    print(xyz)

foo()
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)

--------------------------------------------
In [85]: def foo(xyz=[], u='abc', z=123):
    ...:     xyz = xyz[:] # 影子拷貝
    ...:     xyz.append(1)
    ...:     print(xyz)
    ...:
    ...: foo()
    ...: print(foo.__defaults__)
    ...: foo()
    ...: print(foo.__defaults__)
    ...: foo([10])
    ...: print(foo.__defaults__)
    ...: foo([10,5])
    ...: print(foo.__defaults__)
[1]
([], 'abc', 123)
[1]
([], 'abc', 123)
[10, 1]
([], 'abc', 123)
[10, 5, 1]
([], 'abc', 123)

--------------------------------------------
In [84]: a = []
    ...: b =a[:]
    ...: id(a),id(b)
Out[84]: (1885549915592, 1885549914248)   # < -- 切片是淺拷貝的過程,生成了一個新的列表

2.2 使用不可變型別預設值(建議使用的方法)

  • 如果使用預設值None就建立一個列表
  • 如果傳入一個列表,就修改這個列表
def foo(xyz=None, u="abc", z=123):
    if xyz is None:
        xyz = []
    xyz.append(1)
    print(xyz)

foo()
print(foo.__defaults__)
foo()
print(foo.__defaults__)
foo([10])
print(foo.__defaults__)
foo([10,5])
print(foo.__defaults__)

--------------------------------------------

In [86]: def foo(xyz=None, u="abc", z=123):
    ...:     if xyz is None:
    ...:         xyz = []
    ...:     xyz.append(1)
    ...:     print(xyz)
    ...:
    ...: foo()
    ...: print(foo.__defaults__)
    ...: foo()
    ...: print(foo.__defaults__)
    ...: foo([10])
    ...: print(foo.__defaults__)
    ...: foo([10,5])
    ...: print(foo.__defaults__)
[1]
(None, 'abc', 123)
[1]
(None, 'abc', 123)
[10, 1]
(None, 'abc', 123)
[10, 5, 1]
(None, 'abc', 123)

2.3 兩種方法的比較

  • 第一種方法
    使用影子拷貝建立一個新的物件,永遠不能改變傳入的引數
  • 第二種方法
    通過值的判斷就可以靈活的選擇建立或者修改傳入物件,這種方式靈活,應用廣泛,很多函式的定義,都可以看到使用None這個不可變的值作為預設引數,可以說這是一種慣用法

3 + 和 += 對函式預設值的影響

3.1 使用 += 改變了__defaults__屬性, += 是就地修改相當於 extend

def x(a=[]):
    a += [5]
print(x.__defaults__)
x()
print(x.__defaults__)
In [78]: def x(a=[]):
    ...:     a += [5]
    ...: print(x.__defaults__)
    ...: x()
    ...: print(x.__defaults__)
([],)
([5],)

3.2 使用 + 並沒有改變__defaults__ , 是右邊通過 + 生成一個新的物件再賦值給左邊的變數,並沒有堆預設值操作。

def y(a=[]):
    a = a + [5]
print(y.__defaults__)
y()
print(y.__defaults__)
In [79]: def y(a=[]):
    ...:     a = a + [5]
    ...: print(y.__defaults__)
    ...: y()
    ...: print(y.__defaults__)
([],)
([],)

3.3 兩種方法結果不同的原因

  • 這兩種方法在處理列表時採用的方式不一樣
  • 第一種方法,本質上使用的是列表的extend方法
  • 第二種方法,本質上就是列表的+,返回一個新列表
  • 但是對於字串這種非引用型別的話,+ 和 += 都是生成一個新的物件。
In [87]: a = 'abc'
    ...: a += "d"

In [88]: a
Out[88]: 'abcd'

In [89]: a = "abc"
    ...: a = a + "d"

In [90]: a
Out[90]: 'abcd'

本文連結:https://www.cnblogs.com/shichangming/p/10383563.html

相關文章