您好,登錄后才能下訂單哦!
這篇文章主要講解了“Python閉包技巧是什么”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Python閉包技巧是什么”吧!
有時我們會定義只有一個方法(除了__init__()
之外)的類,而這種類可以通過使用閉包(closure
)來替代。閉包是被外層函數(shù)包圍的內(nèi)層函數(shù),它能夠獲取外層函數(shù)范圍中的變量(即使外層函數(shù)已執(zhí)行完畢)。因此閉包可以保存額外的變量環(huán)境,用于在函數(shù)調(diào)用時使用??紤]下面這個例子,這個類允許用戶通過某種模板方案來獲取URL。
from urllib.request import urlopen class UrlTemplate: def __init__(self, template) -> None: self.template = template def open(self, **kwargs): return urlopen(self.template.format_map(kwargs)) yahoo = UrlTemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}') for line in yahoo.open(names='IBM,AAPL,FB', fields = 'sllclv'): print(line.decode('utf-8'))
這個類可以用一個簡單的函數(shù)來替代:
def urltempalte(template): # 后面用對象再次調(diào)用函數(shù)的時候傳入kwargs def opener(**kwargs): return urlopen(template.format_map(kwargs)) return opener yahoo = urltempalte('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}') for line in yahoo(names='IBM,AAPL,FB', fields = 'sllclv'): print(line.decode('utf-8'))
在許多情況下,我們會使用只有單個方法的類的原因是需要保存額外的狀態(tài)給類方法使用。我們上面提到的UrlTemplate類的唯一目的就是將template
的值保存在某處,然后在open()
方法中使用它。而使用閉包來解決該問題會更加簡短和優(yōu)雅,我們使用opener()函數(shù)記住參數(shù)template
的值,然后再隨后的調(diào)用中使用該值。
所以大家在編寫代碼中需要附加額外的狀態(tài)給函數(shù)時,一定要考慮使用閉包。
我們知道閉包內(nèi)層可以用來存儲函數(shù)需要用到的變量環(huán)境。接下來,我們可以通過函數(shù)來擴展閉包,使得在閉包內(nèi)層定義的變量可以被訪問和修改。
一般來說,閉包內(nèi)層定義的變量對外界來說是完全隔離的,如果想要訪問和修改它們,需要編寫存取函數(shù)(accessor function
, 即getter/setter
方法),并將它們做為函數(shù)屬性附加到閉包上來提供對內(nèi)層變量的訪問支持:
def sample(): n = 0 # 閉包函數(shù) def func(): print("n =", n) # 存取函數(shù)(accessor function),即getter/setter方法 def get_n(): return n def set_n(value): # 必須要加nolocal才能修改內(nèi)層變量 nonlocal n n = value # 做為函數(shù)屬性附加 func.get_n = get_n func.set_n = set_n return func
該算法測試運行結果如下:
f = sample() f() # n = 0 f.set_n(10) f() # n = 10 print(f.get_n()) # 10
可以看到,get_n()
和set_n()
工作起來很像實例的方法。注意一定要將get_n()和set_n()做為函數(shù)屬性附加上去,否則在調(diào)用set_n()
和get_n()
就會報錯:'function' object has no attribute 'set_n'
。
如果我們希望讓閉包完全模擬成類實例,我們需要架構內(nèi)層函數(shù)拷貝到一個實例的字典中然后將它返回。
示例如下:
import sys class ClosureInstance: def __init__(self, locals=None) -> None: if locals is None: locals = sys._getframe(1).f_locals # Update instance dictionary with callables self.__dict__.update( (key, value) for key, value in locals.items() if callable(value) ) # Redirect special methods def __len__(self): return self.__dict__['__len__']() # Example use 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) # <__main__.ClosureInstance object at 0x101efc280> s.push(10) s.push(20) s.push('Hello') print(len(s)) # 3 print(s.pop()) # Hello print(s.pop()) # 20 print(s.pop()) # 10
用閉包模型類的功能比傳統(tǒng)的類實現(xiàn)方法要快一些。比如我們用下面這個類做為測試對比。
class Stack2: def __init__(self) -> None: self.items = [] def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def __len__(self): return len(self.items)
下面是我們的測試結果:
from timeit import timeit s = Stack() print(timeit('s.push(1);s.pop()', 'from __main__ import s')) # 0.98746542 s = Stack2() print(timeit('s.push(1);s.pop()', 'from __main__ import s')) # 1.07070521
可以看到采用閉包的版本要快大約8%。因為對于實例而言,測試話費的大部分時間都在對實例變量的訪問上,而閉包要更快一些,因為不用涉及額外的self變量。
不過這種奇技淫巧在代碼中還是要謹慎使用,因為相比一個真正的類,這種方法其實是相當怪異的。像繼承、屬性、描述符或者類方法這樣的特性在這種方法都是無法使用的。而且我們還需要一些花招才能讓特殊方法正常工作(比如我們上面ClosureInstance中對__len__()
的實現(xiàn))。不過,這仍然是一個非常有學術價值的例子,它告訴我們對閉包內(nèi)部提供訪問機制能夠?qū)崿F(xiàn)怎樣的功能。
感謝各位的閱讀,以上就是“Python閉包技巧是什么”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對Python閉包技巧是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。