你想要擴展函數中的某個閉包,允許它能訪問和修改函數的內部變量。
解決方案
通常,閉包的內部變量對外界是完全隱藏的。但可以編寫訪問函數,將其作爲函數屬性綁定到閉包上來實現訪問。
def sample():
n = 0
# 閉包函數
def func():
print('n=', n)
# 屬性n的訪問方法
def get_n():
return n
def set_n(value):
nonlocal n
n = value
# 附加爲函數屬性
func.get_n = get_n # 將get_n函數附加爲func函數的屬性
func.set_n = set_n
return func
f = sample()
f()
f.set_n(10)
f()
f.get_n()
f()
輸出:
n= 0
n= 10
n= 10
注意兩點:
-
nonlocal 聲明可以讓我們編寫函數來修改內部變量的值。
-
函數屬性允許將訪問方法綁定到閉包函數上。
討論
閉包模擬類的實例。僅僅是複製上面的內部函數到一個字典並返回。
import sys
class ClosureInstance:
def __init__(self, locals=None):
if locals is None:
locals = sys._getframe(1).f_locals
# 使用可調用對象更新字典
self.__dict__.update((key,value) for key, value in locals.items()
if callable(value) )
# 重定向的特殊方法
def __len__(self):
return self.__dict__['__len__']()
def Stack():
items = []
def push(item):
items.append(item)
def pop():
return items.pop()
def __len__():
return len(items)
return ClosureInstance()
s = Stack()
print(s)
s.push(10)
s.push(20)
s.push('hello')
print(len(s))
輸出:
<__main__.ClosureInstance object at 0x00000189D3A55F88>
3
這個代碼比普通的類定義要快很多。
class Stack2:
def __init__(self):
self.items = []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def __len__(self):
return len(self.items)
#學習中遇到問題沒人解答?小編創建了一個Python學習交流羣:153708845
from timeit import timeit
s = Stack()
print(timeit('s.push(1);s.pop()', 'from __main__ import s'))
s2 = Stack2()
print(timeit('s2.push(1);s2.pop()', 'from __main__ import s2'))
輸出:
0.3306974
0.34851409999999994
閉包方案運行起來要快 8%,原因是對實例變量的訪問,閉包不會涉及到額外的 self 變量。
注意:
在代碼的編寫中一般不會這樣做。原因如下:
雖然它運行更快,但它只是真實類的一個奇怪替換。
此時類的繼承、屬性、描述器、類方法都不能用。並且要做額外的工作才能讓一些特殊方法生效(比如上面ClosureInstance 中重寫的 __len__()
)。
讓閱讀代碼的人感到疑惑,看起來不是一個普通的類定義。