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
>>>