示例:定義一個斐波拉契數列
>>> def fib(n): # 打印 Fibonacci 序列到 n
... """打印到 n 的 Fibonacci 序列."""
... a, b = 0, 1
... while a < n:
... print(a, end=' ')
... a, b = b, a+b
... print()
...
>>> # 現在調用我們剛定義的函式:
... fib(2000)
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597
幾個概念:
文檔字串(docstring),用於文檔註釋。
符號表(symbol table),函數中的符號表用於該函式的局部變量,因此稱作局部符號表。在函式中被賦值的變量會存儲在局部符號表中。變量引用會先在局部符號表中尋找,然後纔是閉包函式的局部符號表,再然後是全局變量,最後是內建名字表。在函式中的全局變量儘管可以引用,但是不能賦值。函數內賦值只會創建一個新的同名的局部變量。
函式的實參,在它被調用時被引入到這個函式的局部變量表。並且參數是按值傳遞( 總是對象的一個 引用 , 而不是對象本身的值)。當一個函式調用另一個時, 對應這次調用,一個新的局部符號表就會被創建.
函式定義會在當前的符號表裏引入該函式的名字. 函式名對應的值被解釋器認定爲自定義函式類型 函式名的值可以被賦予另一個名字, 使其也能作爲函式使用.
若函數沒有返回值,默認返回 None(內建名字)。如果要唯一輸出的值是 None
, 那麼解釋器會正當的抑制這次返回. 如你實在想看看這個值,可以使用 print()
:
>>> fib(0)
>>> print(fib(0))
None
* 關於符號表
import dis
c = 3
def abc():
a = 1
b = a + c
dis.dis(abc)
輸出
19 0 LOAD_CONST 1 (1)
3 STORE_FAST 0 (a)
20 6 LOAD_FAST 0 (a)
9 LOAD_GLOBAL 0 (c)
12 BINARY_ADD
13 STORE_FAST 1 (b)
16 LOAD_CONST 0 (None)
19 RETURN_VALUE
默認參數
使用默認參數,可以方便進行調用
def meet(you='lisi', him='wangwu'):
print(you,'meet',him)
meet()
meet('wo')
meet('wo','zhangsan')
重要警告: 默認參數的值只會被求一次值. 但這在默認參數是可變參數的情況下就不一樣了, 如列表, 字典, 或大多類的對象時. 例如, 下面的函式在隨後的調用中會累積參數值:
通常情況
i = 5
def f(arg=i):
print(arg)
i = 6
f()
# output:
5
特殊情況
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
# output:
[1]
[1, 2]
[1, 2, 3]
如果不希望參數值被後續調用共享:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
* 爲什麼默認參數會變?
默認參數和函數對象本身是一一對應的,一個函數擁有一個默認參數的 tuple。這也很好理解,默認參數隨函數一起定義,並且出現在函數簽名裏,理所應當是函數的一部分。
問題的點在於 Python 中對象傳遞全部爲“引用”,list 對象又都是 mutable 的,所以函數調用時往 list 對象作爲的默認參數中 append 會造成額外副作用。
事實上,Python 開發是倡導用 immutable 對象作爲默認參數的。比如用 None 就是個不錯的選擇:
def foo(bar=None):
bar = bar or []
又或者用了 mutable 參數一定記得創建副本:
def foo(bar=[]):
bar = list(bar)
關鍵字參數
函數在定義或者調用時,可以指定參數名稱
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
print("-- This parrot wouldn't", action, end=' ')
print("if you put", voltage, "volts through it.")
print("-- Lovely plumage, the", type)
print("-- It's", state, "!")
可以使用下列方法調用(在函式調用時, 關鍵字參數必須跟在位置參數之後. )
parrot(1000)
parrot(action = 'VOOOOOM', voltage = 1000000)
parrot('a thousand', state = 'pushing up the daisies')
parrot('a million', 'bereft of life', 'jump')
當最後一個形參的形式爲 **name
時, 則排除其他的形參的值, 它將以字典 (參閱 映射類型——字典) 的形式包含所有剩餘關鍵字參數. 這種調用可以與具有 *name
形式的形式參數 (在下一小節中介紹) 聯合使用, 這種形參接受所有超出函式接受範圍的位置參數. ( *name
必須在 **name
之前使用) 例如, 如果我們像這樣定義一個函式:
def cheeseshop(kind, *arguments, **keywords):
print("-- Do you have any", kind, "?")
print("-- I'm sorry, we're all out of", kind)
for arg in arguments:
print(arg)
print("-" * 40)
keys = sorted(keywords.keys())
for kw in keys:
print(kw, ":", keywords[kw])
它可以如下調用
cheeseshop("Limburger", "It's very runny, sir.",
"It's really very, VERY runny, sir.",
shopkeeper="Michael Palin",
client="John Cleese",
sketch="Cheese Shop Sketch")
打印結果(注意, 關鍵字參數名的列表是通過之前對字典 keys()
進行排序操作而創建的; 如果不這樣做, 參數打印的順序是不確定的.)
-- Do you have any Limburger ?
-- I'm sorry, we're all out of Limburger
It's very runny, sir.
It's really very, VERY runny, sir.
----------------------------------------
client : John Cleese
shopkeeper : Michael Palin
sketch : Cheese Shop Sketch
任意參數列表
指定函式能夠在調用時接受任意數量的參數. 這些參數會被包裝進一個元組 (參看 元組和序列). 在變長參數之前, 可以使用任意多個正常參數。
def write_multiple_items(file, separator, *args):
file.write(separator.join(args))
一般地, 這種 variadic
參數必須在形參列表的末尾, 因爲它們將接收傳遞給函式的所有剩餘輸入參數.除此之外,任何出現在 *arg 之後的形式參數只能是關鍵字參數
>>> def concat(*args, sep="/"):
... return sep.join(args)
...
>>> concat("earth", "mars", "venus")
'earth/mars/venus'
>>> concat("earth", "mars", "venus", sep=".")
'earth.mars.venus'
參數列表解包
當參數存在於一個既存的列表或者元組之中, 但卻需要解包以若干位置參數的形式被函數調用。
下面是一個利用 * 操作符解從列表或者元組中解包參數以供函數調用的例子
def cute(sex, age, height):
print(sex,age,height,end=' ')
print()
t = ['female', 18, 167]
cute(*t)
# output:
female 18 167
同樣的, 字典可以通過 ** 操作符來解包參數:
d = {'sex': 'male', 'age':18, 'height': 172}
cute(**d)
# output:
male 18 172
* 或 **操作符有兩個作用:
*arg 表示分散的參數,接受分散的參數。解析後的 arg 表示tuple或者map,可以傳入函數參數爲元組的函數。