Python中的函式,無論是命名函式,還是匿名函式,都是語句和表示式的集合。在Python中,函式是第一個類物件,這意味著函式的用法並沒有限制。Python函式的使用方式就像Python中其他值一樣,例如字串和數字等。Python函式擁有一些屬性,通過使用Python內建函式dir就能檢視這些屬性,如下程式碼所示:
1 2 3 4 5 6 7 8 |
def square(x): return x**2 >>> square <function square at 0x031AA230> >>> dir(square) ['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name'] >>> |
其中,一些重要的函式屬性包括以下幾個:
1. __doc__返回指定函式的文件字串。
1 2 3 4 5 6 |
def square(x): """return square of given number""" return x**2 >>> square.__doc__ 'return square of given number' |
2. __name__返回函式名字。
1 2 3 4 5 6 |
def square(x): """return square of given number""" return x**2 >>> square.func_name 'square' |
3. __module__返回函式定義所在模組的名字。
1 2 3 4 5 6 |
def square(x): """return square of given number""" return x**2 >>> square.__module__ '__main__' |
4. func_defaults返回一個包含預設引數值的元組,預設引數將在後文進行討論。
5. func_globals返回一個包含函式全域性變數的字典引用。
1 2 3 4 5 6 |
def square(x): """return square of given number""" return x**2 >>> square.func_globals {'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', 'square': <function square at 0x10f099c08>, '__doc__': None, '__package__': None} |
6. func_dict返回支援任意函式屬性的名稱空間。
1 2 3 4 5 6 |
def square(x): """return square of given number""" return x**2 >>> square.func_dict {} |
7. func_closure返回一個胞體元組,其中胞體包含了函式自由變數的繫結,閉包將在後文討論。
函式可以作為引數傳遞給其他函式。這些以其他函式作為引數的函式通常稱為更高階函式,這就構成了函數語言程式設計中一個非常重要的部分。高階函式一個很好的例子就是map函式,該函式接受一個函式和一個迭代器作為引數,並將函式應用於迭代器中的每一項,最後返回一個新的列表。我們將在下面的例子中演示這一點,例子中將前面定義的square函式和一個數字迭代器傳遞給map函式。
1 2 |
>>> map(square, range(10)) [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] |
此外,函式也可以在其他函式程式碼塊內部定義,同時也能從其他函式呼叫中返回。
1 2 3 4 5 |
def outer(): outer_var = "outer variable" def inner(): return outer_var return inner |
在上面的例子中,我們在函式outer中定義了另一個函式inner,並且當函式outer執行時將返回inner函式。此外,像任何其他Python物件一樣,函式也可以賦值給變數,如下所示:
1 2 3 4 5 6 7 8 9 10 |
def outer(): outer_var = "outer variable" def inner(): return outer_var return inner >>> func = outer() >>> func <function inner at 0x031AA270> >>> |
在上面的例子中,outer函式被呼叫時將會返回一個函式,並將返回的函式賦值給變數func。最後,該變數就可以像被返回的函式一樣被呼叫:
1 2 |
>>> func() 'outer variable' |
函式定義
關鍵字def用於建立使用者自定義函式,函式定義就是一些可執行的語句。
1 2 |
def square(x): return x**2 |
在上面的square函式中,當包含該函式的模組載入到Python直譯器中時,或者如果該函式在Python REPL中定義,那麼將會執行函式定義語句def square(x)。然而,這對以可變資料結構作為值的預設引數有一些影響,這一點我們將會在後文講述。函式定義的執行會繫結當前本地名稱空間中的函式名(可以將名稱空間當作名字到值的一種對映,並且這種對映還可以巢狀,名稱空間和範圍會在另一個教程中詳細介紹)到一個函式物件,該物件是一個對函式中可執行程式碼的包裝器。這個函式物件包含了一個對當前全域性名稱空間的引用,而當前名稱空間指該函式呼叫時所使用的全域性名稱空間。此外,函式定義不會執行函式體,只有在函式被呼叫時才會執行函式體。
函式呼叫引數
除了正常的引數之外,Python函式還支援數量可變的引數。這些引數有主要有下面描述的三種類別:
1. 預設引數值:這允許使用者為函式的引數定義一些預設值。這種情況下,可以以更少的引數來呼叫該函式,而函式呼叫時未提供的引數,Python會使用預設提供的值作為這些引數值。下面的例子展示了這種用法:
1 2 |
def show_args(arg, def_arg=1, def_arg2=2): return "arg={}, def_arg={}, def_arg2={}".format(arg, def_arg, def_arg2) |
上面例子函式的定義中,包含一個正常位置的引數arg和兩個預設引數def_arg和def_arg2。該函式可以以下面中的任何一種方式進行呼叫:
(1)只提供非預設位置引數值。在本例中,預設引數取預設值:
1 2 3 4 5 |
def show_args(arg, def_arg=1, def_arg2=2): return "arg={}, def_arg={}, def_arg2={}".format(arg, def_arg, def_arg2) >>> show_args("tranquility") 'arg=tranquility, def_arg=1, def_arg2=2' |
(2)用提供的值覆蓋一些預設的引數值,包括非預設位置引數:
1 2 3 4 5 |
def show_args(arg, def_arg=1, def_arg2=2): return "arg={}, def_arg={}, def_arg2={}".format(arg, def_arg, def_arg2) >>> show_args("tranquility", "to Houston") 'arg=tranquility, def_arg=to Houston, def_arg2=2' |
(3)為所有引數提供值,可以用這些值覆蓋預設引數值:
1 2 3 4 5 |
def show_args(arg, def_arg=1, def_arg2=2): return "arg={}, def_arg={}, def_arg2={}".format(arg, def_arg, def_arg2) >>> show_args("tranquility", "to Houston", "the eagle has landed") 'arg=tranquility, def_arg=to Houston, def_arg2=the eagle has landed' |
當使用可變的預設資料結構作為預設引數時,需要特別小心。因為函式定義只執行一次,所以這些可變的資料結構(引用值)只在函式定義時建立一次。這就意味著,相同的可變資料結構將用於所有函式呼叫,如下面例子所示:
1 2 3 4 5 6 7 8 |
def show_args_using_mutable_defaults(arg, def_arg=[]): def_arg.append("Hello World") return "arg={}, def_arg={}".format(arg, def_arg) >>> show_args_using_mutable_defaults("test") "arg=test, def_arg=['Hello World']" >>> show_args_using_mutable_defaults("test 2") "arg=test 2, def_arg=['Hello World', 'Hello World']" |
在每個函式呼叫中,“Hello World”都被新增到了def_arg列表中,在呼叫兩次函式之後,預設引數中將有兩個“Hello World”字串。當使用可變預設引數作為預設值時,注意到這一點非常重要。當我們討論Python資料模型時,將會清楚理解其原因。
2. 關鍵字引數:以“kwarg=value”的形式使用關鍵字引數也可以呼叫函式。其中,kwarg指函式定義中使用的引數名稱。以下面定義的含有預設和非預設引數的函式為例:
1 2 |
def show_args(arg, def_arg=1): return "arg={}, def_arg={}".format(arg, def_arg) |
為了演示使用關鍵字引數呼叫函式,下面的函式可以以後面的任何一種方式呼叫:
1 |
show_args(arg="test", def_arg=3) |
1 |
show_args(test) |
1 |
show_args(arg="test") |
1 |
show_args("test", 3) |
在函式呼叫中,關鍵字引數不得早於非關鍵字引數,所以以下呼叫會失敗:
1 |
show_args(def_arg=4) |
函式不能為一個引數提供重複值,所以下面的呼叫方法是非法的:
1 |
show_args("test", arg="testing") |
在上面的例子中,引數arg是位置引數,所以值“test”會分配給它。而試圖將其再次分配給關鍵字arg,意味著在嘗試多重賦值,而這是非法的。
傳遞的所有關鍵字引數必須匹配一個函式接受的引數,而包含非可選引數的關鍵字順序並不重要,所以下面調換了引數順序的寫法是合法的:
1 |
show_args(def_arg="testing", arg="test") |
3. 任意的引數列表:Python還支援定義這樣的函式,該函式可以接受以元組形式傳遞的任意數量的引數,Python教程中的一個例子如下所示:
1 2 |
def write_multiple_items(file, separator, *args): file.write(separator.join(args)) |
任意數量的引數必須在正常引數之後。在本例中,任意數量引數存在於引數file和separator之後。下面是一個呼叫上述定義函式的示例:
1 2 |
f = open("test.txt", "wb") write_multiple_items(f, " ", "one", "two", "three", "four", "five") |
上面的引數one、two、three、four、five捆綁在一起共同組成了一個元組,通過引數args就能訪問該元組。
解包函式引數
有時候,函式呼叫的引數可能是以元組、列表或字典的形式存在。可以通過使用“*”或“**”操作符將這些引數解包到函式內部以供呼叫。以下面的函式為例,該函式接受兩個位置引數,並列印出兩個引數的值。
1 2 3 |
def print_args(a, b): print a print b |
如果提供給函式的引數值是以列表形式存在,那麼我們可以直接將這些值解包到函式中,如下所示:
1 2 3 4 |
>>> args = [1, 2] >>> print_args(*args) 1 2 |
類似的,當我們有關鍵詞時,可以使用字典來儲存kwarg到值的對映關係,並利用“**”操作符將關鍵字引數解包到函式,如下所示:
1 2 3 4 5 6 7 8 |
>>> def parrot(voltage, state=’a stiff’, action=’voom’): print "-- This parrot wouldn’t", action, print "if you put", voltage, "volts through it.", print "E’s", state, "!" >>> d = {"voltage": "four million", "state": "bleedin’ demised", "action": "VOOM"} >>> parrot(**d) >>> This parrot wouldn’t VOOM if you put four million volts through it. E’s bleedin’ demised |
利用“*”和“**”定義函式
有時候,當定義一個函式時,我們之前可能不知道引數的數量。這就導致了下面簽名的函式定義:
1 |
show_args(arg, *args, **kwargs) |
“*args”參數列示未知的位置引數序列長度,而“**kwargs”代表包含關鍵字和值對映關係的字典,它可以包含任意數量的關鍵字和值對映,並且在函式定義中“*args”必須位於“**kwargs”前面。下面的程式碼演示了這種情況:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def show_args(arg, *args, **kwargs): print arg for item in args: print args for key, value in kwargs: print key, value >>> args = [1, 2, 3, 4] >>> kwargs = dict(name='testing', age=24, year=2014) >>> show_args("hey", *args, **kwargs) hey 1 2 3 4 age 24 name testing year 2014 |
必須向函式提供正常的引數,但“*args”和“**kwargs”卻是可選的,如下所示:
1 2 |
>>> show_args("hey", *args, **kwargs) hey |
在函式呼叫中,普通引數以正常方式提供,而可選引數則可以通過解包的形式到達函式呼叫中。
匿名函式
Python也支援匿名函式,這些函式使用lambda關鍵字建立。Python中Lambda表示式的形式如下所示:
1 |
lambda_expr ::= "lambda" [parameter_list]: expression |
Lambda表示式返回評估後的函式物件,並且具有與命名函式相同的屬性。在Python中,Lambda表示式通常只用於非常簡單的函式,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
>>> square = lambda x: x**2 >>> for i in range(10): square(i) 0 1 4 9 16 25 36 49 64 81 >>> |
上面的lambda表示式的功能與下面命名函式的功能相同:
1 2 |
def square(x): return x**2 |
巢狀函式和閉包
在一個函式內部定義函式就建立了巢狀函式,如下所示:
1 2 3 4 5 6 7 |
```python def outer(): outer_var = "outer variable" def inner(): return outer_var return inner ``` |
在這種型別的函式定義中,函式inner只在函式outer內部有效,所以當內部函式需要被返回(移動到外部作用範圍)或被傳遞給另一個函式時,使用巢狀函式通常比較方便。在如在上面的巢狀函式中,每次呼叫外部函式時都會建立一個新的巢狀函式例項,這是因為,在每次執行外部函式時,都會執行一次內部函式定義,而其函式體則不會被執行。
巢狀函式可以訪問建立它的環境,這是python函式定義語義的直接結果。一個結果是,外部函式中定義的變數可以在內部函式中引用,即使外部函式已經執行結束。
1 2 3 4 5 6 7 8 9 10 11 |
def outer(): outer_var = "outer variable" def inner(): return outer_var return inner >>> x = outer() >>> x <function inner at 0x0273BCF0> >>> x() 'outer variable' |
當內部巢狀的函式引用外部函式中的變數時,我們說巢狀函式相對於引用變數是封閉的。我們可以使用函式物件的一個特殊屬性“__closure__”來訪問這個封閉的變數,如下所示:
1 2 3 4 5 6 |
>>> cl = x.__closure__ >>> cl (<cell at 0x029E4470: str object at 0x02A0FD90>,) >>> cl[0].cell_contents 'outer variable' |
Python中的閉包有一個古怪的行為。在Python 2.x及更低版本中,指向不可變型別(例如字串和數字)的變數不能在閉包內反彈。下面的例子說明了這一點:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def counter(): count = 0 def c(): count += 1 return count return c >>> c = counter() >>> c() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in c UnboundLocalError: local variable 'count' referenced before assignment |
一個相當不可靠的解決方案是,使用一個可變型別來捕獲閉包,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def counter(): count = [0] def c(): count[0] += 1 return count[0] return c >>> c = counter() >>> c() 1 >>> c() 2 >>> c() 3 |
Python 3引入了“nonlocal”關鍵字用來解決下面所示的閉包範圍問題。在本教程名稱空間一節中,我們更加詳細地描述了這些古怪用法。
1 2 3 4 5 6 7 |
def counter(): count = 0 def c(): nonlocal count count += 1 return count return c |
閉包可以用來維持狀態(與類作用不同),在一些簡單的情況下,還可以提供一種簡潔性與可讀性比類更強的解決方案,我們使用tech_pro中的一個日誌例子來說明這一點。假設一個非常簡單的日誌API,它使用基於類的物件導向思想,並可以在不同級別上列印日誌:
1 2 3 4 5 6 7 8 9 10 |
class Log: def __init__(self, level): self._level = level def __call__(self, message): print("{}: {}".format(self._level, message)) log_info = Log("info") log_warning = Log("warning") log_error = Log("error") |
相同的功能也可以使用閉包來實現,如下所示:
1 2 3 4 5 6 7 8 |
def make_log(level): def _(message): print("{}: {}".format(level, message)) return _ log_info = make_log("info") log_warning = make_log("warning") log_error = make_log("error") |
可以看出,即使兩個版本都實現了相同的功能,但基於閉包的版本更簡潔、可讀性更好。閉包在一個主要的Python函式“函式修飾符”中也扮演著很重要的角色,這是使用非常廣泛的功能,我們將在接下來的教程講解。
如果文中你發現了任何錯誤、問題或者你有更好的話題想讓我寫出來,可以在Twitter上聯絡我(@obi_inc)。