11.函数
函数在Python占有非常重要的地位,可以实现代码复用,增强代码可读性等等。在Python在函数通常被分为常规函数、匿名函数和高阶函数。
11.1常规函数
在Python定义一个函数使用def关键字,其格式如下所示:
def functionName(para1,para2,...,paran)
"""docString"""
doSomething
# comment
return result
Python中函数如果没有显式声明返回值,则默认返回为None
在Python中函数非常灵活,既可以简单调用,也可以传入非常复杂的参数从简到复杂的参数形态如下所示:
- 1.位置参数:即按指定的顺序进行传递参数
- 2.默认参数:即如果参数没有传递值,则使用其定义时的默认值
- 3.可变参数:即传递的参数个数不确定,其定义格式为*args
- 4.关键字参数:即传递的参数是键值对形式传递,其定义格式为**kwargs
- 5.命名关键字参数:即用于限制关键字参数的名字,调用时关键字名称必须使用函数定义的参数名
- 6.参数组合:各种参数类型的组合
11.1.1 位置参数
位置参数的示例如下所示:
110101位置参数.png- def:声明函数的关键字
- function_name:函数名称
- arg:位置参数
- docstring:函数的说明信息,一般用来描述函数的功能,传递的参数意义和返回的值说明等信息,如果函数中有写docstring,可以使用help(函数名称)查看
- statement :函数主体,标志该的主要代码功能
示例代码如下所示:
# 函数定义
def func(name,age):
print(f"name is {name} , age is {age}")
# 函数传递参数
func("Surpass",28)
在位置参数中,参数的传递是按照对应的顺序进行传递,调用函数时,传递的参数个数必须函数定义的参数个数保持一致
11.1.2 默认参数
在位置参数中传递的参数个数必须与函数定义的参数个数一致,否则则会出现报错,那有没有那种,在调用函数不想传递所有参数,但又希望未传递的参数使用一个固定的值,如何操作?能完成这种操作的氷是默认参数,如下所示:
110102默认参数.png- arg2=v:参数名称=默认值,调用参数,如果没有给其传递值,则使用默认值,如果有传递值,则使用传递过来的值
# 函数定义
def func(name,age=25):
print(f"name is {name} , age is {age}")
# 函数传递参数
func("Surpass")
在上面示例中,如果age未传递值,则函数中的age值就是25,如果传递的值为28,则age为28
使用默认参数的参数一定要置于位置参数之后,否则会出现报错
在一个函数可以使用多个默认参数,这时则会出现另一个情况,传递的参数过多,无法记住顺序,此时传递的参数会失去其相应的意义。针对这种情况,可以使用参数名称=参数值形式进行传递值。如下所示:
# 函数定义
def func(name,age=25,weight=55,city="shanghai"):
print(f"name is {name} , age is {age},weight is {weight},now in {city}")
# 函数传递参数
func("Surpass",city="wuhan",weight=60,age=28)
11.1.3 可变参数
如果传递的参数个数是固定,可以使用位置参数或默认参数,但如果传递的参数个数不确定时,怎么办呢?在Python中提供了可变参数这个功能,常用*args表示,示意图如下所示:
110103可变参数.png- *args - 可变参数,可以是从零个到任意个,自动组装成元组
示例代码如下所示:
# 函数定义
def func(name,age=25,weight=55,city="shanghai",*loveSport):
print(f"name is {name} , age is {age},weight is {weight},now in {city},love sport is {loveSport}")
# 函数传递参数
func("Surpass",28,60,"wuhai","run","tabletennis","basketball")
输出结果如下所示:
name is Surpass , age is 28,weight is 60,now in wuhai,love sport is ('run', 'tabletennis', 'basketball')
除了直接传递多个参数之外,还可以将所有参数先封装成元组,再传递参数前面添加*(用于解包,拆散元组)
# 函数定义
def func(name,age=25,weight=55,city="shanghai",*loveSport):
print(f"name is {name} , age is {age},weight is {weight},now in {city},love sport is {loveSport}")
# 函数传递参数
func("Surpass",28,60,"wuhai",*("run","tabletennis","basketball"))
在使用可变参数时,如果有默认参数需要传递值时,此时不能使用参数名称=参数值进行传递,需要按照位置参数的形式进行传递值,否则会被当成关键字传递
11.1.4 关键字参数
关键字参数,传递的参数是以键值对形式进行传递,类似于默认参数,一般表示语法为**kwargs,但请注意区别,示意图如下所示:
110104关键字参数.png- **kw - 关键字参数,可以是从零个到任意个,自动组装成字典。
可变参数与关键字参数的区别如下所示:
- 可变参数和关键字参数都可以传递零到任意个参数
- 可变参数会将传递的参数封装成元组
- 关键字参数会将传递的参数封装成字典
# 函数定义
def func(name,age=25,weight=55,city="shanghai",*loveSport,**kwargs):
print(f"name is {name} , age is {age},weight is {weight},now in {city},love sport is {loveSport},kwargs is {kwargs}")
# 函数传递参数
func("Surpass",28,60,"wuhai",*("run","tabletennis","basketball"),otherinfoA="otherinfoA",otherinfoB="otherinfoB")
输出结果如下所示:
name is Surpass , age is 28,weight is 60,now in wuhai,love sport is ('run', 'tabletennis', 'basketball'),kwargs is {'otherinfoA': 'otherinfoA', 'otherinfoB': 'otherinfoB'}
除了直接传递多个关键字参数之外,还可以将所有参数先封装成字典,再传递参数前面添加**(用于解包,拆散字典)
11.1.5 命名关键字参数
对于关键字参数,函数的调用方可以传入任意不受限制的关键字参数和及其对应的值,如果要限制关键字参数的名字,则需要使用命名关键字参数,如只接受city和job作为关键字。其定义方式如下所示:
110105命名关键字参数.png使用关键字参数需要注意的事项如下所示:
- **1.命名关键字参数需要使用分隔符为* **
- 2.命名关键字参数必须传入参数名,否则将报错
- **3.如果函数定义已经存在可变参数,则后面跟着的命名关键字参数就不再需要分隔符* **
- 4.命名关键字参数也可以定义默认值来简化调用
- 5.位置参数和命名关键字参数之间必须使用分隔符*加以区分,否则则视为位置参数
1.定义命名关键字参数
命名关键字参数和位置使用分隔符*进行区分,调用时必须传递参数,否则会报错
def person(name,age,*,height,weight):
print(f"name is {name},age is {age} , height is {height} , weight is {weight}")
person("Surpass",28,height=190,weight=69)
输出结果为:
name is Surpass,age is 28 , height is 190 , weight is 69
**2.使用可变参数,命名关键字参数不再需要分隔符* **
def person(name,age,*args,height,weight):
print(f"name is {name},age is {age} , args is {args},height is {height} , weight is {weight}")
person("Surpass",28,1,2,height=190,weight=69)
输出结果为:
name is Surpass,age is 28 , args is (1, 2),height is 190 , weight is 69
3.命名关键字参数可设置其默认值来简化调用
def person(name,age,*args,height=189,weight):
print(f"name is {name},age is {age} , args is {args},height is {height} , weight is {weight}")
person("Surpass",28,1,2,weight=69)
输出结果为:
name is Surpass,age is 28 , args is (1, 2),height is 189 , weight is 69
4.如果命名关键参数缺少分隔符*,则将会视为位置参数
def person(name,age,height,weight):
print(f"name is {name},age is {age} ,height is {height} , weight is {weight}")
person("Surpass",28,height=189,weight=100)
输出结果为:
name is Surpass,age is 28 ,height is 189 , weight is 100
5.命名关键字参数与关键字参数结合
def person(name,age,*,height,weight,**kwargs):
print(f"name is {name},age is {age} ,height is {height} , weight is {weight},kwargs is {kwargs}")
person("Surpass",28,height=189,weight=100,keyA="keyA",keyB="keyB")
输出结果为:
name is Surpass,age is 28 ,height is 189 , weight is 100,kwargs is {'keyA': 'keyA', 'keyB': 'keyB'}
6.与其他参数类型的组合
def person(name,age,job="enginerr",*args,height,weight,**kwargs):
print(f"name is {name},age is {age} occupation is {job},args is {args} height is {height} , weight is {weight},kwargs is {kwargs}")
person("Surpass",28,"IT",1,2,3,height=189,weight=100,keyA="keyA",keyB="keyB")
输出结果为:
name is Surpass,age is 28 occupation is IT,args is (1, 2, 3) height is 189 , weight is 100,kwargs is {'keyA': 'keyA', 'keyB': 'keyB'}
11.1.6 参数组合
在Python中定义函数,可以用位置参数、默认参数、可变参数、关键字参数和命名关键字参数,虽然这些参数可以进行组合使用,但也必须遵循一定的规则,参数定义的顺序如下所示:
- 位置参数、默认参数、可变参数、关键字参数(最为常见)
- 位置参数、默认参数、可变参数、命名关键字参数
- 位置参数、默认参数、命名关键字参数、关键字参数
- 位置参数、默认参数、可变参数、命名关键字参数、关键字参数
可变参数和关键字参数的语法:
- *args:可变参数,接收后会封装成元组
- **kwargs:关键字参数,接收后会封装为字典
在Python中,虽然各种参数可以进行各种组合,但不建议使用太多的组合,否则函数比较难懂。
11.2 匿名函数
从目前所学知识来看,如果要定义一个函数必须使用def关键字进行定义,再实现函数体代码。在部分情况,函数实现的功能就非常简单,如果每次都需要定义后才能使用,会显得非常麻烦。在Python提供了lambda表达式来简化这一操作,使用lambda定义的函数称之为匿名函数(因为没有函数名称),其表达式如下所示:
110201lambda表达式.png- lambda:定义匿名函数的关键字
- argument_list:函数的参数,与常规函数参数一致
- : 冒号,匿名函数参数与表达式的分隔符
- expression:匿名函数表达式,对应于常规函数中的函数主体代码
示例代码如下所示:
1.示例1
f=lambda x,y:list(range(x,y))
print(f(0,10))
# 与下面的函数功能是等效的
def f(x,y):
print(list(range(x,y)))
输出结果为:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
2.示例2
f=lambda *args:print(sum(args))
f(1,2,3,4,5)
# 与下面的函数功能是等效的
def f(*args):
print(sum(args))
输出结果为:
15
3.示例3
f=lambda **kwargs:print(kwargs)
f(keyA="keyA",keyB="keyB",keyC="keyC")
# 与下面的函数功能是等效的
def f(**kwargs):
print(kwargs)
输出结果为:
{'keyA': 'keyA', 'keyB': 'keyB', 'keyC': 'keyC'}
虽然匿名函数使用方便,但要结合实际情况来,无需过度使用,如果一个函数需要被经常调用,需要使用def定义为常规函数
11.3 高阶函数
高阶函数在函数式编程中非常常见,其主要形式如下所示:
- 参数是函数
- 返回值类型是函数
11.3.1 闭包
维基的解释如下所示:
在计算机科学中,闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
Python里面的闭包是一种高阶函数,返回值为函数对象,简单来讲就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外执行,这种函数称之为闭包,示例如下:
def outter(x):
temp=[x]
def add():
temp[0]+=x
print(temp)
def sub():
temp[0]-=x
print(temp)
return add,sub
add,sub=outter(100)
add()
sub()
输出结果如下所示:
[200]
[100]
11.3.2 偏函数
偏函数的主要功能是把函数的一个或多个参数固定下来,使其成为一个新函数,并用于其他应用上面 。若要使用偏函数,需要导入以下的包,如下所示:
from functools import partial
以上的解释看起来似懂非懂,来看看下面的示例:
tempList=[12,100,999,12412,1,-98,1209]
print(sorted(tempList))
以上的示例代码是对列表进行排序,且默认排序为升序排序,如果需要进行倒序排序,则需要单独添加参数reverse=True,如下所示:
sorted(tempList,reverse=True)
假设需要多次使用到这个排序函数,每次都这么写特别麻烦。除了重写函数之外,有没有办法可以固定住该函数的reverse参数?这时候轮到偏函数上场了,示例如下所示:
mySortedpartial=partial(sorted, reverse=True)
print(mySortedpartial)
输出结果如下所示:
functools.partial(<built-in function sorted>, reverse=True)
从输出结果来看,mySortedpartial是一个函数,单独设置的参数被固定住了,这样就不用每次单独输入这个参数了,而且函数功能依然能正常使用,如下所示:
from functools import partial
tempList=[12,100,999,12412,1,-98,1209]
print(sorted(tempList,reverse=True))
mySortedpartial=partial( sorted, reverse=True )
print(mySortedpartial(tempList))
输出结果如下所示:
[12412, 1209, 999, 100, 12, 1, -98]
[12412, 1209, 999, 100, 12, 1, -98]
通过以上的示例,可以看出来偏函数的主要作用了,下面再来一个我们自己定义一个函数,再使用偏函数固定某个参数,如下所示:
from functools import partial
def getUserInfo(username,age,sex,country):
userInfo = []
userInfo.append((username,sex,age,country))
return userInfo
if __name__ == '__main__':
myGetUserInfo=partial(getUserInfo,sex="男",country="中国")
print(myGetUserInfo)
print(myGetUserInfo("Surpass",28))
print(myGetUserInfo("Kevin",35))
print(myGetUserInfo("Leo",38))
输出结果如下所示:
functools.partial(<function getUserInfo at 0x000001ACCA803048>, sex='男', country='中国')
[('Surpass', '男', 28, '中国')]
[('Kevin', '男', 35, '中国')]
[('Leo', '男', 38, '中国')]
当函数的参数个数较多,且参数默认值不符合自己需求时,可以使用偏函数创建一个新的函数,来简化调用。
偏函数简单来讲,可以理解为了满足自身需求,在固定原有函数部分参数的值后,创建一个新的函数,来简化调用,是不是很像给一个函数取了一个别名?
11.3.3 柯里化
柯里化是指将原来接受两个参数的函数变为接受一个参数的函数过程,新的函数参数返回一个以原有第二个参数为参数的函数。即g=f(x,y)转变为g=f(x)(y)
以普通的加法函数为例:
def addA(x:int,y:int)->int:
return x+y
通过函数嵌套,可以转换为以下形式:
def addB(x:int)->int:
def subadd(y:int):
return x+y
return subadd
对比以上两个函数,我们来看看调用形式,分别如下所示:
print(addA(1,2))
g=addB(1)
print(g(2))
print("简写形式")
print(addB(1)(2)) # 将g=f(x,y)转变为g=f(x)(y)
输出结果如下所示:
3
3
简写形式
3
11.4 函数作用域
系统每次执行一次函数时,就会创建新的局部命名空间。该命名空间代表一个局部环境,其中包含函数的参数名称和在函数体内赋值的变量名称。解析这些名称时,解释器将首先搜索局部命名空间;如果没有找到匹配的名称,则会搜索全局命名命名空间。函数的全局命名空间始终是定义该函数的模块,如果解释器在全局命名空间中也找不到匹配值,则最终会检查内置命名空间,如果仍未找到,则触发NameError异常。
变量的搜索顺序 局部命名空间->全局命名空间->内置命名空间
示例代码如下所示:
a=12
def f():
a=120
if __name__ == '__main__':
print(f"调用函数前,变量的值{a}")
f()
print(f"调用函数后,变量的值{a}")
输出结果如下所示:
调用函数前,变量的值12
调用函数后,变量的值12
以上示例代码,尽管在函数f()修改了变量a的值,但返回a的值没有变。当变量在函数中被赋值时,这些变量始终被绑定在该函数的局部命名空间中,因此函数体中的变量a引用的是一个包含值为120的全新对象,而不是外面的变量。如果要改变这个行为,可以使用关键字global。
global关键字可以明确将变量名称声明为属于全局命名空间,只有在需要修改全局变量时才使用,可以放在函数任意位置且可重复使用。
a=12
b=13
def f():
global a
a=120
b=130
if __name__ == '__main__':
print(f"调用函数前,变量的值{a} - {b}")
f()
print(f"调用函数后,变量的值{a} - {b}")
输出结果如下所示:
调用函数前,变量的值12 - 13
调用函数后,变量的值120 - 13
我们再来看看以下的示例:
def counter(start):
n=start
# 定义嵌套函数
def show():
print(f"current value is {n}")
while n >0:
show()
n-=1
在Python中,函数是可以进行嵌套的。嵌套函数中的变量由静态作用域限定的,即解释器在解析名称时首先检查局部作用域,然后由内而外一层层检查外部嵌套函数定义的作用域。如果找不到匹配,则搜索全局命名空间和内置命名空间,但内部函数不能给外部函数定义的局部变量重新赋值,以下这段代码存在问题的:
def counter(start):
n=start
# 定义嵌套函数
def show():
print(f"current value is {n}")
def decrement():
n-=1 #Pycharm会自动检查出这里有问题
while n >0:
show()
decrement()
像这种情况,Python3中可以使用关键字nonlocal来解决,如下所示:
def counter(start):
n=start
# 定义嵌套函数
def show():
print(f"current value is {n}")
def decrement():
nonlocal n # 绑定到外部的n
n-=1
while n >0:
show()
decrement()
网友评论