國慶節快樂~~雖說今天是假期的最後一天。。
好久沒學習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, 繼續學習~~