簡介
函數是程序的可復用片段,允許你為語句塊賦予名字之后在程序的任何地方運行它們任意次,這稱做函數調用。
我們已經使用過一些內建函數,例如len和range等。
函數也許是任何有意義的軟件中最重要的構件,所以我們將在本章探究函數的方方面面。
函數以關鍵字def定義,其后緊跟函數名,由一對小括號閉合的形參,最后以冒號結束定義行,
定義行下面的是函數體,它是一個語句塊。
聽着有點復雜,其實定義起來是很簡單的,見下面的例子:
范例:
#!/usr/bin/python
# Filename: function1.py
def sayHello():
print('Hello World!') # 語句塊也就是函數體
# 結束函數定義
sayHello() # 調用函數
sayHello() # 再次調用
輸出:
$ python function1.py
Hello World!
Hello World!
工作流程:
我用使用上面講解的函數定義語法定義了一個名叫sayHello的函數,這個函數沒有形參因此小括號里也就沒有
定義變量。函數形參只是提供給函數的輸入,所以我們可以為函數傳遞不同的形參而后得到相應的結果。
注意我們調用了相同的函數兩次,這也意味着我們無需多次編寫相同的代碼(注:函數體)。
函數形參
一個函數可以擁有形參,它們是你提供給函數的值,因此函數就可以利用這些值進行一些操作了。
函數形參非常類似變量,只是這些變量是在我們調用函數時定義的並在函數運行前被賦值。
形參在函數定義中的小括號內指定,並以逗號分隔。當我們調用函數時以同樣的方式提供形參值。
注意下面兩個術語的區別 – 函數定義中給定的參數叫做形參,而在函數調用提供的值叫做 – 實參
范例:
#!/usr/bin/python
# Filename: func_param.py
def printMax(a, b):
if a > b:
print(a, 'is maximum')
elif a == b:
print(a, 'is equal to', b)
else:
print(b, 'is maximum')
printMax(3, 4) # 直接提供字面值
x = 5
y = 7
printMax(x, y) # 以變量作為實參
輸出:
$ python func_param.py
4 is maximum
7 is maximum
工作流程:
我們定義的函數叫做printMax,它需要兩個形參,分別叫做a和b。
函數利用簡單的if…else…語句找出兩者中較大的數並將其打印。
第一次調用printMax時我們直接使用數字作為實參,而第二次調用時我們使用變量。
printMax(x, y)促使實參x的值賦給形參a, y賦給b。
兩次調用中,pringMax的工作方式完全相同。
局部變量
在函數內聲明的變量與在函數外的同名變量沒有任何關系,即變量名對於函數是局部的。
這被稱作變量的作用域,變量的作用域開始於它們所在塊中定義它們的定義點處。
范例:
#!/usr/bin/python
# Filename: func_local.py
x = 50
def func(x):
print('x is', x)
x = 2
print('Changed local x to', x)
func(x)
print('x is still', x)
輸出:
$ python func_local.py
x is 50
Changed local x to 2
x is still 50
如何工作:
在函數內部,我們首先使用變量x的值時,python引用的是函數形參x的值。
接下來,我們為x賦值2,因為x是這個函數的局部名字,所以當我們在函數中使用x的時候,
不會影響到主塊中的x ,在最后的print調用中我們打印了主塊中x的值也證明了這點。
使用global語句
當你希望為程序的頂級名字賦值時(沒有定義在任何其他作用域中的變量,比如函數或類作用域),
(注: python有名字空間的概念,名字空間建立起名字與實際對象(/數據)的映射關系,
這里的”名字”指的是名字所對應的對象)。這時你必須告訴python,名字不是局部而是全局的。
通過global語句可以做到這點,否則是不可能對一個定義在函數外的變量賦值的。
你可以使用定義在函數外的變量的值(但要假設函數內沒有同名變量),不過並不鼓勵這種用法而是應該
盡量避免,這會讓代碼的讀者難以弄清變量到底定義在哪?
范例:
#!/usr/bin/python
# Filename: func_global.py
x = 50
def func():
global x
print('x is', x)
x = 2
print('Changed global x to', x)
func()
print('Value of x is', x)
輸出:
$ python func_global.py
x is 50
Changed global x to 2
Value of x is 2
工作流程:
global語句用於聲明x是一個全局變量 – 因此我們在函數內為x賦值后變化也會反映在主塊中對x的使用中。
你也可以使用一個global語句指定多個全局變量,比如global x, y, z。
使用nonlocal語句
我們已經看到如何存取局部和全局變量。還有另一類叫做”非局部”的作用域,它介於局部和全局之間。
當在函數內定義函數時你會注意到非局部作用域。
因為python中的一切只是可執行代碼,所以你可以在任何地方定義函數。讓我們看一個例子:
#!/usr/bin/python
# Filename: func_nonlocal.py
def func_outer():
x = 2
print('x is', x)
def func_inner():
nonlocal x
x = 5
func_inner()
print('Changed local x to', x)
func_outer()
輸出:
$ python func_nonlocal.py
x is 2
Changed local x to 5
工作流程:
當我們處於func_inner函數中時,func_outer第一行定義的變量x既不是局部也不是全局變量。
這時通過nonlocal x我們聲明這個x是非局部的,所以我們才能夠存取它。
試着將nonlocal x改為global x觀察這兩種用法有什么不同。
默認實參值
對於一些函數,你可能希望它們的形參是可選的,並當用戶沒有為這些形參提供值的時候給它們一個默認值。
這需要借助默認實參值。默認實參值在函數定義時通過為形參名賦一個默認值實現。
注意默認實參值應該是一個常量,更確切的應該是一個不可變類型 – 后面的章節會有具體解釋,現在只要記住這
點就可以了。
范例:
#!/usr/bin/python
# Filename: func_default.py
def say(message, times = 1):
print(message * times)
say('Hello')
say('World', 5)
輸出:
$ python func_default.py
Hello
WorldWorldWorldWorldWorld
工作流程:
函數say用於以指定次數打印指定字符串. 如果我們沒有指定次數則默認的字符串只被打印1次。
我們通過為形參times指定一個默認實參值1實現這個功能。
第一次調用say時,我們只提供了被打印的字符串然后函數只打印一次。
而第二次調用時,我們不僅提供了被打印的字符串還提供了實參5以表明我們需要字符串被打印5次。
重點:
只有在實參列表靠后的參數才能擁有默認實參值,即你不能先聲明帶有默認實參值的形參再聲明不帶有默認實參值的參數。
這是因為實參值是根據形參的位置賦給形參的,例如:def func(a, b = 5)合法,但def func(a = 5, b)就非法了。
關鍵實參
如果你有一些函數擁有許多參數,但你只想使用其中的幾個,這時你可以通過形參名為其賦值。
這被稱做關鍵實參- 使用形參名(關鍵字)為函數指定實參而不是我們一直使用的通過位置指定實參。
這樣做有兩個優點,首先函數用起來更簡單,因為我們不用操心實參的順序了。
其次,可以只為我們感興趣的形參賦值,如果其它參數帶有默認實參值的話。
范例:
#!/usr/bin/python
# Filename: func_key.py
def func(a, b=5, c=10):
print('a is', a, 'and b is', b, 'and c is', c)
func(3, 7)
func(25, c=24)
func(c=50, a=100)
輸出:
$ python func_key.py
a is 3 and b is 7 and c is 10
a is 25 and b is 5 and c is 24
a is 100 and b is 5 and c is 50
工作流程:
函數func擁有一個不帶有默認實參值的參數,后跟兩個帶有默認實參值的參數。
第一次調用func(3, 7),形參a得到值3,b為7,c為默認值10。
第二次調用func(25, c=24), 因為a在參數表中所處的位置,它得到25。
而c通過引用其名字得到24也就是關鍵字實參的功能。b為默認值5。
第三次調用func(c=50, a=100), 我們只使用關鍵字實參指定形參值。
注意盡管在函數定義中a比c更早定義,但我們仍然可以先指定c再指定a。
可變參數(VarArgs)
TODO
因為我們還沒有討論列表和字典,我是不是應該將這部分放到后面的章節?
有時你可能希望編寫一個可以接受任意多個形參的函數,使用星號可以幫你做到:
#!/usr/bin/python
# Filename: total.py
def total(initial=5, *numbers, **keywords):
count = initial
for number in numbers:
count += number
for key in keywords:
count += keywords[key]
return count
print(total(10, 1, 2, 3, vegetables=50, fruits=100))
輸出:
$ python total.py
166
工作流程:
當我們以星號聲明一個形參比如*param,那么這個參數點之后的所有實參會被收集成一個列表,
本例中這個列表叫做param。與之類似如果我們以雙星號聲明一個形參,它會被收集成一個關鍵字實參字典。
后面的章節我們會研究列表和字典。
只能以關鍵字賦值的形參(Keyword-only Parameters)
(注:為方便理解和翻譯,以后直接使用英文術語 keyword-only)
如果我們希望某些關鍵字形參只能通過關鍵字實參得到而不是按照實參的位置得到,可以將其聲明在星號形參后面:
#!/usr/bin/python
# Filename: keyword_only.py
def total(initial=5, *numbers, vegetables):
count = initial
for number in numbers:
count += number
count += vegetables
return count
print(total(10, 1, 2, 3, vegetables=50))
print(total(10, 1, 2, 3))
# 引發錯誤,因為我們沒有為vegetables提供默認實參值
輸出:
$ python keyword_only.py
66
Traceback (most recent call last):
File "test.py", line 12, in <module>
print(total(10, 1, 2, 3))
TypeError: total() needs keyword-only argument vegetables
工作流程:
在星號形參后面聲明的形參導致它成為keyword-only實參。
如果沒有為這些實參提供一個默認值,那么必須在調用函數時以關鍵字實參為其賦值,否則將引發錯誤。
如果你只需要keyword-only實參但不需要星號實參,那么可以簡單的省略星號實參的實參名。
例如def total(initial=5, *, vegetables)。
return語句
return用於從函數返回,即跳出函數。也可以利用return語句從函數返回一個值。
范例:
#!/usr/bin/python
# Filename: func_return.py
def maximum(x, y):
if x > y:
return x
else:
return y
print(maximum(2, 3))
輸出:
$ python func_return.py
3
工作流程:
maximum函數用於返回參數中的最大值,本例中我們以數字作為參數調用它。
函數使用一個簡單的if…else語句比較出最大值並使用return語句將其返回。
注意一個不帶有返回值的return語句相當於返回return None。
None是python的一個特殊類型,代表空。例如如果一個變量的值為None則代表它不存在值。
每個函數的末尾都隱含的包含一個return None語句除非你編寫了自己的return語句。
你可以通過print(someFunction())證實這點,someFunction是一個沒有顯式使用return語句的函數。例如:
def someFunction():
pass
其中pass語句用來指示一個空語句塊。
提示
python已經包含了一個被稱作max的內建函數,它的功能即是尋找最大值,所以盡可能的使用這個函數吧。
DocStrings
python擁有一個俏皮的特性被稱作文檔字符串,通常它被簡稱為docstrings。
文檔字符串是一個你應該利用的重要的工具,因為它幫助你更好的注釋程序使得程序更易於理解。
更神奇的是你甚至可以在程序運行時取得文檔字符串!
范例:
#!/usr/bin/python
# Filename: func_doc.py
def printMax(x, y):
'''Prints the maximum of two numbers.
The two values must be integers.'''
x = int(x) # convert to integers, if possible
y = int(y)
if x > y:
print(x, 'is maximum')
else:
print(y, 'is maximum')
printMax(3, 5)
print(printMax.__doc__)
輸出:
$ python func_doc.py
5 is maximum
Prints the maximum of two numbers.
The two values must be integers.
工作流程:
一個函數的第一個邏輯行的字符串將成為這個函數的文檔字符串。
注意類和模塊同樣擁有文檔字符串,在后面相應的章節我們會學到它們。
根據慣例,文檔字符串是一個多行字符串,其中第一行以大寫字母開頭,並以句號結尾。
接下來的第二行為空行,從第三行開始為詳細的描述。
我強烈建議你在你的正規函數中遵循這個編寫文檔字符串的慣例。
我們可以通過使用函數的__doc__屬性(注意雙下划線)存取printMax的文檔字符串。
記住python中的一切都是對象,其中也包括函數。在后面的類一章我們會學到更多。
如果你在python使用過help(),其實你已經看到過文檔字符串的應用了!
help()只是取出函數的__doc__屬性,然后以一種整潔的方式顯示給你。
你可以用上面的函數作個實驗 – 在你的程序中包含help(printMax)即可,記住按q鍵退出help。
函數注解(Annotations)
函數還擁有另一個被稱作函數注解的高級特性,對於附加額外的形參和返回值信息非常有用。
因為python語言本身並不提供這樣的功能(甩給了第三方庫,具體實現方式第三方庫說了算)。
所以在我們的討論中決定跳過這一特性。如果你對函數注解有興趣可以參見python增強提議No.3107
(http://www.python.org/dev/peps/pep-3107/)(注:python3已經支持這個特性了)
小結
我們已經看到了函數的方方面面,但注意這些不是函數的所有方面。
不過現有的關於python函數的知識已經足夠應付日常應用了。
接下來我們將學習如何使用和創建pthon模塊。

