1. 函数的定义
函数是组织好的可以重复使用的代码段。函数可以提高应用的模块性和代码的重复使用率。
函数的参数(形式参数)可以支持多个。
def mufun1(name,times):for i in range(times):print(f'I love {name}')
mufun1('python',5)
2. 函数的返回值
函数的返回值,打个比方,len()函数返回可迭代对象的长度,而自定义函数的返回值依靠return语句。
return语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的return语句返回None。
def div(x,y):if y==0:return '除数不能为0'else:return x/y
print(div(10,0))
print(div(10,5))
def div1(x,y):z = x/yprint(z)
div1(10,5)
print(div1(10,5))
此处div1()函数没有定义返回值,其返回值默认为None,而print()函数打印函数时是先执行函数体内部的内容,然后打印函数的返回值,例如print(print())返回的值是None。
3. 函数的参数
函数的参数可以分为形式参数和实际参数,简称形参和实参。上面函数的定义中,myfun1()函数中括号定义的name,times就是形参,传入的 'python' 、5就是实参。
实参默认按照形参定义的顺序进行传递,此时的实参称为位置参数,同时实参也可以指定函数形参的值,此时的实参称为关键字参数。位置参数必须在关键字参数之前。
① 关键字参数
def myfun2(s,v,t):return ''.join((t,v,s))
print(myfun2(t = '我', v = '爱', s = '你')) # -> 我爱你print(myfun2('我','爱','你')) # -> 你爱我
print(myfun2('我','爱', t='你')) # -> 你爱我
print(myfun2('我','爱', v='你')) # -> 异常 got multiple values for argument 'v'
print(myfun2(s='我','爱','你')) # -> 报错 positional argument follows keyword argument
此处的join方法是 将序列中的元素以指定字符链接生成一个新的字符串。譬如将上面代码段中return后面的语句改为’-‘.join([t,v,s])则返回 "我-爱-你"。
② 默认参数
默认参数实在函数定义形参时给形参赋予的默认值。如果默认参数没有被传入新的值,则保持不变。
使用默认参数需要将其放到最后 不然会报错。
def myfunc3(s,v,t='我'):return ''.join((t,v,s))
print(myfunc3('香蕉','吃')) # -> 我吃香蕉def myfunc3(s='我',v,t):return ''.join((t,v,s))
print(myfunc3('香蕉','吃')) # -> 报错 non-default argument follows default argument
③ / 和 *
/
看一下内置函数sum 和 abs的帮助文档, 可以发现定义形参时有一个 / 符号,/ 之前的参数必须传入位置参数,/ 之后的可以是位置参数也可以是关键字参数,自定义函数也遵循这一规则。
print(sum([1,2,3],4)) # -> 10
print(sum([1,2,3],start=4)) # -> 10
print(abs(-1.5)) # -> 1.5
print(abs(x=-1.5)) # -> 报错
*
* 左侧的参数可以是位置参数和关键字参数 ,但是右侧只能是关键字参数
def abc(a,*,b,c):print(a,b,c)
abc(1,c=2,b=3) # -> 1,3,2
abc(a=1,c=2,b=3) # -> 1, 3, 2
abc(1,2,3) # -> 异常 abc() takes 1 positional argument but 3 were given
④ 不定长参数(收集参数)
*args
想传入多少实参就传入多少实参的形参 称为收集参数,例如print()函数可以print很多实参。定义收集参数,只需要在形参前加上 * 。
def myfun(*args):print('有{}个参数'.format(len(args)))print('第二个参数是{}'.format(args[1]))print(args)print(type(args))
myfun('python','java','c++')
可以看到,上述args的type类型是元组, *args(arguements)其背后的原理就是元组的打包和解包功能。
不难想到,若*args后面还跟有形参,则传入实参时必须使用关键字参数。和上述 * 中的一样,def abc(a,*,b,c) 中*相当于一个匿名的收集参数。
**kwargs
收集参数不光可以将参数打包为元组,还可以将参数打包为字典。 **kwargs(keyword-argumes)。此时传入的实参只能是关键字参数,类似于键值对。
def myfunc(**kwargs):print(kwargs)
myfunc(a=1,b=2,c=3)def myfunc(a,*b,**c):print(a,b,c)
myfunc(1,2,3,4,x=5,y=6)
*不光可以用在形参里打包实参,也可以用在实参里实现参数解包。
args = (1,2,3,4)
kwargs = {'a':1, 'b':2, 'c':3, 'd':4}
def myfunc(a,b,c,d):print(a,b,c,d)
myfunc(*args)
myfunc(**kwargs)
4. 函数变量的作用域----LEGB规则
① 局部变量与全局变量
如果变量被定义在函数的里面,则这个变量的作用域仅限于函数体内部,称为局部变量。
def myfunc():x = 520print(x)
myfunc() # -> 520
print(x) # -> name 'x' is not defined
如果在任何一个函数的外部去定义一个变量,改变量作用于全局,称为全局变量。
x = 880
def myfunc():print(x)
myfunc() # -> 880
print(x) # -> 880
在函数内部,局部变量会覆盖同名的全局变量,但是出了函数,局部变量就失效了。
x = 880
def myfunc():x = 520print(x)
myfunc() # -> 520
print(x) # -> 880
但如果在函数内部采用global语句将局部变量声明为全局变量,就可以在函数体内部改变全局变量的值。
x = 880
def myfunc():global xx = 520print(x)
myfunc() # -> 520
print(x) # -> 520
② 嵌套函数及LEGB规则
嵌套函数中嵌套进去的内部函数是不可以直接调用的。可以在定义外部函数时对内部函数就直接调用。对于嵌套函数的作用域,内部函数可以访问到外部函数的局部变量,但是无法修改它。只能通过nonlocal语句才可修改。
def funA():x = 520def funB():x = 880print('in funB, x = ',x)funB()print('in funA, x = ',x)
funA()
def funA():x = 520def funB():nonlocal x # -> 采用nonlocal语句x = 880print('in funB, x = ',x)funB()print('in funA, x = ',x)
funA()
可以看到,如此多的作用域,它们之间的影响范围又存在相互覆盖的情况,那么当冲突发生时,python会选择谁呢?这里不得不说明一下python的变量解析机制---LEGB规则。
L --- Local 局部作用域
E --- Enclosed 嵌套函数外层作用域
G ---Global 全局作用域
B --- BIF(Build-In-Function) 内置函数
这个规则什么意思呢。前面已经演示过,当局部变量和全局变量冲突时,python会使用局部变量(L 在 G 前),除非使用 global 语句;当嵌套发生时,局部作用域又会覆盖外层函数的作用域(L 在 E前),除非使用 nonlocal 语句,而对于嵌套函数 E-G 相当于普通函数的 L-G。
最后,内置函数BIF最没有地位,起个变量名和它一样就可以覆盖它,因为 G 在 B 前,如:
str = 520
a = str(520)
print(str) # ->520
print(a) # -> 异常
注:本文内容总结于 《小甲鱼零基础入门学习python》视频课程P42-P45
原视频链接:【Python教程】《零基础入门学习Python》最新版_哔哩哔哩_bilibili