1. 程式人生 > >Python——返回函式

Python——返回函式

一. 返回函式

高階函式除了可以接受函式作為引數外,還可以把函式作為結果值返回。

>>> def lazy_sum(*args):
...     def sum():
...             z = 0
...             for i in args:
...                     z += i
...             return z
...     return sum
...
>>>
>>>
>>>
>>> # 呼叫lazy_sum()時,返回的並不是求和結果,而是求和函式
...
>>> lazy_sum(1, 3, 5, 7, 9)
<function lazy_sum.<locals>.sum at 0x00EE3810>

>>> # 呼叫lazy_sum()時,每次呼叫都會返回一個新的函式,即使傳入相同的引數
...

>>> lazy_sum(1, 3, 5, 7, 9)
<function lazy_sum.<locals>.sum at 0x00EE3858>
>>>
>>> #  呼叫函式
...
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f()         # 呼叫函式f時,才真正計算求和的結果
25

函式lazy_sum中又定義了函式sum,並且,內部函式sum可以引用外部函式lazy_sum的引數和區域性變數,當lazy_sum返回函式sum時,相關引數和變數都儲存在返回的函式中,這種稱為“閉包(Closure)”的程式結構擁有極大的威力。

【注】返回的函式並沒有立刻執行,而是直到呼叫了f()才執行

二. 閉包

閉包:如果在一個內部函式裡對外部函式(不是在全域性作用域)的變數進行引用,內部函式就被認為是閉包。

1. 閉包產生條件

要建立閉包,必須滿足以下條件:

  • 必須包含一個巢狀函式
  • 巢狀函式必須引用封閉函式中定義的值(自由變數)
  • 封閉函式必須返回巢狀函式
>>> def outer(x):
...     def inner():
...         return x
...     return inner  # 返回的函式沒有圓括號
... 
>>> f = outer('Hello')
>>> f()
'Hello'


>>> del outer
>>> f()
'Hello'
>>> 
>>> outer('Hello')
Traceback (most recent call last):
...
NameError: name 'outer' is not defined

當外部函式 outer(x) 被呼叫時,一個閉包 inner() 就形成了,並且該閉包持有自由變數 - x。這也意味著,當函式 outer(x) 的生命週期結束之後,變數 x 的值依然會被記住。

2. 閉包的好處

  • 取代硬編碼中的常量
  • 避免使用全域性值,並提供某種形式的資料隱藏。
  • 提供一致的函式簽名
  • 實現面向物件
>>> def fpower(exp):
...     def inner(x):
...         return x ** exp
...     return inner
... 
>>> square = fpower(2)
>>> cube = fpower(3)
>>> 
>>> square(2)
4
>>> 
>>> cube(2)
8

這樣做的好處是:fpower 可以用來構建任何一個指數(2、3、4、…)。

【注】返回的函式並沒有立刻執行,而是直到呼叫了f()才執行

>>> def count():
...     fs = []
...     for i in range(1,4):
...             def f():
...                     return i*i
...             fs.append(f)
...     return fs
...
>>>
>>> f1, f2, f3 = count()
>>>
>>>
>>> f1()
9
>>> f2()
9
>>> f3()
9

每次迴圈,都建立了一個新的函式,然後,把建立的3個函式都返回了。

函式呼叫結果都是9!原因就在於返回的函式引用了變數i,但它並非立刻執行。等到3個函式都返回時,它們所引用的變數i已經變成了3,因此最終結果為9

 返回閉包時:返回函式不要引用任何迴圈變數,或者後續會發生變化的變數。

如果一定要引用迴圈變數怎麼辦?方法是再建立一個函式,用該函式的引數繫結迴圈變數當前的值,無論該迴圈變數後續如何更改,已繫結到函式引數的值不變:

>>> def count():
...     def f(j):
...             def g():
...                     return j*j
...             return g
...     fs = []
...     for i in range(1,4):
...             fs.append(f(i))
...     return fs
...
>>>
>>>
>>> f1, f2, f3 = count()
>>>
>>> f1()
1
>>>
>>> f2()
4
>>> f3()
9