User: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 语句用于说明变量为全局变量。
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
语句用于说明变量不是局部变量。此时,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 语句可以返回一个值,从而向函数外传递信息。
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()
方法。注意到该方法在 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 表达式可以用于创建简单函数。我们看以下示例:
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)]
这种方法除了看起来更简洁美观之外,其作用和上面的“定义函数”一模一样。