Python3學習筆記 | 二十一、Python的函數-函數的高級話題
- 2019 年 10 月 6 日
- 筆記
部分設備閱讀本文會存在代碼錯亂的情況,可點擊閱讀原文鏈接到博客中進行查看
一、函數設計概念
當我們使用函數時,就開始面對如何將組件組合在一起的選擇。例如,如何將任務分解成為更有針對性的函數(導致了聚合性),函數將如何通訊(耦合性)等。我們要深入考慮函數的大小概念,因為它們直接影響到代碼的可用性。 耦合性:對於輸入使用參數並且對於輸出使用return語句。 耦合性:只有真正必要的情況下使用全局變量。 耦合性:不要改變可變類型的參數,除非調用者希望這麼做。 聚合性:每一個函數都應該有一個單一的、統一的目標。 大小:每一個函數應該相對較小。 耦合性:避免直接改變在另一個模塊文件中的變量。
二、遞歸函數
之前筆記也提到過,就是調用自身來進行循環的函數。
>>> sum([1,2,3,4]) 10 >>> def mysum(l): ... if not l: ... return 0 ... else: ... return l[0]+mysum(l[1:]) ... >>> mysum([1,2,3,4,5]) 15
函數也可簡單寫成如下:
>>> def mysum(l): ... return 0 if not l else l[0] + mysum(l[1:]) ...
循環VS遞歸
一般情況下,循環會比遞歸簡單。上面函數可以使用循環來解決,不需要遞歸,如下面的例子。
>>> sum = 0 >>> l = [1,2,3,4,5] >>> for i in l: ... sum += i ... >>> sum 15
但在一些特殊情況,遞歸還是比較有用。比如,獲取下面列表中所有數字的合:
>>> l = [1,2,[3,[4,5],6],[7,[8,[9]]],10] >>> def sumtree(l): ... sum = 0 ... for i in l: ... if not isinstance(i,list): ... sum += i ... else: ... sum += sumtree(i) ... return sum ... >>> sumtree(l) 55
三、函數對象:屬性和註解
在Python里函數也是以對象的形態出現。函數名也是以變量名形式存放。因此函數也可以跨模塊,以參數形勢等傳遞。函數對象也能調用根本無關的操作:屬性存儲與注釋。
間接函數調用:
>>> def myprint(x): ... print(x) ... >>> myprint2 = myprint >>> myprint2('Dora') Dora
因為函數對象可以被引用到變量,因此函數里也可以以參數方式傳遞:
>>> def myfunc(func,text): ... func(text) ... >>> myfunc(myprint,'Dora') Dora
或者把函數放進列表或元組裡:
>>> printing = [(myprint,'first'),(myprint,'second'),(myprint,'thrid')] >>> for func,text in printing: ... func(text) ... first second thrid
函數也可作為返回值:
>>> def make(food): ... def myprint(quantity): ... print('We made{}:{}'.format(food,quantity)) ... return myprint ... >>> Cake = make('cake') >>> Cake(10) We madecake:10
由於函數是對象,我們可以使用對象工具來處理函數。
>>> def myfunc(text): ... print(text) ... >>> dir(myfunc) ['__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__']
我們可以發現它有很多屬性、方法等。
>>> myfunc.__name__ 'myfunc'
我們也可以查看代碼里的內容:
>>> myfunc.__code__ <code object myfunc at 0x000002395FF8C5D0, file "<stdin>", line 1> >>> dir(myfunc.__code__) ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames'] >>> myfunc.__code__.co_varnames ('text',) >>> myfunc.__code__.co_argcount 1
我們也可以在函數里添加任意屬性,可以看到下面最後多了一個『a』:
>>> myfunc.a = 10 >>> dir(myfunc) ['__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__', 'a']
四、Python3.x中的函數註解
從Python3.0開始可以為函數的參數與返回值進行註解:
>>> def myfunc(a:int,b:str): ... return a + b ... >>> myfunc(1,3) 4 >>> def myfunc(a:int,b:str) -> list: ... return a + b ... >>> myfunc(1,3) 4 >>> myfunc.__annotations__ {'a': <class 'int'>, 'b': <class 'str'>, 'return': <class 'list'>}
但在代碼里,沒有任何限制。我們可以在特定工具可以利用。
五、匿名函數:lambda
之前也使用過lambda匿名函數。lambda會生成函數對象,但不賦值給任何變量。 lambda表達式:lambda [] [,] [,<arg3]…. : expression using args 參數不是必須的,但沒有參數就沒有相對意義。 lambda簡單說明: lambda是一個表達式,而不是一個語句。--生成一個對象。lambda的主體是一個單個的表達式,而不是一個代碼塊。
>>> lambda a:a*a <function <lambda> at 0x00000239602732F0> >>> myfunc = lambda a:a*a >>> myfunc(3) 9 >>> (lambda a:a*a)(4) 16
為什麼要使用 lambda?
通常 lambda 起到了一種函數速寫的作用。功能上def完全可以代替lambda。但當我們把函數對象放進列表裡等操作的時候,使用def感覺很臃腫。這個時候我們可以使用lambda來簡化過程。
>>> funclist = [lambda x : x**2, ... lambda x : x**3, ... lambda x : x**4] >>> funclist[0](3) 9 >>> funclist[1](3) 27 >>> funclist[2](3) 81
六、lambda 作用域
對於lambda來說,作用域與函數相當。也遵循LEGB原則,關於LEGB原則可以參考:https://www.jianshu.com/p/3b72ba5a209c
>>> a = (lambda:1,print(2)) 2 >>> def action(x): ... return lambda y:x+y ... >>> act =action(10) >>> act(10) 20 >>> act = lambda x:lambda y :x+y >>> act_rslt = act(20) >>> act_rslt(10) 30 >>> (lambda x:lambda y:x+y)(20)(2) 22
七、在序列中映射函數:map
我們有個要求:下面列表裡的每個值增加10 [1, 3, 5] 這個時候我們會想到循環
>>> l = [1,3,5] >>> for i in range(3): ... l[i] += 10 ... >>> l [21, 23, 25]
但這個要是使用map,會更簡單。
>>> l = [1,3,5] >>> l = list(map(lambda x: x +10,l)) >>> l [11, 13, 15]
map的第一個傳遞參數是函數,第二個是可迭代的對象,每個對象當做函數的輸入,輸出結合為可迭代的對象(Python2.x里是列表)
八、函數式編程工具(1):filter
filter與map相似,但是針對返回的bool結果判斷,結果為真,保留元素;結果為假,棄用元素。結果也是保存在可迭代的對象里,在Python2.x是存放列表裡。
>>> list(filter((lambda x : x > 1),[-1,-3,-5,1,3,5])) [3, 5]
下面的示例因為返回的是列表裡的值,只有0的時候bool值判斷為假,因此除了0以外都保存了起來。
>>> list(filter((lambda x : x ),[-1,-3,-5,0,1,3,5])) [-1, -3, -5, 1, 3, 5]
九、函數式編程工具(2):reduce
reduce函數是在functools里的,因此我們得import這個函數。
>>> from functools import reduce >>> reduce((lambda x, y: x + y), [1, 2, 3, 4]) 10
這個方法是,第一次從可迭代對象里提取兩個元素當做函數的參數傳入,按前面的函數進行運算,保存返回值,當可迭代對象里還有元素的時候,之前的返回值為第一個參數,可迭代對象里取下一個繼續運算,直到可迭代對象空。最後返回函數的返回值。
>>> reduce((lambda x, y: x + y), 'test text') 'test text' >>> reduce((lambda x, y: x + y), ['test', ' ', 'text']) 'test text'