打包代码。
创建和调用
def myfunc()
pass
myfunc()
函数传参
def myfunc(name):
for i in range(3):
print(f"I love {name}")
这样,name即为传入的参数,此时将参数位赋值:
myfunc("genshin")
>>>I love genshin
I love genshin
I love genshin
也可以自己指定打印次数:
def myfunc(name,times):
for i in range(times):
print(f"I love {name}")
这样执行之后得到:
myfunc("genshin",5)
>>>I love genshin
I love genshin
I love genshin
I love genshin
I love genshin
以上的操作中,暂时占位、表示用法和对应传入位置的叫形式参数,顾名思义并没有传入实际值;而“genshin”、5,则为实际传入的参数,即为实际参数。
函数的返回值
用return给出函数的返回值。这个返回值可以在嵌套的时候以参数形式传入,或更多作用。
用上面的函数来举例:
def myfunc(name,times):
for i in range(times):
print(f"I love {name}")
如果这里使用print来执行这个函数会怎么样?
def myfunc(name,times):
for i in range(times):
print(f"I love {name}")
print(myfunc("genshin",5))
>>>I love genshin
I love genshin
I love genshin
I love genshin
I love genshin
None
可以看到,在最后一行悄咪咪地多了一个None。这是因为在myfunc()这个函数里,我们没有规定函数的返回值:有循环时默认输出每次循环的结果,而当函数执行完最后一遍循环之后,就没有输出也没有返回值了,因此会给出一个默认的None返回到print()里面完成执行。
参数
即为定制的接口。
位置参数与关键字参数
def myfunc(a,b,c):
return "".join((c,b,a))
显然,这个函数的作用是将输入的三个值倒序拼接成一个字符串。这里a,b,c均为指示传入位置的参数。正常输入实参得到:
print(myfunc("genshin","destroys","world"))
>>>worlddestroysgenshin
可以看到,位置参数按照默认的先后顺序从左到右依次接收输入的实参。但如果形参太多,有时会混淆,这个时候我们就可以用关键字参数来明确哪个值传递给哪个位置的形参。
print(myfunc(c="genshin", b="destroys", a="world"))
>>>genshindestroysworld
但要注意:传入实参时,关键字参数必须在位置参数之后。
默认参数
默认参数在定义函数时就输入了,其作用是如果实际调用函数时没有输入足够个数的实参,就用默认参数顶替成为实参以保证函数的正常运行。例如:
def myfunc(b,c,a="genshin"):
return "".join((c,b,a))
print(myfunc(c="genshin", b="destroys"))
>>>genshindestroysgenshin
同样的,设置默认参数时也需要在位置参数之后。
另:用help()
查看函数文档时,会发现有/出现,比如help(abs)
,会出现abs(x,/)
,这个斜杠则是表明斜杠左侧只能使用位置参数,右侧则随意。
又另:自己定义函数时,可以利用星号*
限制右侧只能使用关键字参数。例如myfunc(a, *
, b, c),这里左侧随意,右侧则只能使用关键字参数传入实参。
收集参数
Python中函数支持可变不定数量参数的一种模式,能让函数接收任意数量的参数,提升灵活性与扩展性。两种类型:
位置收集参数
在函数定义时,在某个形参前加一个星号 *
,该形参就是收集参数。调用函数时,先按位置匹配不带星号的形参和实参,多余的实参将作为一个元组的元素,保存到带星号的收集参数中。例如:
def func(a, b, *args):
print(a, b)
print(args)
func(1, 2, 3, 4, 5)
上述代码中,a
接收 1
,b
接收 2
,3
、4
、5
被收集到 args
元组中,输出为 1 2
和 (3, 4, 5)
。收集参数最好放在最后一个形参位置;若不在最后,其后面的参数调用时必须用关键字参数模式传值,否则会被当作收集参数的一部分。
关键字收集参数
在函数定义时,在形参前加两个星号 **
,该形参是用于收集关键字参数的收集参数。调用函数时,多余的关键字参数会被打包成一个字典,以 “键 - 值” 对形式存储在该收集参数中。例如:
def func(a,*arg,**kwargs):
print(a,arg,kwarg)
func(1,2,3,4,x=5,y=6)
>>>1 (2, 3, 4) {'x': 5, 'y': 6}
上述代码中,调用时func
函数首先按照位置将第一位1
传入位置参数a
中;再将多出来的位置参数打包起来,使用*args
全部打包到位置收集参数的元组里;又使用 **kwargs
收集关键字参数,传入的 x
、y
关键字参数被收集到 kwargs
字典里,输出每个 “键 - 值” 对。
解包参数
顾名思义是将元组或字典解包后将值逐个传入参数中。
因为元组直接传入时,其只代表一个参数,当不满足函数需要参数的数量时就会报错:
args = (1,2,3,4)
def func(a,b,c,d):
print(a,b,c,d)
func(args)
>>>(报错)
因此要解包传入:
args = (1,2,3,4)
def func(a,b,c,d):
print(a,b,c,d)
func(*args)
>>>1 2 3 4
同样的,字典也有解包方法:
kwargs = {'a':1,'b':2,'c':3,'d':4}
def func(a,b,c,d):
print(a,b,c,d)
func(**kwargs)
>>>1 2 3 4
作用域
变量有效的范围。
局部作用域
函数内部定义的变量,局部变量,只在函数内部起作用。
全局作用域
任何函数外部定义的变量,全局变量,全局可生效。
但:如果局部和全局有同名变量,那么在函数内部调用时,局部的会覆盖全局的。在函数内部进行修改,也只会改动局部变量的值。
而如果想在函数内部修改全局变量的值呢?可以使用global语句:
def func():
global x #这就表示在函数内部修改了全局变量x的值
x = 1
但不建议,容易混淆。
嵌套函数
def funcoutside():
x = "out"
def funcinside():
x = "in"
print("in funcinside,x = ",x)
funcinside()
print("in funcoutside,x = ",x)
funcoutside()
>>>in funcinside,x = in
in funcoutside,x = out
同样的,如果只在内层函数修改变量值,这个变量的作用域仍然只在内层函数中。要是想在内层函数里修改外层函数也存在的变量,但不影响全局呢?就可以使用nonlocal语句。
def funcoutside():
x = "out"
def funcinside():
nonlocal x
x = "in"
print("in funcinside,x = ",x)
funcinside()
print("in funcoutside,x = ",x)
funcoutside()
>>>in funcinside,x = in
in funcoutside,x = in
LEGB规则
也就是判断作用域优先级的顺序,依次是:
内函数的局部作用域➡️外函数的嵌套作用域➡️全局作用域➡️内置作用域
内置作用域的意思是:比如原本str是一个函数名,其作用是将参数修改为字符串类型;但如果在调用这个函数之前将str作为变量名赋值了(str = xxxx),这个函数在同域里就失效了。所以不要随便用函数名作为变量名。
举个例子:
一个普通的嵌套函数,可以看到内层函数funcB调用了非自己作用域内的参数x。这是因为,根据LEGB原则,当自身局部作用域内查找不到对应参数时,按照顺序,就接着查找嵌套作用域,于是在funcA里找到对应的x,并调来传入执行。
def funcA():
x = 1
def funcB():
print(x)
funcB()
funcA()
>>>1
理解了这里嵌套函数的执行逻辑后,更有利于理解后续的闭包与装饰器等概念与用法。