上一節我們學習了函式的定義和呼叫,理解了基本的函式知識。本節進一步學習函式相關的更多內容,深入瞭解函式,包括:預設引數、關鍵字引數、位置引數、變數的作用域等等。
形參和實參的不同
首先,我們先搞清兩個概念:
形參(parameters),是定義函式時宣告的引數名稱,它定義了函式可以接受的引數型別;
實參(arguments),是呼叫函式時傳給函式的實際值。
比如下面的函式定義:
def func(foo, bar=True, **kwargs):
pass
foo, bar, kwargs 是函式的形參(parameters)。
當我們呼叫func
時,比如:
func(20, bar=False, abc='xyz')
傳給函式的值20
,False
和'xyz'
就是實參。
預設引數值
預設引數就是在函式定義時,給引數一個預設值。呼叫該函式時可以不給有預設值的引數傳值,這樣呼叫時的引數可以減少。
看下面的例子:
def say_hi(name, greeting='Hi', more=''):
print(greeting, name, more)
這個函式有三個引數,其中greeting
和more
有預設值,呼叫該函式時,有以下幾種形式:
- 只傳一個值給name: say_hi(‘Tom’)
- 給可選引數greeting傳值: say_hi(‘Tom’, ‘Hello’)
- 給所有引數傳值: say_hi(‘Tom’, ‘Hello’, ‘how are you’)
一個函式可以有任意多個預設引數,也可以全部引數都有預設值。但是,沒有預設值的引數不能在有預設值的引數的後面,否則會報錯。也就是說,引數的先後順序必須是無預設值引數在前,有預設值引數在後面。
In [175]: def foo(a=1, b):
...: return a + b
File "<ipython-input-175-f8e0e97b520a>", line 1
def foo(a=1, b):
^
SyntaxError: non-default argument follows default argument
預設引數值只生成一次,如果預設值是可變物件比如list、dict、set等就會出現詭異的結果,使用時要非常留心。下面的例子,預設值為一個空list,函式呼叫時它把第一個引數放到list裡面。
def func(a, L=[]):
L.append(a)
return L
print(func(1))
print(func(2))
print(func(3))
程式的輸出是:
[1]
[1, 2]
[1, 2, 3]
這是因為預設值L在函式定義時生成,後面的呼叫(使用預設值,不給L傳值)就會不斷給定義時生成的預設list新增元素。
如果你希望使用這個詭異的特性也沒問題,但要清楚它是什麼。通常我們不建議預設值為可變物件,而是不可變的整數、浮點數、字串等等。
關鍵字引數
呼叫函式時,我們可以使用“關鍵字引數”,它的形式是:kwarg=value
。比如前面的say_hi
函式的呼叫:
In [180]: say_hi(name='Tom')
Hi Tom
In [181]: say_hi(name='Tom', greeting='Hello')
Hello Tom
In [182]: say_hi(name='Tom', more='how are you')
Hi Tom how are you
In [183]: say_hi(more='good day', name='Tom', greeting='Hi')
Hi Tom good day
上面最後一個呼叫告訴我們,關鍵字引數是透過關鍵字來確認引數的,所以可以不用按照函式定義時的順序傳遞引數。
關鍵字引數跟預設引數類似有引數位置的限制,關鍵字引數後面必須都是關鍵字引數。
下面的呼叫都是無效的:
say_hi() # 缺少必須的引數name
say_hi(name='Tom', 'Hello') # 關鍵字引數後面出現了非關鍵字引數
say_hi('Tom', name='Tim') # 同樣的引數傳了兩個值
say_hi(age=10) # 函式定義中不存在的關鍵字引數
如果函式定義的最後一個引數是兩個星號加名稱:**name
,那麼它接受一個字典包含所有關鍵字引數,這個字典不包括name前面宣告的普通引數:
In [190]: def foo(a, **kwargs):
...: print(a)
...: for k, v in kwargs.items():
...: print('%s : %s' % (k, v))
...:
In [191]: foo(1)
1
In [192]: foo(a=1)
1
In [193]: foo(a=1, b=2, c=3, d=4, e='a')
1
b : 2
c : 3
d : 4
e : a
可見,**kwargs
把a後面的所有關鍵字引數都接收了。這對我們以後寫引數非常多的函式時很有幫助。
可變引數列表
可變引數列表類似關鍵字引數**kwargs
,因為它沒有key只有value,所以它是一個序列(確切的說是一個tuple),它的形式是*args
,名稱前面有一個星號*
,用以接收不確定數量的引數。我們常用的內建函式print
就是一個可變引數函式。
下面我自己定義一個可變引數函式:
In [197]: def foo(*args):
...: print(type(args))
...: print('|'.join(args))
...:
In [198]: foo('a', 'b', 'c', 'd')
<class 'tuple'>
a|b|c|d
同樣的,可變引數後面必須跟關鍵字引數:
In [204]: def foo(*args, joiner='|'):
...: print(type(args))
...: print(joiner.join(args))
...:
In [205]: foo('a', 'b', 'c', 'd')
<class 'tuple'>
a|b|c|d
In [206]: foo('a', 'b', 'c', 'd', joiner='/')
<class 'tuple'>
a/b/c/d
解包引數列表
這個正好跟可變引數列表相反,如果要呼叫的函式的引數值已經在list或tuple裡面了,我們可以透過解包list或tuple來給函式傳值。比如內建的range()
函式可以輸入兩個引數:start和stop,如果它們在一個llist或tuple裡面,可以透過*
運算子解包來傳值:
In [207]: list(range(1, 8))
Out[207]: [1, 2, 3, 4, 5, 6, 7]
In [208]: args = [1, 8]
In [209]: list(range(*args))
Out[209]: [1, 2, 3, 4, 5, 6, 7]
同樣的,dict可以透過**
運算子傳遞關鍵字引數:
In [212]: d = {'name':'Tom', 'greeting':'Hello', 'more':'good day'}
In [213]: say_hi(**d)
Hello Tom good day
lambda函式
透過關鍵字lambda
來實現小的匿名函式。匿名函式返回一個函式物件,在可以作為函式引數傳遞給函式。比如內建函式sorted
中的key
引數就接受一個函式物件。
In [215]: func = lambda a, b: a * b
In [216]: func(1,2)
Out[216]: 2
In [217]: func(3,5)
Out[217]: 15
再看看sorted使用lambda函式的情況,先對學生按姓名排序,再按年齡排序:
In [218]: students = [
...: {'name': 'Tom', 'age': 12},
...: {'name': 'Jack', 'age': 13},
...: {'name': 'Aby', 'age': 10},]
In [219]: sorted(students, key=lambda s: s['name'])
Out[219]:
[{'name': 'Aby', 'age': 10},
{'name': 'Jack', 'age': 13},
{'name': 'Tom', 'age': 12}]
In [220]: sorted(students, key=lambda s: s['age'])
Out[220]:
[{'name': 'Aby', 'age': 10},
{'name': 'Tom', 'age': 12},
{'name': 'Jack', 'age': 13}]
變數的作用域和生命週期
程式中的變數不是在任何地方都可見的,它有自己的作用域。
區域性變數
定義在函式內部的變數只在函式內部可見,也就是說,它是函式的區域性變數。
In [1]: def func():
...: x = 'hello'
...: print(x)
...:
In [2]: func()
hello
In [3]: x
------------------------
NameError Traceback (most recent call last)
<ipython-input-3-6fcf9dfbd479> in <module>
----> 1 x
NameError: name 'x' is not defined
x是func
內部的一個變數,對該函式內部可見,所以print(x)
語句能列印x的值。但是在函式外部訪問x時就會報錯:x是未定義的。
全域性變數
相對於區域性變數,全域性變數是定義在函式外部的,它具有全域性作用域。
In [4]: x = 'hello'
In [5]: def func2():
...: print(x)
...:
In [6]: func2()
hello
In [7]: x
Out[7]: 'hello'
如果要在函式內部修改全域性變數,就需要用關鍵字global
來宣告全域性變數:
In [8]: def func3():
...: global x
...: x += 'world'
...: print(x)
...:
In [9]: func3()
helloworld
In [10]: x
Out[10]: 'helloworld'
區域性變數變數的生命週期從函式呼叫開始,到函式執行結束為止;全域性變數的生命週期直到整個程式結束為止。
刪除函式
前面的章節中,我們使用關鍵字del
來刪除列表或其中的元素,它同樣可以用來刪除函式:
In [11]: def func4():
...: print('func4')
...:
In [12]: func4()
func4
In [13]: del func4
In [14]: func4()
-------------------
NameError Traceback (most recent call last)
<ipython-input-14-0e6ad11a93c1> in <module>
----> 1 func4()
NameError: name 'func4' is not defined
在Python中,函式也是物件,所以用del
刪除函式就跟刪除其它物件一樣。
文件字串(docstring)
作為類、函式或模組之內的第一個表示式出現的字串字面值。它在程式碼執行時會被忽略,但會被直譯器識別並放入所在類、函式或模組的 doc 屬性中。由於它可用於程式碼內省,因此是物件存放文件的規範位置。
In [15]: def my_func():
...: '''first line is summary of this function
...:
...: the more lines are details of this function
...: '''
...: pass
...:
In [16]: print(my_func.__doc__)
first line is summary of this function
the more lines are details of this function
寫docstring的規則一般是這樣的:
(1)第一行簡短概述該函式或類的功能
(2)第二行空白
(3)後面幾行詳細描述函式的引數、返回值等等
總結
定義函式時,引數稱為“形參”,表述引數的型別;呼叫函式時,引數為“實參”,是傳給函式的具體值。
定義函式時,可以為引數指定預設值;呼叫函式時,可以透過關鍵字引數呼叫。
定義函式是,可以指定引數為可變引數列表 *args
或**kwargs
;呼叫函式時,可以透過解包list,tuple和dict來傳入引數。
我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。
***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***