Python自學記錄——返回函數、匿名函數、裝飾器與偏函數

國慶節快樂~~雖說今天是假期的最後一天。。

好久沒學習Python了。。值得高興的是 《怪物獵人:世界》目前所有的龍我(統槍)都打過一遍了 (/得意)。

正題,開始學習、記錄:

返回函數

顧名思義,返回函數即 返回值爲函數。調用一個函數,返回另一個函數,當執行另一個函數時,另一個函數的內部才執行。有點繞,示例如下:

>>> def test(alist):
...     def sum():
...             s = 0
...             for x in alist:
...                     s = s + x
...             return s
...     return sum
...
>>> L = [1,3,5,7,9]
>>> c = test(L)
>>> c
<function test.<locals>.sum at 0x000001B48D574488>
>>> c()
25

每次調用這個函數都會生成一個新的函數,函數不相等,函數的結果相等,接着上面的示例,如下:

>>> c1 = test(L)
>>> c2 = test(L)
>>> c1 == c2
False
>>> c1() == c2()
True

學習到這裏時,涉及到了一個新的概念,名爲"閉包"。

我理解的 閉包 即 調用一個函數所返回的函數裏包含參數及變量,返回的函數是一個待執行完成的函數,只要調用就會根據其內部的元素結構返回結果。

這樣的話會產生一個問題,示例如下:

>>> def count():
...     f = []
...     for i in range(1, 4):
...         def ft():
...              return i*i
...         f.append(ft)
...     return f
...
>>> f = count()
>>> for x in f:
...     x()
...
9
9
9
>>> f[0]
<function count.<locals>.f at 0x000001B48D5746A8>

上述示例中,首先看起來返回的是 列表f ,不是函數。實際上函數是存放在 列表f 中,返回的是個函數列表;其次,遍歷執行列表中的函數,值相同。想要的結果 是返回 1、4、9 ,不是 9、9、9。

由於在函數中定義了 變量 i ,i 在不斷循環獲取新的值,所生成的函數並沒有執行運算。當循環完成,在開始執行運算時,i指向的變量值爲循環所得的最後一個值,即3,所以值都爲9。

想要得到結果爲 1、4、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
...
>>> f = count()
>>> for x in f:
...     x()
...
1
4
9

返回函數在返回前執行了運算,將 i 當前的值傳入了返回函數中。

練習題:利用閉包返回一個計數器函數,每次調用它返回遞增整數。自解法如下:

>>> def createCounter():
...     a=[0]
...     def counter():
...             a[0] += 1
...             return a[0]
...     return counter
...
>>> c = createCounter()
>>> c()
1
>>> c()
2
>>> c()
3

此題中還有其他解法,涉及到了變量範圍問題,在這裏把我理解的簡單說下:

nonlocal

讓內部函數獲取並使用其外層函數定義的變量,以練習題示例如下:

>>> def createCounter():
...     a = 0
...     def counter():
...         nonlocal a
...         a += 1
...         return a
...     return counter
...
>>> c = createCounter()
>>> c()
1
>>> c()
2

global

內部想要修改全局變量時,需要在局部先聲明全局變量,示例如下:

>>> a = 0
>>> def createCounter():
...     def counter():
...             global a
...             a += 1
...             return a
...     return counter
...
>>> c = createCounter()
>>> c()
1
>>> c()
2
>>> c()
3

匿名函數

顧名思義,即沒有名字的函數。這種函數有個好處,就是不用擔心函數命名衝突的問題,使用方式如下所示:

>>> t = lambda x: x*x
>>> t
<function <lambda> at 0x000001B48D574488>
>>> t(10)
100
# lambda x: x*x 爲匿名函數,它等於下方函數
>>> def ft(x):
...     return x*x

關鍵字 lambda 表示爲匿名函數,匿名函數有限制,只能有一個表達式,不需要寫return ,返回值即表達式結果。

它的用法也很靈活,比如寫在函數的返回值裏等等。

裝飾器

裝飾器(Decorator),簡單來說,就是在不改變原函數的情況下,動態加強函數的功能。

由於函數也是一個對象,而且函數對象可以賦值給變量,所以也能通過變量調用函數。

在Python的函數對象中,有 " __name__ " 屬性(下劃線前面兩個,後面兩個),調用它可以得到函數的名字,示例如下所示:

>>> def test():
...     print('Hello world!')
...
>>> test.__name__
'test'
>>> a = test
>>> a.__name__
'test'

下面寫一個裝飾器(Decorator),實現在調用函數時打印一條語句。本質上,裝飾器就是一個返回函數,示例如下:

>>> def log(func):
...     def wrapper(*arg,**kw):
...             print('call %s:' % func.__name__)
...             return func(*arg,**kw)
...     return wrapper
...

如上所示,log函數接收一個函數,也返回一個函數。使用它需要藉助Python中的@語法,示例如下:

>>> @log
... def now():
...     print('2018-10-07')
...
>>> now()
call now:
2018-10-07

如上所示,在要調用的函數上一行加 @和 裝飾器名稱,在調用函數時便自動打印出了一句事先寫好的話。

此時,將此函數賦值給一個變量,打印變量的函數名,如下所示:

>>> tname = now
>>> tname.__name__
'wrapper'

其實,把裝飾器放在函數定義上方,相當於執行了:

now = log(now)

執行 now函數前,Python生成一個同函數名的變量,並讓它等於執行傳入函數的裝飾器後返回函數。看裝飾器的代碼部分,log函數先執行了wrapper函數,輸出了一條語句,之後返回執行傳入的函數;再之後返回一個wrapper函數。

如果裝飾器需要傳參的話,會比較複雜,但也是個返回函數,示例如下:

>>> def log(test):
...     def decotator(func):
...             def wrapper(*args,**kw):
...                     print('%s %s:' % (test,func.__name__))
...                     return func(*args,**kw)
...             return wrapper
...     return decotator
...
>>> @log('The test')
... def now():
...     print('2018-10-07')
...
>>> now()
The test now:
2018-10-07

現在還有一個重要的問題,調用當前函數返回的函數名是 wrapper ,而不是當前函數名。上述代碼中當前函數名等其他函數屬性並未複製到 wrapper函數中,若代碼中有函數依賴函數的簽名,則會出錯。Python中內置的 functools.wraps 可以解決這個問題,示例如下:

>>> import functools
>>> def log(func):
...     @functools.wraps(func)
...     def wrapper(*args,**kw):
...             print('call %s:' % func.__name__)
...             return func(*args,**kw)
...     return wrapper
...
>>> @log
... def now():
...     print('2018-10-07')
...
>>> now()
call now:
2018-10-07
>>> tnow = now
>>> tnow.__name__
'now'

同理,帶參數的裝飾器如下所示:

>>> import functools
>>> def log(text):
...     def decorator(func):
...             @functools.wraps(func)
...             def wrapper(*args,**kw):
...                     print('%s %s' % (text,func.__name__))
...                     return func(*args,*kw)
...             return wrapper
...     return decorator
...
>>> @log('Good')
... def now():
...     print('2018-10-07')
...
>>> now()
Good now
2018-10-07
>>> tnow = now
>>> tnow.__name__
'now'

回顧下知識點,%s爲佔位符號,詳情在這裏

偏函數

簡單來說,偏函數就是給函數設置默認值,使得後面寫代碼時調用函數不用總輸入固定值,示例如下:

>>> int('123')
123
>>> int('123',base=8)
83
>>> def int2(x,base=2):
...     return int(x,base)
...
>>> int2('1000000')
64

如上所示,這樣調用函數就方便了。在Python中我們可以用functools.partial直接生成一個偏函數,不需要我們自己定義,示例如下:

>>> import functools
>>> int2 = functools.partial(int,base=2)
>>> int2('1000000')
64

本篇就到這裏,教材網址:https://www.liaoxuefeng.com, 繼續學習~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章