你想要擴充套件函式中的某個閉包,允許它能訪問和修改函式的內部變數。
解決方案
通常,閉包的內部變數對外界是完全隱藏的。但可以編寫訪問函式,將其作為函式屬性繫結到閉包上來實現訪問。
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__()
)。
讓閱讀程式碼的人感到疑惑,看起來不是一個普通的類定義。