使用者:Xyy23330121/Python/函數
定義函數
編輯在 Python 中,使用 def
關鍵詞來定義函數。以下是一種簡單的定義函數和調用的方式:
def func(): #定义函数。函数名为"func"。
print("运行了函数:func()") #调用函数时,执行的语句组
func() #调用函数,此处输出:运行了函数:func()
可以看出,對於需要多次執行的操作,使用函數可以縮減代碼。如果要對這種操作進行更改,直接對函數進行更改也更容易。函數是增加代碼可維護性、使代碼簡潔易讀的手段。
帶參數的函數
編輯def func1(arg): #定义函数,带有参数 arg 。
print("运行了函数:func1(),参数为", arg)
def func2(arg1, arg2): #定义函数,带有参数 arg1, arg2 。
print("运行了函数:func2(),参数为", arg1, arg2)
在函數名後面的括號內,可以添加任意個參數。這相當於在調用函數時,用傳入的參數執行了賦值語句。如果調用上例中的 func1
函數:
func1("传入的参数字符串")
其運行結果,等同於:
arg = "传入的参数字符串"
print("运行了函数:func1(),参数为", arg)
注意,此時定義的參數是必須傳入的,如果不傳入,則會報錯。我們看以下示例:
func1() #报错:TypeError: func1() missing 1 required positional argument
參數與作用域
編輯作用域規則
編輯任何定義在函數中的參數,或在函數中被賦值的變量,都無法用在函數外。這些變量被稱作「局部變量」。在函數中,對變量賦值時,會自動設置變量為「局部變量」,對函數外的變量無影響。我們看以下幾個示例:
外部變量可以應用於內部,內部變量無法應用於外部
編輯def argFunc(arg):
var = 0 #在函数内,给变量 var 赋值
print(a) #使用了函数外的变量 a
print(arg)
a = 1
argFunc(123) #输出:1。
print(var) #报错:NameError: name 'var' is not defined.
print(arg) #报错:NameError: name 'arg' is not defined.
可以看出,函數內賦值的變量,或函數的參數不可以用於函數外。而在函數調用前賦值的函數外變量可以使用於函數內。
在內部對變量賦值,對外部變量無影響
編輯def argFunc2():
a = 0
print(a)
a = 1
argFunc2() #输出:0
print(a) #输出:1
特別情況
編輯只要在函數內,有給變量 a 賦值的語句。變量 a 就會被視為局部變量,而非外部變量。
def argFunc3():
print(a)
a = 0
a = 1
argFunc3() #报错:UnboundLocalError: cannot access local variable 'a' where it is not associated with a value
在執行 argFunc3
的時候,print(a)
查詢了局部變量 a
的值。但此時局部變量 a
尚未賦值,從而導致了報錯。
作用域查詢變量的規則
編輯上面已經說明了幾種作用域的簡單情況,這裡討論更複雜的情況。
在查詢變量名時,會先查詢同層作用域是否有同名變量,隨後查詢上一層作用域。如果上一層作用域沒有,會再查詢更上一層。我們看以下示例:
a = b = c = "Global"
def first():
b = c = "First"
def second():
c = "Second"
print(a,b,c)
second()
first() #输出:Global First Second
在輸出時,c
優先輸出定義在 second
中的版本;而 b
由於在 second
中沒有,所以往上一層,輸出定義在 first
中的版本;而 a
由於在 first
中也沒有,從而輸出了全局版本。
作用域和 def
子句的位置相關,而不與運行函數的位置相關。我們把上面示例改為:
a = b = c = "Global"
def first():
b = c = "First"
second()
def second():
c = "Second"
print(a,b,c)
first() #输出:Global Global Second
可以發現輸出改變了。這是因為 second
的上一層直接就是全局作用域。因此,在運行 second
時,根本就沒查詢 first
的作用域。
global 語句
編輯global 語句用於說明變量為全局變量。
a = b = c = d = "Global"
def first():
b = c = d = "First"
def second():
global c,d
c = d = "Second"
second()
first()
print(a,b,c,d) #输出:Global Global Second Second
通過最後的輸出,我們看出函數直接對全局變量進行了更改。
對於 global 語句聲明的變量,不能在 global 語句之前調用該變量。以下的示例會報錯:
def causeError():
a = 1
Global a
causeError()
nonlocal 語句
編輯nonlocal
語句用於說明變量不是局部變量。此時,Python 會按照查詢規則,將變量設置為查詢到的變量——到查詢到全局作用域之前為止。我們看以下示例:
a = b = "Global"
def first():
a = b = "First"
def second():
b = "Second"
def third():
nonlocal a,b
a = b = "Third"
third()
print(a,b)
second()
print(a,b)
first()
print(a,b)
輸出為:
Third Third
Third First
Global Global
在 third
中,遇到的各變量與作用域如下:
全局 | first 的作用域 | second 的作用域 | |
---|---|---|---|
a | "Global" |
"First" |
|
b | "Global" |
"First" |
"Second"
|
nonlocal
語句查詢到 first
中的 a
和 second
中的 b
——從而之後更改的也是這兩個。於是,各變量及作用域變為如下的情況:
全局 | first 作用域 | second 作用域 | |
---|---|---|---|
a | "Global" |
"Third" |
|
b | "Global" |
"First" |
"Third"
|
所以輸出會變成上面那樣。
注意,nonlocal
語句不會查詢全局作用域。如果一個變量只有全局作用域才有,此時使用 nonlocal
會報錯。
與 global
語句相同,nonlocal
語句聲明的變量,不能在 nonlocal
語句之前調用該變量。
return 語句
編輯return 語句可以返回一個值,從而向函數外傳遞信息。
def argFunc4():
return 0
a = argFunc5()
print(a) #输出:0
由於返回的可以是元組、列表、字典等任何數據結構。所以想返回多少個信息就能返回多少個信息。
特殊:可變序列
編輯函數在對外部的可變序列變量進行更改(而非直接賦值)時,在函數內的更改會表現在函數外。比如:
def add1(s):
s.append(1)
iter = [0, 0]
add1(iter)
print(iter) #输出:[0, 0, 1]
def badAdd1(s):
s = s + [1]
badAdd1(iter)
print(iter) #输出:[0, 0, 1]
由於函數 badAdd1
裡面是一個賦值語句,所以對外部的序列變量沒有影響。而 Add1
裡面是一個更新列表的語句(而非直接賦值),所以它可以影響到外部的序列變量。
傳入參數的方法
編輯我們考慮以下函數:
def func3(arg1, arg2, arg3):
print(arg1, arg2, arg3)
傳入參數的簡單方法
編輯該函數有幾種傳入參數的方法。
按順序傳入
編輯我們可以不管變量名,直接按順序傳入參數。Python 會按順序給參數賦值。
func3(1,2,3) #输出:1 2 3
按關鍵字傳入
編輯可以按參數名傳入參數。此時,Python 會根據參數名給參數賦值。
func3(arg1 = 1, arg2 = 2, arg3 = 3) #输出:1 2 3
func3(arg2 = 2, arg3 = 3, arg1 = 1) #输出:1 2 3
混合按序、按關鍵字傳入
編輯func3(1, arg3 = 3, arg2 = 2) #输出:1 2 3
func3(1, 2, arg3 = 3) #输出:1 2 3
可以混合上面的方法來傳入參數。此時,按序傳入的參數必須在按關鍵字傳入的參數前面。
參數解包
編輯除上述方法外,以下還有兩種方法,可以分別變換為按順序傳入與按關鍵字傳入。
解包參數序列
編輯有時,我們希望把一個序列中的所有元素分別作為參數按序傳入到函數中。此時,我們可以使用:
a = [1,2]
func3(*a,3) #输出:1 2 3
在調用函數時,*a,3
等同於1,2,3
解包參數字典
編輯類似,如果需要把一個字典中的所有鍵-值對分別作為參數傳入到函數中。我們可以使用:
s = {arg2: 2, arg3: 3}
func3(arg1 = 1, **s} #输出:1 2 3
處理參數的方法
編輯規定傳入方式
編輯規定按順序傳入
編輯若在參數列表中加入 /
,則位於 /
之前的參數只能按序傳入。比如,以下的代碼會報錯:
def func4(arg1, arg2, /, arg3):
print(arg1, arg2, arg3)
func4(arg1 = 1, arg2 = 2, arg3 = 3) #报错:TypeError: func4() got some positional-only arguments passed as keyword arguments: 'arg1, arg2'
規定按關鍵字傳入
編輯若在參數列表中加入 *
,則位於 *
之後的參數只能按關鍵字傳入。比如,以下的代碼會報錯:
def func4(arg1, arg2, *, arg3):
print(arg1, arg2, arg3)
func4(1, 2, 3) #报错:TypeError: func4() got some positional-only arguments passed as keyword arguments: 'arg1, arg2'
同時使用
編輯帶默認值的參數(可選參數)
編輯def func5(arg1, arg2=2, arg3):
print(arg1, arg2, arg3)
func5(1, arg3 = 3) #输出:1 2 3
帶默認值的參數可以不傳入。如果不傳入,則會直接使用默認值。這裡 arg2
帶有默認值 2
,從而不用傳入它。
在,本例中,如果想只傳入兩個參數,只用按順序傳入會導致 arg3
不被傳入任何值,從而導致報錯。因此,如果只傳入兩個參數,此處的 arg3
必須是用關鍵字傳入的。
一般情況下,帶默認值的參數會放在必須傳入的參數後。這是為了在不傳入可選參數的情況下,必須的參數也可以選擇傳入方式。
參數打包
編輯元組
編輯我們看以下示例:
def func6(arg, *args):
print(args)
func6(1, 2, 3) #输出:(2, 3)
可以看出,在按序傳入參數1
之後,對於多出來的、按序傳入的參數,Python 把它們弄成一個元組,傳入了*
後面跟着的參數名稱args
裡面。
字典
編輯我們看以下示例:
def func7(arg, **kwargs):
print(kwargs)
func7(0, one = 1, two = 2) #输出:{'one': 1, 'two': 2}
可以看出,在按序傳入參數1
之後,對於多出來的、按關鍵字傳入的參數,Python 把它們弄成一個字典,傳入了**
後面跟着的參數名稱args
裡面。
參數打包的提示
編輯由於元組和字典打包的是不同傳入方式的參數,一個函數在處理時可以同時打包元組和字典。同時,按關鍵字傳入的參數可以打亂順序。因此,以下的方式也是可行的:
def func8(arg0, *args, arg1, **kwargs):
print(arg0, args, arg1, kwargs)
func8(0, 1, 2, one = 1, arg1 = True, two = 2)
#输出:0 (1, 2) True {'one': 1, 'two': 2}
函數對象
編輯函數對象不過是支持在名稱後添加 ()
並輸入參數進行操作的對象:函數也是對象。對於一般對象適用的操作,對函數對象也適用。比如:
函數可以作為參數傳入另一個函數:
def do(func):
func(1)
def todo(arg):
print(list(range(arg,10)))
do(todo) #输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]
以上方式向函數 do
中傳入函數 todo
作為參數。並執行了作為參數的函數。
函數可以打印,或作為變量進行賦值:
def MyFunc(): return "调用了函数"
a = MyFunc
print(a) #输出:<function MyFunc at *>
print(MyFunc) #输出:<function MyFunc at *>
print(a()) #输出:调用了函数
MyFunc = "更改"
print(a) #输出:<function MyFunc at *>
print(MyFunc) #输出:更改
賦值後,a
也支持 MyFunc
的一切操作。而 MyFunc
作為變量也可以用賦值語句修改。
函數可以作為返回值:
def getFunc():
def f(): return "返回的函数"
return f
a = getFunc()
print(a()) #输出:返回的函数
用於 list.sort()
編輯我們在學習列表的排序時,提到過 list.sort()
方法。注意到該方法在 Python 文檔中寫為:
sort(*, key=None, reverse=False)
。這代表該函數的所有參數必須是按參數名傳入。
我們將在此處講解該方法中,參數 key
的作用。
參數 key
需要傳入的是一個函數,它以列表中的元素為參數,並返回一個可比較的值。sort
方法將用輸出的、可比較的值來進行排序。比如:
def key0(elem):
return elem[0]
def key1(elem):
return elem[1]
s = [(2,2), (0,1), (1,0)]
s.sort(key=key0) #用 key0 输出的值进行排序
print(s) #输出:[(0, 1), (1, 0), (2, 2)]
s.sort(key=key1) #用 key1 输出的值进行排序
print(s) #输出:[(1, 0), (0, 1), (2, 2)]
裝飾器
編輯注意到有些函數可以取函數作為參數,裝飾器就是這樣的一類函數。以下是一個簡單的裝飾器。
def SimpleDecorator(func):
return "String"
裝飾器的使用方法如下:
def MyFunc(): pass
MyFunc = SimpleDecorator(MyFunc)
對於需要對一些函數作統一的處理時,可以使用裝飾器。
裝飾器:特殊語法
編輯為了簡便和易於閱讀,Python 支持用這種語法來處理函數:
@SimpleDecorator
def MyFunc(): pass
這種方法得到的結果,和上面的方法沒有區別。
lambda 表達式
編輯lambda 表達式可以用於創建簡單函數。我們看以下示例:
square_sum = lambda x,y=0: x**2+y**2
print(square_sum(3,4)) #输出:25
其中,冒號左邊的內容為參數列表,參數列表可以設置默認值。而冒號右邊的內容為函數的返回值。
這對於某些只使用一次的函數很有用。比如上面的 list.sort()
的示例,我們可以改為:
s = [(2,2), (0,1), (1,0)]
s.sort(key=lambda elem: elem[0]) #用 key0 输出的值进行排序
print(s) #输出:[(0, 1), (1, 0), (2, 2)]
s.sort(key=lambda elem: elem[1]) #用 key1 输出的值进行排序
print(s) #输出:[(1, 0), (0, 1), (2, 2)]
這種方法除了看起來更簡潔美觀之外,其作用和上面的「定義函數」一模一樣。