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'