python cookbook閱讀之——7. 函數

def語句定義的函數是所有程序的基石。

7.1 編寫可接受任意數量參數的函數

(1)可接受任意數量的位置參數的函數,使用*開頭的參數。例如:

>>> def avg(first, *rest):
...     return (first + sum(rest)) / (1 + len(rest))
... 
>>> avg(1,2)
1.5
>>> avg(1,2,3,4)
2.5

這個示例中,rest是一個元祖,它包含了其他所有傳遞過來的位置參數。代碼在之後的計算中會將其視爲一個序列來處理。

(2)接受任意數量的關鍵字參數,使用**開頭的參數。例如:

>>> import html
>>> def make_element(name, value, **attrs):
...     keyvals = [' %s="%s"' % item for item in attrs.items()]
...     attr_str = ''.join(keyvals)
...     element = '<{name}{attrs}>{value}</{name}>'.format(name=name, attrs=attr_str, value=html.escape(value))
...     return element
... 
>>> make_element('item','Abcd', size='large', quet=6)
'<item size="large" quet="6">Abcd</item>'
>>> make_element('div', '小試牛刀', style='color:red;')
'<div style="color:red;">小試牛刀</div>'

這裏的attrs是一個字典,它包含了所有傳遞過來的關鍵字參數。

(3)函數能同時接受任意數量的位置參數和關鍵字參數,只要聯合使用*和**即可。例如:

>>> def anyargs(*args, **kwargs):
...     print(args)     #tuple
...     print(kwargs)   #dict
... 
>>> anyargs(2,3,4,param1="a",param2="b")
(2, 3, 4)
{'param1': 'a', 'param2': 'b'}

這個函數中所有的位置參數都放置在元祖args中,所有的關鍵字參數都會放置在字典kwargs中。

總結:在函數定義中,以*打頭的參數只能作爲最後一個位置參數出現,而以**打頭的參數只能作爲最後一個參數出現。(在*打頭的參數後仍然可以有其他參數出現,但是出現在*打頭的參數後,只能作爲關鍵字參數使用)例如:

>>> def anyargs(x, *args, y, **kwargs):
...     print(args)
...     print(kwargs)
...     print(x)
...     print(y)
... 
>>> anyargs(2,3,4,param1="a",y="1,2",param2="b")
(3, 4)
{'param1': 'a', 'param2': 'b'}
2
1,2
>>> anyargs(2,3,4,y="1,2",param1="a",param2="b")
(3, 4)
{'param1': 'a', 'param2': 'b'}
2
1,2

7.2 帶參數註解的函數,增加代碼可讀性。函數註解會保存在__annotations__屬性中。例如:

>>> def addInt(x:int, y:int) -> int:
...     return x + y
... 
>>> addInt(2,9)
>>> addInt.__annotations__
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

7.4 從函數中返回多個值

>>> def myfun():  
...     return 1,2,3   #實際上返回的是一個元祖
... 
>>> a,b,c = myfun()   #元祖解包賦值給變量
>>> a
1
>>> b
2
>>> c
3
>>> d = myfun()   #代表了整個元祖
>>> d
(1, 2, 3)

7.5 定義默認參數

默認參數賦值,應該總是不可變的對象,例如:None、True、False、數字、或者字符串。特別要注意,絕對不要編寫這樣的代碼:

def fun(a, b=[]):  # NO!  會陷入各種麻煩中,默認值在函數體外被修改時,調用會產生持續影響
    ...

#None會被判定爲False,還有長度爲0的字符串,列表,元祖,字典 也會被判斷爲False
>>> def fun(a, b=None):
...     if not b:   # NO! 可以使用 b is None  代替這句
...             b = []
... 
>>> 

7.6 定義匿名或內聯函數

>>> b = lambda x,y: x + y     #等價於 def b(x, y)
>>> b(3, 4)
7
>>> def b(x, y):
...     return x + y
... 
>>> b(3, 4)
7

下面lambda表達式中x是一個自由變量,此實例可以驗證,lambda在運行時才進行綁定,而不是定義的時候綁定(與def不同)

>>> x = 10
>>> a = lambda y: x + y
>>> a(10)
20
>>> x = 20
>>> a(10)
30

再來看一下易出錯的循環迭代,示例如下:

>>> funcs = [lambda x: x+n for n in range(5)] #執行時候n始終爲最後一次的賦值
>>> for f in funcs:
...     print(f(0))
... 
4
4
4
4
4
>>> funcs = [lambda x, n=n: x+n for n in range(5)]   #執行時候n每次賦值
>>> for f in funcs:
...     print(f(0))
... 
0
1
2
3
4

7.7 訪問定義在閉包中的變量

通過函數來擴展閉包,使得閉包內層定義的變量可以背訪問和修改。一般來說,在閉包內層定義的變量對於外界來說是完全隔離的。但是可以通過編寫來存取函數,並將它們作爲函數屬性附加到閉包上來提供對內層變量的訪問。例如:

>>> def sample():
...     n = 0
...     def fun():
...             print('n = ', n)
...     def get_n():
...             return n
...     def set_n(value):
...             nonlocal n     #nonlocal 聲明使得編寫函數來修改內層變量成爲可能
...             n = value
...     fun.get_n = get_n     #函數屬性能夠將存取函數以直接的方式附加到閉包函數上
...     fun.set_n = set_n
...     return fun
... 
>>> f = sample()
>>> f()
n =  0
>>> f.set_n(10)
>>> f()
n =  10
>>> f.get_n()
10

閉包模擬成類實例,速度比class定義的實例,測試出大約快8%,測試中的大部分時間都花在對實例變量的直接訪問上,閉包要更快一些,這是因爲不用涉及額外的self變量。

>>> import sys
>>> class aaa():
...     def __init__(self, locals = None):
...             if locals is None:
...                     locals = sys._getframe(1).f_locals
...             self.__dict__.update((key,value) for key, value in locals.items() if callable(value))
...     def __len__(self):
...             return self.__dict__['__len__']()
... 
>>> def Stack():   #閉包模擬成類實例
...     items = []
...     def push(item):
...             items.append(item)
...     def pop():
...             items.pop()
...     def __len__():
...             return len(items)
...     return aaa()
... 
>>> s = Stack()
<__main__.aaa object at 0x7f89f34773c8>
>>> s.push(10)
>>> s.push(20)
>>> s.push('hello')
>>> len(s)
3
>>> s.pop()  #pop出'hello'
>>> len(s)
2
>>> s.pop()  #pop出20
>>> s.pop()  #pop出10
>>> len(s)
0
>>> class Stack2:      #實例
...     def __init__(self):
...             self.items = []
...     def push(self, item):
...             self.items.append(item)
...     def pop(self):
...             return self.items.pop()
...     def __len__(self):
...             return len(self, items)
... 
#性能對比
>>> from timeit import timeit
>>> s = Stack()
>>> timeit('s.push(1);s.pop()', 'from __main__ import s')   #閉包耗用時間
0.3924146047793329
>>> s = Stack2()
>>> timeit('s.push(1);s.pop()', 'from __main__ import s')   #實例耗用時間
0.4104466247372329
>>> 

 

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