27 closure
27.1 把function當成output
- 之前講nested function,是在強調他可以簡化原function內部的計算(我把原function內一直重複出現的pattern,寫成一個inner function,讓他在裡面可以重複使用)。
- 但有時候我們寫nested function的目的,是為了要output出這個nested function
- 例如這樣:
def raise_val(n):
"""Return the inner function."""
def inner(x):
"""Raise x to the power of n."""
raised = x ** n
return raised
return inner
square = raise_val(2)
cube = raise_val(3)
print(square(2), cube(4))
#> 4 64- 酷…我input東西後,可以製作出各式各樣的function
- 那,為啥要這麼做呢? 其實很簡單啊,還是在偷懶啊。想想看,如果今天你要寫個2次方的function,3次方的function,4次方的function…,你要一直重複定義這些一樣pattern的寫法(def function(x) x**2),你不累嗎?所以我就寫個general function,然後你只要輸入n,我就給你n次方的function
27.2 closure
- 剛剛講了nested function,以及把nested function給output出來
- 那現在要講的是,這個output的nested function,可以攜帶他外層的資訊(nonlocal variables)一起出來,這種行為我們叫closure
- 看以下這個簡單的例子:
def foo():
a = 5
def bar():
print(a)
return bar
f = foo()
f()
#> 5- 可以看到,當我們call
f的時候,其實是在callbar這個函數,那bar這個函數會需要用到a,那a從哪裡來?我又沒定義,他怎麼知道?
- 答案是,你如果只看
bar這個函數,你當然沒有a的資訊,但如果你是用foo()所造出的bar函數,那他就帶有enclosing scope的a = 5這個資訊了。這個a就是nonlocal variable。
- 驗證一下,
f這個函數是用foo()所output出的function,所以他應該會帶有closure性質,那我就可以用以下語法檢驗:
f.__closure__
#> (<cell at 0x10cf3ff10: int object at 0x10baa7960>,)- 發現,f果然存有
.__closure__這個attribute
- 如果你只是一般的function物件,是不會有
.__closure__這個attribute的
def bar():
print(a)
dir(bar)
#> ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']- 那回到
f.__closure__,我現在看不到他的長相,但可以看他的type:
type(f.__closure__)
#> <class 'tuple'>- 發現是一個tuple,他裡面其實就存有所有要用到的一對一對的(nonlocal variable, value) pair
- 比如剛剛這個例子,就是(a, 5)這個pair
- 所以我們可以驗證一下,
f.__closure__這個tuple是不是只有一個pair:
len(f.__closure__)
#> 1- 如果要抓出來看,用以下語法:
f.__closure__[0].cell_contents
#> 5f.__closure__[0].cell_contents
#> 5- 打鐵趁熱,看以下的例子:
def parent(arg1, arg2):
value = 22
my_dict = {"chocolate": "yummy"}
def child():
print(2*value) # 這邊就用到nonlocal variable: value
print(my_dict['chocolate']) # 這邊就用到nonlocal variable: my_dict
print(arg1+arg2) # 這邊用到nonlocal variable: arg1, arg2
return child- 從這邊可以看到,我們要output出來的
childfunction,他會攜帶arg1,arg2,my_dict,value這四個nonlocal variable(照字母順序排列)
- 驗證一下:
new_function = parent(3, 4)
len(new_function.__closure__)
#> 4- 果然帶有4個nonlocal variables,我們一一把它們叫出來看:
for element in new_function.__closure__:
print(element.cell_contents)
#> 3
#> 4
#> {'chocolate': 'yummy'}
#> 22