目錄 | 上一節 (7.2 匿名函數) | 下一節 (7.4 裝飾器)python
本節介紹使用函數建立其它函數的思想。git
考慮如下函數:github
def add(x, y): def do_add(): print('Adding', x, y) return x + y return do_add
這是返回其它函數的函數。segmentfault
>>> a = add(3,4) >>> a <function do_add at 0x6a670> >>> a() Adding 3 4 7
請觀察內部函數是如何引用外部函數定義的變量的。閉包
def add(x, y): def do_add(): # `x` and `y` are defined above `add(x, y)` print('Adding', x, y) return x + y return do_add
進一步觀察會發現,在 add()
函數結束後,這些變量仍然保持存活。函數
>>> a = add(3,4) >>> a <function do_add at 0x6a670> >>> a() Adding 3 4 # Where are these values coming from? 7
當內部函數做爲結果返回時,該內部函數稱爲閉包(closure)。ui
def add(x, y): # `do_add` is a closure def do_add(): print('Adding', x, y) return x + y return do_add
基本特性:閉包保留該函數之後正常運行所需的全部變量的值。能夠將閉包視做一個函數,該函數擁有一個額外的環境來保存它所依賴的變量的值。翻譯
雖然閉包是 Python 的基本特性,可是它們的用法一般很微妙。常見應用:code
考慮這樣的函數:get
def after(seconds, func): import time time.sleep(seconds) func()
使用示例:
def greeting(): print('Hello Guido') after(30, greeting)
after
(延遲30 秒後)執行給定的函數......
閉包附帶了其它信息。
def add(x, y): def do_add(): print(f'Adding {x} + {y} -> {x+y}') return do_add def after(seconds, func): import time time.sleep(seconds) func() after(30, add(2, 3)) # `do_add` has the references x -> 2 and y -> 3
閉包也能夠用做一種避免代碼大量重複的技術。
閉包的一個更強大的特性是用於生成重複的代碼。讓咱們回顧 練習 5.7 代碼,該代碼中定義了帶有類型檢查的屬性:
class Stock: def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price ... @property def shares(self): return self._shares @shares.setter def shares(self, value): if not isinstance(value, int): raise TypeError('Expected int') self._shares = value ...
與其一遍又一遍地輸入代碼,不如使用閉包自動建立代碼。
請建立 typedproperty.py
文件,並把下述代碼放到文件中:
# typedproperty.py def typedproperty(name, expected_type): private_name = '_' + name @property def prop(self): return getattr(self, private_name) @prop.setter def prop(self, value): if not isinstance(value, expected_type): raise TypeError(f'Expected {expected_type}') setattr(self, private_name, value) return prop
如今,經過定義下面這樣的類來嘗試一下:
from typedproperty import typedproperty class Stock: name = typedproperty('name', str) shares = typedproperty('shares', int) price = typedproperty('price', float) def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price
請嘗試建立一個實例,並驗證類型檢查是否有效:
>>> s = Stock('IBM', 50, 91.1) >>> s.name 'IBM' >>> s.shares = '100' ... should get a TypeError ... >>>
在上面示例中,用戶可能會發現調用諸如 typedproperty('shares', int)
這樣的方法稍微有點冗長 ——尤爲是屢次重複調用的時候。請將如下定義添加到 typedproperty.py
文件中。
String = lambda name: typedproperty(name, str) Integer = lambda name: typedproperty(name, int) Float = lambda name: typedproperty(name, float)
如今,請從新編寫 Stock
類以使用如下函數:
class Stock: name = String('name') shares = Integer('shares') price = Float('price') def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price
啊,好一點了。這裏的要點是:閉包和 lambda
經常使用於簡化代碼,並消除使人討厭的代碼重複。這一般很不錯。
請從新編寫 stock.py
文件中的 Stock
類,以便使用上面展現的類型化特性(typed properties)。