人工智能技术之函数之二
第8章 函数(下)
本章学习目标
• 理解间接调用函数
• 掌握匿名函数
• 掌握闭包与装饰器
• 理解偏函数
• 掌握常用的内建函数
上一章讲解了函数的基本知识,本章将带领读者继续深入学习函数,只有了解函数的高级的用法,才能更好地编写出简洁的代码,同时也方便读者阅读其他开发者的优秀代码并借鉴到自己程序中。
8.%2 间接调用函数
前面调用函数时,使用函数名加参数列表的形式调用,除此之外,还可以将函数名赋值给一个变量,再通过变量名加参数列表的形式间接调用函数,如例8-1所示。
例8-1
1 def output(message):
2 print(message)
3 output('直接调用output()函数!')
4 x = output
5 x('间接调用output()函数!')
运行结果如图8.1所示。
图8.1 运行结果
在例8-1中,第2行通过函数名直接调用output()函数,第5行通过变量x间接调用output()函数。
读者可能会疑惑,间接调用函数有何用处,这种用法可以使一个函数作为另一个函数的参数,如例8-2所示。
例8-2
1 def output(message):
2 print(message)
3 def test(func, arg):
4 func(arg)
5 test(output, '一个函数作为另一个函数的参数')
运行结果如图8.2所示。
图8.2 运行结果
在例8-2中,第5行将output()函数的函数名作为参数传入test()函数中,第4行相当于间接调用output()函数。
另外,函数还可以作为其他数据类型的元素,如例8-3所示。
例8-3
1 def output(message):
2 print(message)
3 list1 = [(output, '千锋教育'), (output, '扣丁学堂')]
4 for (func, arg) in list1:
5 func(arg)
运行结果如图8.3所示。
图8.3 运行结果
在例8-3中,第3行列表list1中的每个元素为元组,元组中的第一个元素为函数output()的函数名,第4行遍历列表list1,第5行相当于间接调用output()函数。
8.%2 匿名函数
匿名函数是指没有函数名称的、临时使用的小函数,它可以通过lambda表达式来声明,其语法格式如下:
lambda [arg1 [, arg2, ..., argn]] : 表达式
其中,“[arg1 [, arg2, ..., argn]]”表示函数的参数,“表达式”表示函数体,lambda表达式只可以包含一个表达式,该表达式的计算结果可以看作是函数的返回值,不允许包含其他复杂的语句,但在表达式中可以调用其他函数。
接下来演示lambda表达式的使用,如例8-4所示。
例8-4
1 sum = lambda num1, num2 : num1 + num2
2 print(sum(4, 5))
运行结果如图8.4所示。
图8.4 运行结果
在例8-4中,第1行使用lambda表达式声明匿名函数并赋值给sum,相等于这个函数有了函数名sum,该行相当于以下代码:
def sum(num1, num2):
return num1 + num2
使用lambda表达式声明的匿名函数也可以作为自定义函数的实参,如例8-5所示。
例8-5
1 def fun(num1, num2, func):
2 return func(num1, num2)
3 print(fun(8, 6, lambda num1, num2 : num1 + num2))
4 print(fun(8, 6, lambda num1, num2 : num1 - num2))
运行结果如图8.5所示。
图8.5 运行结果
在例8-5中,第3行使用lambda表达式作为fun()函数的实参,第2行相当于间接调用lambda表达式声明匿名函数。
此外,lambda表达式声明的匿名函数还可以作为内建函数的实参,如例8-6所示。
例8-6
1 info = [
2 {'name':'xiaoqian', 'score':'99'},
3 {'name':'xiaofeng', 'score':'90'},
4 {'name':'xiaoming', 'score':'95'}
5 ]
6 # 按姓名字母由大到小排序
7 info1 = sorted(info, key = lambda x:x['name'], reverse = True)
8 print(info1)
9 # 按分数由小到大排序
10 info2 = sorted(info, key = lambda x:x['score'])
11 print(info2)
运行结果如图8.6所示。
图8.6 运行结果
在例8-6中,第7行使用“key = lambda x:x['name']”作为sorted()函数的关键参数,此时sorted()函数将列表info中的元素按照'name'对应的值进行排序并赋值给info1,“reverse = True”指定排序规则为从大到小排序。
lambda表达式表示一个匿名函数,则也可以作为列表或字典的元素,如例8-7所示。
例8-7
1 power = [lambda x: x**2, lambda x: x**3, lambda x: x**4]
2 print(power[0](2), power[1](2), power[2](2))
3 dict1 = {1:lambda x:print(x), 2: lambda x = '扣丁学堂':print(x)}
4 dict1[1]('千锋教育')
5 dict1[2]()
运行结果如图8.7所示。
图8.7 运行结果
在例8-7中,第1行列表power中每个元素都为一个lambda表达式,即构成一个匿名函数列表,第2行分别调用列表power中的每个匿名函数,第3行字典dict1中键对应的值为lambda表达式,注意lambda表达式中也可以含有默认参数,第4行调用字典dict1中键为1对应的匿名函数,第5行调用字典dict1中键为2对应的匿名函数,此处使用默认参数。
8.%2 闭包
在前面章节中,函数可以通过return返回一个变量,此外,也可以返回一个函数名,如例8-8所示。
例8-8
1 def f1():
2 print('f1()函数')
3 def f2():
4 print('f2()函数')
5 return f1
6 x = f2()
7 x()
8 f1() # 正确
运行结果如图8.8所示。
图8.8 运行结果
在例8-8中,第5行在函数f2()中返回一个函数名f1,第6行调用f2()函数并将返回值赋值给x,第7行通过变量x间接调用f1()函数。
此外,还可以将f1()函数的定义移动到f2()中,这样f2()函数外的作用域就不能直接调用f1()函数,如例8-9所示。
例8-9
1 def f2():
2 print('f2()函数')
3 def f1():
4 print('f1()函数')
5 return f1
6 x = f2()
7 x()
8 # f1() 错误
运行结果如图8.9所示。
图8.9 运行结果
在例8-9中,函数f1()的定义嵌套在函数f2()中,此时在函数f2()外的作用域不能直接调用函数f1(),如第8行代码。
将一个函数的定义嵌套到另一个函数中,还有其他的作用,如例8-10所示。
例8-10
1 list1 = [1, 2, 3, 4]
2 def f2(list):
3 def f1():
4 return sum(list)
5 return f1
6 x = f2(list1)
7 print(x())
运行结果如图8.10所示。
图8.10 运行结果
在例8-10中,函数f2()中传入一个参数,在函数f1()中对该参数中的元素求和,具体执行过程如图8.11所示。
图8.11 程序执行过程
在图8.11中,list1作为参数传进函数f2()中,此时不能把函数f1()移到函数f2()的外面,因为函数f1()的功能是计算list中所有元素值的和,所以f1()函数必须依赖于函数f2()的参数,如果函数f1()在函数f2()外,则无法取得f2()中的数据进行计算,这就引出了闭包的概念。
如果内层函数引用了外层函数的变量(包括其参数),并且外层函数返回内层函数名,此时就称内层函数为闭包。从概念中可以得出,闭包需要满足的三个条件:
• 内层函数的定义嵌套在外层函数中
• 内层函数引用外层函数的变量
• 外层函数返回内层函数名
8.%2 装饰器
在夏天天气晴朗时,人们通常只穿T恤就可以了,但当刮风下雨时,人们通常在T恤的基础上再增加一件外套,它可以遮风挡雨,并且不影响T恤原有的作用,这就是现实生活中装饰器的概念。
8.4.1 装饰器的概念
在讲解装饰器之前,先看一段简单的程序,如例8-11所示。
例8-11
1 def f2(func):
2 def f1():
3 x = func()
4 return x + 1
5 return f1
6 def func():
7 print('func()函数')
8 return 1
9 decorated = f2(func)
10 print(decorated())
11 print(func())
运行结果如图8.12所示。
图8.12 运行结果
在例8-11中, 第1行定义了一个带单个参数func 的名称为 f2的函数,第2行f1()函数为闭包,其中调用了func()函数并将func()函数的返回值加1返回。这样每次 f2()函数被调用时,func 的值可能会不同,但不论func()代表何种函数,程序都将调用它。
从程序运行结果可看出,调用函数decorated()的返回值为2,调用func()函数的返回值为1,两者都输出“func()函数”,此时称变量decorated是func的装饰版,即在func()函数的基础上增加新功能,本例是将func()函数的返回值加1。
读者可以用装饰版来代替func,这样每次调用时就总能得到“附带其他功能”的 func 版本,如例8-12所示。
例8-12
1 def f2(func):
2 def f1():
3 return func() + 1
4 return f1
5 def func():
6 print('func()函数')
7 return 1
8 func = f2(func)
9 print(func())
运行结果如图8.13所示。
图8.13 运行结果
在例8-12中,第3行等价于例8-11中第3、4行,第8行将例8-11中第9行decorated改为func,这样每次通过函数名func调用函数时,都将执行装饰的版本。
通过上例可以得出装饰器的概念,即一个以函数作为参数并返回一个替换函数的可执行函数。装饰器的本质是一个嵌套函数,外层函数的参数是被修饰的函数,内层函数是一个闭包并在其中增加新功能。
8.4.2 @符号的应用
例8-12中使用变量名将装饰器函数与被装饰函数联系起来,此外,还可以通过@符号和装饰器名实现两者的联系,如例8-13所示。
例8-13
1 def f2(func):
2 def f1():
3 return func() + 1
4 return f1
5 @f2
6 def func():
7 print('func()函数')
8 return 1
9 print(func())
运行结果如图8.14所示。
图8.14 运行结果
在例8-13中,第5行通过@符号和装饰器名实现装饰器函数与被装饰函数联系,第9行调用func()函数时,程序会自动调用装饰器函数的代码。
8.4.3 装饰有参数的函数
装饰器除了可以装饰无参数的函数外,还可以装饰有参数的函数,如例8-14所示。
例8-14
1 def f2(func):
2 def f1(a = 0, b = 0):
3 return func(a, b) + 1
4 return f1
5 @f2
6 def func(a = 0, b = 0):
7 print('func()函数')
8 return a + b
9 print(func(2, 3))
运行结果如图8.15所示。
图8.15 运行结果
在例8-14中,第6行定义一个带有两个默认参数的func()函数,第6行将f2()函数声明为装饰器函数,用来修饰func()函数,第9行调用func装饰器函数,注意f1()函数中的参数必须包含对应func()函数的参数。
8.4.4 带参数的装饰器
通过上面的学习,装饰器本身也是一个函数,即装饰器本身也可以带参数,此时装饰器需要再多一层内嵌函数,如例8-15所示。
例8-15
1 def f3(arg = '装饰器的参数'):
2 def f2(func):
3 def f1():
4 print(arg)
5 return func() + 1
6 return f1
7 return f2
8 @f3('带参数的装饰器')
9 def func():
10 print('func()函数')
11 return 1
12 print(func())
运行结果如图8.16所示。
图8.16 运行结果
在例8-15中,第1行定义装饰器函数,其由三个函数嵌套而成,最外层函数有一个装饰器自带的参数,内层函数不变,相当于闭包的嵌套。第8行将f3()函数声明为装饰器函数,用来修饰func()函数。
若读者不理解此代码,可以将装饰器写成如下代码,如例8-16所示。
例8-16
1 def f3(arg = '装饰器的参数'):
2 def f2(func):
3 def f1():
4 print(arg)
5 return func() + 1
6 return f1
7 return f2
8 def func():
9 print('func()函数')
10 return 1
11 f2 = f3('带参数的装饰器')
12 func = f2(func)
13 print(func())
运行结果如图8.17所示。
图8.17 运行结果
在例8-16中,将装饰器分解成闭包的嵌套,这种写法更容易理解,此外,还可以将第11、12行代码写成如下代码:
func = f3('带参数的装饰器')(func)
上述代码相当于省略中间变量f2。
8.%2 偏函数
函数最重要的一个功能的是复用代码,有时在复用已有函数时,可能需要固定其中的部分参数,除了可以通过默认值参数来实现之外,还可以使用偏函数,如例8-17所示。
例8-17
1 def myAdd1(a, b, c):
2 return a + b + c
3 def myAdd2(a, b):
4 return myAdd1(a, b, 123)
5 print(myAdd2(1, 1))
运行结果如图8.18所示。
图8.18 运行结果
在例8-17中,第3行定义一个myAdd2()函数,与第1行myAdd1()函数的区别仅在于参数c固定为一个数字123,这时就可以使用偏函数的技术来复用上面的函数。
8.%2 常用的内建函数
在Python中,内建函数是被自动加载的,编程者可以随时调用这些函数,不需要定义,极大地简化了编程。
8.6.1 eval()函数
eval()函数用于对动态表达式的求值,其语法格式如下:
eval(source, globals = None, locals = None)
其中,source是动态表达式的字符串,globals和locals是求值时使用的上下文环境的全局变量和局部变量,如果不指定,则使用当前运行上下文。
接下来演示eval()函数的用法,如例8-18所示。
例8-18
1 x = 3
2 str = input('请输入包含x(x = 3)的Python表达式:')
3 print(str, '的结果为', eval(str))
运行结果如图8.19所示。
图8.19 运行结果
在例8-18中,通过input()函数输入Python表达式,接着通过eval()函数求出该表达式的值。
8.6.2 exec()函数
exec()函数用于动态语句的执行,其语法格式如下:
exec(source, globals = None, locals = None)
其中,source是动态语句的字符串,globals和locals是使用的上下文环境的全局变量和局部变量,如果不指定,则使用当前运行上下文。
接下来演示exec()函数的用法,如例8-19所示。
例8-19
1 str = input('请输入Python语句:')
2 exec(str)
运行结果如图8.20所示。
图8.20 运行结果
在例8-19中,通过input()函数输入Python语句,接着通过exec()函数执行该语句。
8.6.3 compile()函数
compile()函数用于将一个字符串编译为字节代码,其语法格式如下:
compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
其中,source为代码语句的字符串,filename为代码文件名称,如果不是从文件读取代码则传递一些可辨认的值,mode为指定编译代码的种类,其值可以为'exec'、'eval'、'single',剩余参数一般使用默认值。
接下来演示compile()函数的用法,如例8-20所示。
例8-20
1 str = input('请输入Python语句:')
2 co = compile(str, '', 'exec')
3 exec(co)
运行结果如图8.21所示。
图8.21 运行结果
在例8-20中,通过input()函数输入Python语句,接着通过compile()函数将字符串str转换为字节代码对象。
8.6.4 map()函数
程序中经常需要对列表和其他序列中的每个元素进行一个操作并把其结果集合起来,具体示例如下:
list1, list2 = [1, 2, 3, 4], []
for i in list1:
list2.append(i + 10)
print(list2)
该程序运行后,输出结果如下:
[11, 12, 13, 14]
实际上,Python提供了一个更方便的工具来完成此种操作,这就是map()函数,其语法格式如下:
map(function, sequence[, sequence, …])
其中,function为函数名,其余参数为序列,返回值为迭代器对象,通过list()函数可以将其转换为列表,也可以使用for循环进行遍历操作。
接下来演示map()函数的用法,如例8-21所示。
例8-21
1 list1 = [1, 2, 3, 4]
2 func = lambda x : x + 10
3 list2 = list(map(func, list1))
4 print(list2)
运行结果如图8.22所示。
图8.22 运行结果
在例8-21中,map()函数对列表list1中的每个元素调用func函数并将返回结果组成一个可迭代对象,如图8.23所示。
图8.23 map()函数执行过程
此外,map()函数还可以接受两个序列,具体示例如下:
list = list(map(lambda x, y : x + y, range(1, 5), range(5, 9)))
print(list)
该程序运行后,输出结果如下:
[6, 8, 10, 12]
8.6.5 filter()函数
filter()函数可以对指定序列进行过滤操作,其语法格式如下:
filter(function, sequence)
其中,function为函数名,它所引用的函数只能接受一个参数,并且返回值是布尔值,sequence为一个序列,filter()函数返回值为迭代器对象。
接下来演示filter()函数的用法,如例8-22所示。
例8-22
1 seq = ['qianfeng', 'codingke.com', '*#$']
2 func = lambda x : x.isalnum()
3 list = list(filter(func, seq))
4 print(list)
运行结果如图8.24所示。
图8.24 运行结果
在例8-22中,filter()函数对列表list中的每个元素调用func函数并返回使得func函数返回值为True的元素组成的可迭代对象,如图8.25所示。
图8.25 filter()函数执行过程
8.6.6 zip()函数
zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的迭代对象,如例8-23所示。
例8-23
1 list1, list2 = [1, 2, 3], ['千锋教育', '扣丁学堂', '好程序员特训营']
2 list3 = list(zip(list1, list2))
3 print(list3)
运行结果如图8.26所示。
图8.26 运行结果
在例8-23中,zip()函数将列表list1中第1个元素与列表list2中的1个元素组成一个元组,以此类推,最终返回由3个元组组成的迭代对象。
zip()参数可以接受任何类型的序列,同时也可以有两个以上的参数。但当传入参数的长度不同时,zip()函数以最短序列长度为准进行截取获得元组,具体示例如下:
list1, list2, list3 = [1, 2, 3, 4], ['a', {1, 'a'}], [3.4, 5]
list4 = list(zip(list1, list2, list3))
print(list4)
该程序运行后,输出结果如下:
[(1, 'a', 3.4), (2, {1, 'a'}, 5)]
此外,在zip()函数中还可以使用*运算符,如例8-24所示。
例8-24
1 list1, list2 = [1, 2, 3], ['千锋教育', '扣丁学堂', '好程序员特训营']
2 zipped = zip(list1, list2)
3 list4 = zip(*zipped)
4 print(list(list4))
运行结果如图8.27所示。
图8.27 运行结果
在例8-24中,第3行zip()函数中使用*运算符相当于执行相反的操作。
在Python中,还有许多内建函数,读者不需要记住每个函数的用法,当需要某个函数时,只需在PyCharm编辑器中写出函数名,它会自动提示函数的参数,例如,在编辑器中输入map后出现如下提示,如图8.28所示。
图8.28 函数参数提示
此时在编辑器中接着输入(),则会提示函数的每个参数类型,如图8.29所示。
图8.29 参数类型提示
8.%2 小案例
8.7.1 案例一
假设已实现用户聊天、购买商品、显示个人信息等功能,在使用这些功能前需验证用户使用的登陆方式(微信、QQ或其他)及身份信息,要求使用装饰器实现该功能,具体实现如例8-25所示。
例8-25
1 type = input('请输入登录方式:')
2 status = False
3 name, pwd= "小千", "123" # 假设从数据库中获取到用户信息
4 def login(checkType):
5 def check(func):
6 def wrapper(*args, **kwargs):
7 if checkType == 'WeChat'or checkType == 'QQ':
8 global status
9 if status == False:
10 username = input("user:")
11 password = input("pasword:")
12 if username == name and password == pwd:
13 print("欢迎%s!"%name)
14 status = True
15 else:
16 print("用户名或密码错误!")
17 if status == True:
18 func(*args,**kwargs)
19 else:
20 print('仅支持微信或QQ登陆!')
21 return wrapper
22 return check
23 @login(type)
24 def shop():
25 print('购物')
26 @login(type)
27 def info():
28 print('个人信息')
29 @login(type)
30 def chat():
31 print('聊天')
32 info()
33 chat()
34 shop()
运行结果如图8.30所示。
图8.30 运行结果
在例8-25中,使用带参数的装饰器login()修饰函数info()、chat()和shop(),这样在每次调用这三个函数时,将变得非常简单。
8.7.2 案例二
若有以下学生信息,如表8.1所示,现要求只对男同学的成绩进行由高到低排序并输出排序后学生的姓名与成绩,具体实现如例8-26所示。
表8.1 学生信息
姓名 |
性别 |
分数 |
姓名 |
性别 |
分数 |
小千 |
女 |
95 |
小丁 |
男 |
88 |
小锋 |
男 |
99 |
小明 |
男 |
90 |
小扣 |
女 |
86 |
… |
… |
… |
例8-26
1 info = [{'name':'小千', 'sex':0, 'score':95}, # 0代表女性
2 {'name':'小锋', 'sex':1, 'score':99}, # 1代表男性
3 {'name':'小扣', 'sex':0, 'score':86},
4 {'name':'小丁', 'sex':1, 'score':88},
5 {'name':'小明', 'sex':1, 'score':90}]
6 temp1 = list(filter(lambda x:x['sex'] == 1, info))
7 temp2 = list(sorted(temp1, key = lambda x:x['score'], reverse = True))
8 for x in temp2:
9 for key, value in x.items():
10 if key != 'sex':
11 print(value, end = ' ')
12 print()
运行结果如图8.31所示。
图8.31 运行结果
在例8-26中,第6行使用filter()函数过滤出'sex'为1的元素,第7行使用sorted()函数对过滤出的元素再进行降序排序。
8.%2 本章小结
本章主要介绍了Python中函数的高级用法,包括间接调用函数、匿名函数、闭包、装饰器、偏函数及常用的内建函数。通过本章的学习,读者应深刻理解闭包及装饰器的用法并应用到实际编程中。
8.%2 习题
1.填空题
(1) 若一个函数引用另一个函数的变量,则可以使用 实现。
(2) 装饰器本质上是 。
(3) 装饰器的内层函数是一个 。
(4) 在函数定义前添加装饰器名和 符号实现对函数的装饰。
(5) 带参数的装饰器实现时需多一层 函数。
2.选择题
(1) ( )函数可以对指定序列进行过滤。
A.map()函数 B.filter()函数
C.sorted()函数 D.zip()函数
(2) 匿名函数可以通过( )关键字进行声明。
A.def B.return
C.lambda D.anonymous
(3) filter()传入的函数的返回值是( )。
A.布尔值 B.字符串
C.列表值 D.元组值
(4) ( )函数根据传入的函数对指定序列做操作。
A.exec()函数 B.eval()函数
C.map()函数 D.zip()函数
(5) 若print(list(zip(range(2), range(2, 5)))),则输出( )。
A.[(0, 2), (1, 3), (0, 4)] B.[(2, 0), (3, 1)]
C.[(1, 3), (0, 4)] D.[(0, 2), (1, 3)]
3.思考题
(1) 简述闭包的概念。
(2) 简述装饰器的概念。
4.编程题
编写程序,要求使用两个装饰器装饰同一函数。
- 点赞
- 收藏
- 关注作者
评论(0)