6 函数的使用
6.1 函数基础
什么事函数
请看如下代码:
print(" _ooOoo_ ")
print(" o8888888o ")
print(" 88 . 88 ")
print(" (| -_- |) ")
print(" O\\ = /O ")
print(" ____/`---'\\____ ")
print(" . ' \\| |// `. ")
print(" / \\||| : |||// \\ ")
print(" / _||||| -:- |||||- \\ ")
print(" | | \\\\\\ - /// | | ")
print(" | \\_| ''\\---/'' | | ")
print(" \\ .-\\__ `-` ___/-. / ")
print(" ___`. .' /--.--\\ `. . __ ")
print(" ."" '< `.___\\_<|>_/___.' >'"". ")
print(" | | : `- \\`.;`\\ _ /`;.`/ - ` : | | ")
print(" \\ \\ `-. \\_ __\\ /__ _/ .-` / / ")
print(" ======`-.____`-.___\\_____/___.-`____.-'====== ")
print(" `=---=' ")
print(" ")
print(" ............................................. ")
print(" 佛祖镇楼 BUG辟易 ")
print(" 佛曰: ")
print(" 写字楼里写字间,写字间里程序员; ")
print(" 程序人员写程序,又拿程序换酒钱。 ")
print(" 酒醒只在网上坐,酒醉还来网下眠; ")
print(" 酒醉酒醒日复日,网上网下年复年。 ")
print(" 但愿老死电脑间,不愿鞠躬老板前; ")
print(" 奔驰宝马贵者趣,公交自行程序员。 ")
print(" 别人笑我忒疯癫,我笑自己命太贱; ")
print(" 不见满街漂亮妹,哪个归得程序员?")
运行后的形象:
想一想:
如果一个程序在不同的地方需要输出“佛祖镇楼”,程序应该怎样设计?
if 条件1:
输出‘佛祖镇楼’
...(省略)...
if 条件2:
输出‘佛祖镇楼’
...(省略)...
如果需要输出多次,是否意味着要编写这块代码多次呢?
小总结
- 如果在开发程序时,需要某块代码多次执行。为了提高编写的效率以及更好的维护代码,需要把具有独立功能的代码块组织为一个小模块,这就是函数。
6.1.1 函数的定义和调用
一、定义函数
-
函数的定义:
函数就是对实现某一特定功能的代码段的封装
-
函数的分类:
- 系统函数 - 系统已经定义好的函数。如:print、input、type、ord、bin、len、id等
- 自定义函数 - 用户自己定义的函数
定义函数的格式如下:
def 函数名(新参列表):
函数说明文档
代码
# 函数名命名规范:
# a.所有字母都小写,多个单词之间用下划线隔开
# b.不能与系统提供的函数名、类型重名
# c.顾名思义,能简明的知晓函数的功能
# 注意:函数在定义时不会执行函数体,只有在调用时才会执行
# 初学者定义函数的步骤
# 第一步:确定函数的功能
# 第二步:根据函数功能确定函数名
# 第三步:确定函数的参数(看实现函数的功能需不需要额外的数据,需要几个)
# 第四步:实现函数功能
# 第五步:确定函数返回值
# 第六步:写函数说明文档
示例:
# 定义一个函数,能够完成打印信息的功能
def printInfo():
'''
可以打印'人生苦短,我用Python'
'''
print('------------------------------------')
print(' 人生苦短,我用Python')
print('------------------------------------')
# 需求:定义一个函数实现求任意两个数的和的功能
def sum_two_nums(num1, num2):
'''
求两个数的和
:param num1:第一个数
:param num2: 第二个数
:return: 和
'''
sum = num1 + num2
return sum
# 练习:打印指定字符串中大写字母的个数
def count_upper_aphla(str1):
'''
打印指定字符串中大写字母的个数
:param str1:指定字符串
'''
count = 0
for x in str1:
if 'A' <= x <= 'Z':
count += 1
print(count)
二、调用函数
定义了函数之后,就相当于有了一个具有某些功能的代码,想要让这些代码能够执行,需要调用它
调用函数很简单的,通过 函数名(实参列表) 即可完成调用
# 定义完函数后,函数是不会自动执行的,需要调用它才可以
printInfo()
三、注意
- 函数定义好以后,函数体里的代码并不会执行,如果想要执行函数体里的内容,需要手动的调用函数。
- 每次调用函数时,函数都会从头开始执行,当这个函数中的代码执行完毕后,意味着调用结束了。
- 当然了如果函数中执行到了return也会结束函数。
四、练一练
要求:定义一个函数,能够计算两个数字之和,并且调用这个函数让它执行
- 使用def定义函数
- 编写完函数之后,通过 函数名() 进行调用
6.1.2 函数的参数
思考一个问题,如下:
现在需要定义一个函数,这个函数能够完成2个数的加法运算,并且把结果打印出来,该怎样设计?下面的代码可以吗?有什么缺陷吗?
def add2num():
a = 11
b = 22
c = a+b
print(c)
为了让一个函数更通用,即想让它计算哪两个数的和,就让它计算哪两个数的和,在定义函数的时候可以让函数接收数据,就解决了这个问题,这就是 函数的参数
一、定义、调用带有参数的函数
定义一个add2num(a, b)函数,来计算任意两个数字之和:
def add2num(a, b):
c = a+b
print c
add2num(11, 22) # 调用带有参数的函数时,需要在小括号中,传递数据
注意点:
- 在定义函数的时候,小括号里写等待赋值的变量名
- 在调用函数的时候,小括号里写真正要进行运算的数据
调用带有参数函数的运行过程:
二、练一练
要求:定义一个函数,完成前2个数完成加法运算,然后对第3个数,进行减法;然后调用这个函数
- 使用def定义函数,要注意有3个参数
- 调用的时候,这个函数定义时有几个参数,那么就需要传递几个参数
三、调用函数时参数的顺序
>>> def test(a,b):
... print(a,b)
...
>>> test(1,2) # 位置参数
1 2
>>> test(b=1,a=2) # 关键字参数
2 1
>>>
>>> test(b=1,2) # 关键字参数写在位置参数之前会导致出错
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
四、小总结
- 定义时小括号中的参数,用来接收参数用的,称为 “形参”
- 调用时小括号中的参数,用来传递给函数用的,称为 “实参”
6.1.3 函数的返回值
一、返回值的介绍
现实生活中的场景:
我给儿子10块钱,让他给我买个冰淇淋。这个例子中,10块钱是我给儿子的,就相当于调用函数时传递到参数,让儿子买冰淇淋这个事情最终的目标,我需要让他把冰淇淋带回来,此时冰淇淋就是返回值
开发中的场景:
定义了一个函数,完成了获取室内温度,想一想是不是应该把这个结果给调用者,只有调用者拥有了这个返回值,才能够根据当前的温度做适当的调整
综上所述:
- 所谓“返回值”,就是程序中函数完成一件事情后,最后给调用者的结果
- 使用返回值的前提需求就是函数调用者想要在函数外使用计算结果
- 返回值是从函数内部传递到函数外部的数据
二、带返回值的函数
想要在函数中把结果返回给调用者,需要在函数中使用return
return也可以直接结束函数,即在执行函数体时,遇到了return,函数直接结束
# 语法
return 表达式1, 表达式2,...
# 说明:
# 表达式可以一个都没有(None),可以有一个(返回指定数据),也可以是多个(返回一个元组)
# 什么时候需要返回值(初学者)
# 看实现函数的功能,如果产生新的数据且在函数外部需要使用
# 注意:如果一个函数中没有return,那么这个函数的返回值为None
如下示例:
def add2num(a, b):
c = a+b
return c # return 后可以写变量名
list1 = [12,13,56,8]
print(list1.sort()) # None
或者
def add2num(a, b):
return a+b # return 后可以写计算表达式
三、保存函数的返回值
在本小节刚开始的时候,说过的“买冰淇淋”的例子中,最后儿子给你冰淇淋时,你一定是从儿子手中接过来 对么,程序也是如此,如果一个函数返回了一个数据,那么想要用这个数据,那么就需要保存
保存函数的返回值示例如下:
#定义函数
def add2num(a, b):
return a+b
#调用函数,顺便保存函数的返回值
result = add2num(100,98)
#因为result已经保存了add2num的返回值,所以接下来就可以使用了
print(result)
结果:
198
四、函数调用的过程
- 回到函数定义部分
- 用实参给形参赋值(必须保证每个参数都有值)
- 执行函数体
- 确定返回值
- 回到函数调用的位置继续执行
6.1.4 函数的嵌套调用
def testB():
print('---- testB start----')
print('这里是testB函数执行的代码...(省略)...')
print('---- testB end----')
def testA():
print('---- testA start----')
testB()
print('---- testA end----')
testA()
结果:
---- testA start----
---- testB start----
这里是testB函数执行的代码...(省略)...
---- testB end----
---- testA end----
小总结
- 一个函数里面又调用了另外一个函数,这就是所谓的函数嵌套调用
- 如果函数A中,调用了另外一个函数B,那么先把函数B中的任务都执行完毕之后才会回到上次 函数A执行的位置
6.1.5 函数的文档注释
1.基本使用
>>> def test(a,b):
... "用来完成对2个数求和" # 函数第一行写一个字符串作为函数文档
... print("%d"%(a+b))
...
>>>
>>> test(11,22) # 函数可以正常调用
33
>>>
>>> help(test) # 使用 help 查看test函数的文档说明
Help on function test in module __main__:
test(a, b)
用来完成对2个数求和
使用效果
2.高级使用
def get_info(name: str, age: int):
"""
接收用户的名字和年龄,拼接一个字符串并返回
:param name: 接收一个名字
:param age: 接收用户的年龄,必须是 0-200 间的一个整数
:return: 返回拼接好的字符串
"""
return "我的名字叫 %s,今年是 %d 岁" % (name, age)
get_info("吴彦祖", 19)
get_info(520, 19) # 注意,形参上标注的类型只是提高代码的可读性,并不会限制实参的类型
help(get_info)
使用效果
6.2 函数应用
函数应用:打印图形和数学计算
目标
- 感受函数的嵌套调用
- 感受程序设计的思路,复杂问题分解为简单问题
思考&实现1
- 写一个函数打印一条横线
- 打印自定义行数的横线
参考代码1
# 打印一条横线
def printOneLine():
print("-"*30)
# 打印多条横线
def printNumLine(num):
i=0
# 因为printOneLine函数已经完成了打印横线的功能,
# 只需要多次调用此函数即可
while i<num:
printOneLine()
i+=1
printNumLine(3)
思考&实现2
- 写一个函数求三个数的和
- 写一个函数求三个数的平均值
参考代码2
# 求3个数的和
def sum3Number(a,b,c):
return a+b+c # return 的后面可以是数值,也可是一个表达式
# 完成对3个数求平均值
def average3Number(a,b,c):
# 因为sum3Number函数已经完成了3个数的就和,所以只需调用即可
# 即把接收到的3个数,当做实参传递即可
sumResult = sum3Number(a,b,c)
aveResult = sumResult/3.0
return aveResult
# 调用函数,完成对3个数求平均值
result = average3Number(11,2,55)
print("average is %d"%result)
6.3 函数高级
6.3.1 局部变量
'''
压栈:每次在调用函数的时候,系统会自动为这个函数创建一个独立的栈区间,用来保存函数运行过程中产生的数据(函数的参数;函数中声明的变量),当函数调用结束后这个栈区间会自动销毁
'''
一、什么是局部变量
如下图所示:
二、小总结
- 局部变量,就是在函数内部定义的变量
- 其作用范围是这个函数内部,即只能在这个函数中使用,在函数的外部是不能使用的
- 因为其作用范围只是在自己的函数内部,所以不同的函数可以定义相同名字的局部变量(打个比方,把你、我是当做成函数,把局部变量理解为每个人手里的手机,你可有个iPhone8,我当然也可以有个iPhone8了, 互不相关)
- 局部变量的作用,为了临时保存数据需要在函数中定义变量来进行存储
- 当函数调用时,局部变量被创建,当函数调用完成后这个变量就不能够使用了
- 注意:定义函数的时候给形参赋的默认值其实是保存在堆里面
6.3.2 全局变量
一、什么是全局变量
如果一个变量,既能在一个函数中使用,也能在其他的函数中使用,这样的变量就是全局变量
打个比方:有2个兄弟 各自都有手机,各自有自己的小秘密在手机里,不让另外一方使用(可以理解为局部变量);但是家里的电话是2个兄弟都可以随便使用的(可以理解为全局变量)
# 定义全局变量
a = 100
def test1():
print(a) # 虽然没有定义变量a但是依然可以获取其数据
def test2():
print(a) # 虽然没有定义变量a但是依然可以获取其数据
# 调用函数
test1()
test2()
运行结果:
总结1:
- 在函数外边定义的变量叫做
全局变量
- 全局变量能够在所有的函数中进行访问
二、全局变量和局部变量名字相同问题
看如下代码:
总结2:
- 当函数内出现局部变量和全局变量相同名字时,函数内部中的
变量名 = 数据
此时理解为定义了一个局部变量,而不是修改全局变量的值
三、修改全局变量
函数中进行使用时可否进行修改呢?
代码如下:
总结3:
- 如果在函数中出现
global 全局变量的名字
那么这个函数中即使出现和全局变量名相同的变量名 = 数据
也理解为对全局变量进行修改,而不是定义局部变量 - 如果在一个函数中需要对多个全局变量进行修改,那么可以一次性全部声明,也可以分开声明,声明必须放在使用变量之前
# 可以使用一次global对多个全局变量进行声明
global a, b
# 还可以用多次global声明都是可以的
# global a
# global b
- 如果希望在局部的局部中修改局部变量的值,就可以使用
nonlocal
进行说明
四、查看所有的全局变量和局部变量
Python提供了两个内置函数globals()和locals()可以用来查看所有的全局变量和局部变量。
def test():
a = 100
b = 40
print(locals()) # {'a': 100, 'b': 40}
test()
x = 'good'
y = True
print(globals()) # {'__name__': '__main__', '__doc__': None, '__p
6.3.3 函数返回值详解
在python中我们怎样返回多个值?
一、多个return?
def create_nums():
print("---1---")
return 1 # 函数中下面的代码不会被执行,因为return除了能够将数据返回之外,还有一个隐藏的功能:结束函数
print("---2---")
return 2
print("---3---")
总结1:
-
一个函数中可以有多个return语句,但是只要有一个return语句被执行到,那么这个函数就会结束了,因此后面的return没有什么用处
-
如果程序设计为如下,是可以的因为不同的场景下执行不同的return
def create_nums(num): print("---1---") if num == 100: print("---2---") return num+1 # 函数中下面的代码不会被执行,因为return除了能够将数据返回之外,还有一个隐藏的功能:结束函数 else: print("---3---") return num+2 print("---4---") result1 = create_nums(100) print(result1) # 打印101 result2 = create_nums(200) print(result2) # 打印202
二、一个函数返回多个数据的方式
def divid(a, b):
shang = a//b
yushu = a%b
return shang, yushu #默认是元组
result = divid(5, 2)
print(result) # 输出(2, 1)
总结2:
-
return后面可以是元组,列表、字典等,只要是能够存储多个数据的类型,就可以一次性返回多个数据。
def function(): # return [1, 2, 3] # return (1, 2, 3) return {"num1": 1, "num2": 2, "num3": 3}
-
如果return后面有多个数据,那么默认是元组。
6.3.4 函数参数详解
一、缺省参数
调用函数时,缺省参数的值如果没有传入,则取默认值。
下例会打印默认的age,如果age没有被传入:
def printinfo(name, age=35):
# 打印任何传入的字符串
print("name: %s" % name)
print("age %d" % age)
# 调用printinfo函数
printinfo(name="miki") # 在函数执行过程中 age取默认值35
printinfo(age=9 ,name="miki")
以上实例输出结果:
name: miki
age: 35
name: miki
age: 9
总结:
-
在形参中默认有值的参数,称之为缺省参数
-
注意:带有默认值的参数一定要位于参数列表的最后面
>>> def printinfo(name, age=35, sex): ... print name ... File "<stdin>", line 1 SyntaxError: non-default argument follows default argument
二、不定长参数
有时可能需要一个函数能处理比当初声明时更多的参数, 这些参数叫做不定长参数,声明时不会命名。
基本语法如下:
def functionname([formal_args,] *args, **kwargs):
"""函数_文档字符串"""
function_suite
return [expression]
注意:
- 加了星号(*)的变量args会存放所有未命名的变量参数,args为元组
- 而加**的变量kwargs会存放命名参数,即形如key=value的参数, kwargs为字典.
def test(a, b, *args, **kwargs):
"函数在声明时,需要两个参数"
print('a={},b={},args={},kwargs={}'.format(a,b,args,kwargs))
test(2, 3, '你好', 'hi', 'how do you do', name="zhangsan", age=18)
# a=2,b=3,args=('你好', 'hi', 'how do you do'),kwargs={'name': 'zhangsan', 'age': 18}
b = ('hi', '大家好', '今天天气真好')
d = {'name': "zhangsan", "age": 19}
# 注意,在传入参数时的星号问题。
test(10, 20, *b, **d)
# a=10,b=20,args=('hi', '大家好', '今天天气真好'),kwargs={'name': 'zhangsan', 'age': 19}
# 如果在传值时,不使用星号,会把后面的参数当做 args
test(10,20,b,d)
# a=10,b=20,args=(('hi', '大家好', '今天天气真好'), {'name': 'zhangsan', 'age': 19}),kwargs={}
三、缺省参数在*args后面
def sum_nums_3(a, *args, b=22, c=33, **kwargs):
print(a)
print(b)
print(c)
print(args)
print(kwargs)
sum_nums_3(100, 200, 300, 400, 500, 600, 700, b=1, c=2, mm=800, nn=900)
说明:
- 如果很多个值都是不定长参数,那么这种情况下,可以将缺省参数放到 *args的后面, 但如果有**kwargs的话,**kwargs必须是最后的
6.3.5 拆包
对返回的数据直接拆包
def get_my_info():
high = 178
weight = 100
age = 18
return high, weight, age # 函数返回三个数据,会自动打包为元组
# result = get_my_info() # result 接收到一个元组
# print(result)
my_high, my_weight, my_age = get_my_info() # 直接把元组拆分为三个变量来使用,更加方便
print(my_high)
print(my_weight)
print(my_age)
总结:
- 拆包时要注意,需要拆的数据的个数要与变量的个数相同,否则程序会异常
- 除了对元组拆包之外,还可以对列表、字典等拆包
In [17]: a, b = (11, 22)
In [18]: a
Out[18]: 11
In [19]: b
Out[19]: 22
In [20]: a, b = [11, 22]
In [21]: a
Out[21]: 11
In [22]: b
Out[22]: 22
In [23]: a, b = {"m":11, "n":22} # 取出来的是key,而不是键值对
In [24]: a
Out[24]: 'm'
In [25]: b
Out[25]: 'n'
6.3.6 引用
想一想
>>> a = 1
>>> b = a
>>> b
1
>>> a = 2
>>> a
2
Copy
请问此时b的值为多少?
>>> a = [1, 2]
>>> b = a
>>> b
[1, 2]
>>> a.append(3)
>>> a
[1, 2, 3]
Copy
请问此时b的值又是多少?
引用
在python中,值是靠引用来传递来的。
我们可以用id()来判断两个变量是否为同一个值的引用。 我们可以将id值理解为那块内存的地址标示。
>>> a = 1
>>> b = a
>>> id(a)
13033816
>>> id(b) # 注意两个变量的id值相同
13033816
>>> a = 2
>>> id(a) # 注意a的id值已经变了
13033792
>>> id(b) # b的id值依旧
13033816
>>> a = [1, 2]
>>> b = a
>>> id(a)
139935018544808
>>> id(b)
139935018544808
>>> a.append(3)
>>> a
[1, 2, 3]
>>> id(a)
139935018544808
>>> id(b) # 注意a与b始终指向同一个地址
139935018544808
总结:
- 之前为了更好的理解变量,咱们可以把
a=100
理解为变量a中存放了100,事实上变量a存储是100的引用(可理解为在内存中的一个编号)
6.3.7 可变数据类型和不可变数据类型
可变、不可变类型
总结
- 所谓可变类型与不可变类型是指:数据能够直接进行修改,如果能直接修改那么就是可变,否则是不可变
- 可变类型(修改数据,内存地址不会发生变化)有: 列表、字典、集合
- 不可变类型(修改数据,内存地址必定发生变化)有: 数字、字符串、元组
6.3.8 参数的值传递
引用当做实参
- 可变类型与不可变类型的变量分别作为函数参数时,会有什么不同吗?
def test1(b): # 变量b一定是一个局部变量,就看它指向的是谁,可变还是不可变
b += b
print(b)
a = 100
test1(a) # 200
print(a) # 100
c = [100,200]
test1(c) # [100,200,100,200]
print(c) # [100,200,100,200]
总结:
- Python中函数参数是传递引用,也就是数据的内存地址
- 对于不可变类型,修改形参,不影响实参
- 对于可变类型来说,修改形参,会影响实参
6.4 递归函数
1 什么是递归函数
通过前面的学习知道一个函数可以调用其他函数。
如果一个函数在内部不调用其它的函数,而是自己本身的话,这个函数就是递归函数。
2 递归函数的作用
举个例子,我们来计算阶乘 n! = 1 * 2 * 3 * ... * n
解决办法1:使用循环来完成
def cal(num):
result,i = 1,1
while i <= num:
result *= i
i+= 1
return result
print(cal(3))
看阶乘的规律
1! = 1
2! = 2 × 1 = 2 × 1!
3! = 3 × 2 × 1 = 3 × 2!
4! = 4 × 3 × 2 × 1 = 4 × 3!
...
n! = n × (n-1)!
解决办法2:使用递归来实现
def factorial(num):
result = 1
if num == 1:
return 1
result = num * factorial(num -1)
return result
print(cal(3))
原理
- 练习:使用递归实现斐波那契数列。1、1、2、3、5、8、13、21、34、……
def fib(n):
# 1. 找临界值
if n == 1 or n == 2:
return 1
# 2. 找关系:fib(n) = fib(n-1) + fib(n-2)
return fib(n-1) + fib(n-2)
print(fib(6))
- 计算1+ 2 + 3 + 4 + … + n
def sum1(n):
# 1.找临界值
if n == 1:
return 1
# 2.关系:sum1(n) = sum1(n-1) + n
return sum1(n-1) + n
print(sum1(100))
3 怎么使用递归
1) 确定临界值(在临界值的位置结束函数)
2)找关系(找当次循环和上一次循环之间的关系)
3)假设函数的功能已经实现,用 f(n-1) 去实现 f(n) 的功能
6.5 匿名函数
用lambda关键词能创建小型匿名函数。这种函数得名于省略了用def声明函数的标准步骤。
lambda函数的语法只包含一个语句,如下:
lambda 参数列表: 运算表达式
调用匿名函数
函数(实参列表)
# 匿名函数的本质是类型为function的数据
如下实例:
sum = lambda arg1, arg2: arg1 + arg2
# 调用sum函数
print("Value of total : %d" % sum( 10, 20 ))
print("Value of total : %d" % sum( 20, 20 ))
以上实例输出结果:
Value of total : 30
Value of total : 40
lambda函数能接收任何数量的参数但只能返回一个表达式的值
匿名函数可以执行任意表达式(甚至print函数),但是一般认为表达式应该有一个计算结果供返回使用。
python在编写一些执行脚本的时候可以使用lambda,这样可以接受定义函数的过程,比如写一个简单的脚本管理服务器。
应用场合
函数作为参数传递
>>> def fun(a, b, opt):
... print("a = " % a)
... print("b = " % b)
... print("result =" % opt(a, b))
...
>>> add = lambda x,y:x+y
>>> fun(1, 2, add) # 把 add 作为实参传递
a = 1
b = 2
result = 3
练习:
有一个列表
students = [
{'name': 'zhangsan', 'age': 18, 'score': 92},
{'name': 'lisi', 'age': 20, 'score': 90},
{'name': 'wangwu', 'age': 19, 'score': 95},
{'name': 'jerry', 'age': 21, 'score': 98},
{'name': 'chris', 'age': 17, 'score': 100},
]
要求,对上述列表里的数据按照score进行升序排序。
Python中使用函数作为参数的内置函数和类:
函数名或类名 | 功能 | 参数描述 |
---|---|---|
sorted函数 | 用来将一个无序列表进行排序 | 函数参数的返回值规定按照元素的哪个属性进行排序 |
filter类 | 用来过滤一个列表里符合规定的所有元素,得到的结果是一个迭代器 | 函数参数的返回值指定元素满足的过滤条件 |
map类 | 将列表里的每一项数据都执行相同的操作,得到的结果是一个迭代器 | 函数参数用来指定列表里元素所执行的操作 |
reduce函数 | 对一个序列进行压缩运算,得到一个值。python3以后,这个方法被移到了functools模块 | 函数参数用来指定元素按照哪种方式合并 |
6.6 高阶函数
在Python中,函数其实也是一种数据类型。
def test():
return 'hello world'
print(type(test)) # <class 'function'>
函数对应的数据类型是 function
,可以把它当做是一种复杂的数据类型。
既然同样都是一种数据类型,我们就可以把它当做数字或者字符串来处理。
定义一个变量指向函数
在Python中,我们还可以定义一个变量,让它来指向一个函数,相当于给函数起了一个别名。
def test():
return 'hello wrold'
fun = test # 定义了一个变量fun,让它指向了 test 这个函数
print(fun()) # 使用fun()可以直接调用test这个函数
print(id(fun)) # 1819677672040
print(id(test)) # 1819677672040
注意:在定义一个变量表示一个函数时,函数后面不能加括号!加括号表示的是调用这个函数。
def test():
return 'hello world'
result = test() # 这种写法是调用test函数,并把函数的返回值赋值给result变量
print(result()) # 这里会报错 TypeError: 'str' object is not callable
fun = test # 这种写法是给test函数起了一个别名,注意,这里的test后面不能加()
fun() # 可以使用别名调用这个函数
高阶函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,同样,我们还可以把一个函数当做另一个函数的返回值。这种函数的使用方式我们称之为高阶函数。
函数做为另一个函数的参数(参数是函数的函数就是实参高阶函数)
def test(age,action):
if age < 18:
print('您还没满十八岁,请退出')
action() # 把参数action直接当做一个函数来调用
def smoke():
print('我已经年满十八岁了,我想抽烟')
my_action = smoke # 定义一个变量my_action,让它指向smoke函数
test(21, my_action) # 将my_action传给 test 函数作为它的参数
test(21,smoke) # 还可以不再定义一个新的变量,直接传入函数名
系统提供的常见的实参高阶函数:
max、min、sorted
: 有一个参数key需要传一个函数,被传入的函数需要一个参数和一个返回值,这个参数指向的是序列中的元素,返回值是比较对象。
# 实例1:求列表中数字各位数和最大的元素
list1 = [19,90,78,67]
def func3(item):
sum1 = 0
for x in str(item):
sum1 += int(X)
return sum1
print(max(list1,key=func3))
# 实例2:求列表中个位数最大的元素
print(max(list1, key=lambda item : item % 10 ))
# 实例3:用max函数获取学生列表中成绩最高的学生
stu = [
{'name': '张三', 'age': 18, 'score': 89},
{'name': '小明', 'age': 29, 'score': 60},
{'name': '李四', 'age': 25, 'score': 90},
{'name': 'Tom', 'age': 19, 'score': 87}
]
print(max(stu, key=lambda item: item['score']))
# 获取年龄最小的学生
print(min(stu,key = lambda item:item['age']))
# 将学生列表按年龄值从小到大排序
print(sorted(stu,key = lambda item:item['age']))
map
: map(函数,序列) - 将序列中所有的元素按照函数指定的规则进行转换,返回的是转换后的序列(类型是容器)。函数需要一个参数和一个返回值,参数指向的是序列中的元素,返回值就是用来替换原来元素的新元素。
# 实例1:将列表中的所有元素加1
list1 = [10,20,30,40]
new_list = map(lambda item : item+1, list1)
print(list(new_list))
# 实例2:将列表中的所有元素都转换成对应的字符串
new_list = map(str, list1)
print(list(new_list))
reduce
:- reduce(函数,序列) - 对序列中的元素按照函数提供的功能进行累积的操作。函数需要两个参数,第一个参数是初始值或者上次运行的结果,第二个参数是序列中的元素。需要一个返回值
- reduce(函数,序列,初始值) - 初始值为第一个参数提供初始值,默认取0或者None。
# 实例1:求所有元素的和
from functools import reduce
list1 = [10,20,30,40]
res = reduce(lambda x,y:x+y,list1)
print(res)
# 实例2:求所有元素的乘积
res = reduce(lambda x,y:x*y,list1)
print(res)
# 实例3:求整个班级所有学生的总成绩
stu = [
{'name': '张三', 'age': 18, 'score': 89},
{'name': '小明', 'age': 29, 'score': 60},
{'name': '李四', 'age': 25, 'score': 90},
{'name': 'Tom', 'age': 19, 'score': 87}
]
res = reduce(lambda x, y: x + y['score'], stu, 0)
print(res)
函数作为另一个函数的返回值(返回值高阶函数)
def test():
print('我是test函数里输入的内容')
def demo():
print('我是demo里输入的内容')
return test # test 函数作为demo函数的返回值
result = demo() # 我是demo里输入的内容 调用 demo 函数,把demo函数的返回值赋值给 result
print(type(result)) # <class 'function'> result 的类型是一个函数
result() # 我是demo里输入的内容 我是test函数里输入的内容 既然result是一个函数,那么就可以直接使用() 调用这个函数
demo()() # 我是demo里输入的内容 我是test函数里输入的内容
6.7 闭包
函数只是一段可执行代码,编译后就“固化”了,每个函数在内存中只有一份实例,得到函数的入口点便可以执行函数了。函数还可以嵌套定义,即在一个函数内部可以定义另一个函数,有了嵌套函数这种结构,便会产生闭包问题。
函数嵌套
在函数里面还可以定义函数,可以嵌套多层,执行需要被调用。
def outer():
print('outer----hello')
def inner(): # inner这个函数是在outer函数内部定义的
print('inner----hello')
inner() # inner函数只在outer函数内部可见
outer()
# inner() 这里会报错,在outer函数外部无法访问到inner函数
什么是闭包
闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数块+引用环境)。
def outer(n):
num = n
def inner():
return num+1
return inner
print(outer(3)()) # 4
print(outer(5)()) # 5
在这段程序中,函数 inner 是函数 outer 的内嵌函数,并且 inner 函数是outer函数的返回值。我们注意到一个问题:内嵌函数 inner 中引用到外层函数中的局部变量num,Python解释器会这么处理这个问题呢? 先让我们来看看这段代码的运行结果,当我们调用分别由不同的参数调用 outer 函数得到的函数时,得到的结果是隔离的(相互不影响),也就是说每次调用outer函数后都将生成并保存一个新的局部变量num,这里outer函数返回的就是闭包。 如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure).
修改外部变量的值
闭包里默认不能修改外部变量。
def outer(n):
num = n
def inner():
num = num + 1
return num
return inner
print(outer(1)())
上述代码运行时会报错!
UnboundLocalError: local variable 'num' referenced before assignment
原因分析
在python里,只要看到了赋值语句,就会认为赋值语句的左边是一个局部变量。num = num + 1
这段代码里,num
在=
的左边,python解析器会认为我们要修改inner
函数里num
这个局部变量,而这个变量使用之前是未声明的,所以会报错。
解决方案
我们分析过,报错的原因在于当我们在闭包内修改外部变量时,会被python解析器误会为内部函数的局部变量。所以,解决方案就在于,我们需要想办法,让解析器知道我们不是要修改局部变量,而是要修改外部变量。
- 解决方法:使用 nonlocal 关键字
def outer(n):
num = n
def inner():
nonlocal num # 修改前使用nonlocal关键字对 num 变量进行说明
num = num + 1
return num
return inner
print(outer(2)())
6.8 装饰器
- 装饰器的作用:在不修改函数的情况下,给函数添加新的功能。
- 什么是装饰器:装饰器的本质就是一个函数(这个函数既是实参高阶函数,也是返回值高阶函数)
- 无参装饰器的语法:
def 函数名1(函数名2):
def 函数名3(*args, **kwargs):
新功能代码
返回值 = 函数名2(*args, **kwargs)
return 返回值
return 函数名3
'''
说明:
函数名1 - 装饰器名字,命名时和这个装饰器要添加的功能进行关联
函数名2 - 随便命名,指向被添加的函数;可以命名成fn
函数名3 - 随便命名,在原函数上添加完新的功能以后产生新的函数
新功能代码 - 实现新加的功能的代码
'''
'''
补充:定义函数时,*args和**kwargs同时存在的意义?
答:不定长参数的函数在调用的时候既可以使用位置参数,也可以使用关键字参数
'''
实例:
# 写一个装饰器,在函数开始执行前打印‘函数开始’
def start_function(fn):
def new_fn(*args, **kwargs):
print('函数开始')
res = fn(*args, **kwargs)
return res
return new_fun
@start_function
def sum1(num1, num2):
return (num1 + num2)
sum1(10, 20)
练习:写一个装饰器将返回值是字符串的函数,返回值中所有的小写字母变为大写字母。
def upper(fn):
def add_fn(*args, **kwargs):
res = fn(*args, **kwargs)
if type(res) == str:
res = res.upper()
return res
return add_fn
@upper
def str_func():
return 'abc'
print(str_func())
6.9 迭代器
- 什么是迭代器(iter)
-
迭代器是容器型数据类型,只能通过类型转换和生成器来获得迭代器对象
-
迭代器存储数据的特点:同时可以保存多个数据,没有办法直接查看,而是需要先将数据从迭代器中取出来(取出来之后不能再放回去)
-
生成迭代器
-
所有的容器型数据都可以转换成迭代器
print(iter([1,2,3])) print(iter('abc')) print(iter({'name':'张三','age':18}))
-
- 获取迭代器中的元素
-
无论通过什么样的方式,只要将迭代器中的某个元素被取出后,这个元素在迭代器中就不存在了
-
len
不能获取迭代器的长度 -
方法一:遍历
iter1 = iter('hello') for i in iter1: print(i) # iter1已经没有元素了 print(list(iter1)) # []
方法二:获取单个元素
next(迭代器对象) - 获取迭代器中的一个元素(当前最前面的那个元素)
iter1 = iter({'name':'张三','age':18}) print(next(iter1)) # name print(next(iter1)) # age print(next(iter1)) # 报错 StopIteration
6.10 生成器
-
什么是生成器
a. 生成器就是迭代器
b. 调用一个带有
yield
关键字的函数就可以得到一个生成器
def func():
print('+++++++')
print('-------')
yield
res = func()
print(res) #<generator object func at 0x000001B91EB54480>
'''既没有执行函数体里的打印,返回值也不是None,是一个生成器的地址。'''
- 生成器产生数据
- 生成器能生产多少个数据就看执行生成器对应的函数的函数体会遇到几次
yield
,yield
后面表达式的值就是生成器能产生的数据
# 实例一:创建一个生成器能产生10,20,30.
def create_gender():
yield 10
yield 20
yield 30
gen1 = create_gender()
print(list(gen1))
- 生成器产生数据的原理
- 当获取生成器元素的时候,会自动调用生成器关联的函数。第一次从函数开始的地方开始执行,直到遇到
yield
为止,并将yield
后面表达式的值作为获取到的数据;后面每次都是从上一次结束的位置开始执行,直到遇到yield
如果从开始执行到函数结束都没有遇到yield
就不会产生数据(如果用next
去取数据,此时会报错)
def create_gender1():
for x in range(100,150):
print('函数开始')
yield x
gen2 = create_gender1()
print(f'元素:{next(gen2)}')
练习:写一个产生4位验证码的生成器(验证码由随机的4位数字和字母组成)
import random
def code_gender():
alp = 'abcdefghijklmnopqrstuvwxyz'
alp += alp.upper() + '0123456789'
while True:
yield ''.join(random.choices(alp, k=4))
alnum = code_gender()
print(next(alnum))
print(next(alnum))
注意:每次调用函数都是在创建一个新的生成器对象
- 生成式
-
生成式就是生成器的简写
-
列表推导式的[ ]变成( )就变成了生成式
# 列表推导式
list1 = [x*2 for x in range(5)]
print(list1)
# 生成式
gen1 = (x*2 for x in range(5))
print(gen1) # <generator object <genexpr> at 0x0000017B177A4C78>
print(next(gen1)) # 0
print(next(gen1)) # 2
'''
# gen1 = (x*2 for x in range(5))相当于:
def func():
for x in range(5):
yield x*2
gen1 = func()
'''
6.11 函数综合练习
函数应用:名片管理系统
# 定一个列表,用来存储所有的名片信息(每个名片是一个字典)
info_list = []
def print_menu():
print("---------------------------")
print(" 名片管理系统 V1.0")
print(" 1:添加名片")
print(" 2:删除名片")
print(" 3:修改名片")
print(" 4:查询名片")
print(" 5:显示所有名片")
print(" 6:退出系统")
print("---------------------------")
def add_new_info():
"""添加名片信息"""
new_name = input("请输入姓名:")
new_tel = input("请输入手机号:")
new_qq = input("请输入QQ:")
for temp_info in info_list:
if temp_info['name'] == new_name:
print("此用户名已经被占用,请重新输入")
return # 如果一个函数只有return就相当于让函数结束,没有返回值
# 定义一个字典,用来存储用户的名片信息(这是一个字典)
info = {}
# 向字典中添加数据
info["name"] = new_name
info["tel"] = new_tel
info["qq"] = new_qq
# 向列表中添加这个字典
info_list.append(info)
def del_info():
"""删除名片信息"""
del_num = int(input("请输入要删除的序号:"))
if 0 <= del_num < len(info_list):
del_flag = input("你确定要删除么?yes or no")
if del_flag == "yes":
del info_list[del_num]
else:
print("输入序号有误,请重新输入")
def modify_info():
"""修改名片信息"""
modify_num = int(input("请输入要修改的序号:"))
if 0 <= modify_num < len(info_list):
print("你要修改的信息是:")
print("name:%s, tel:%s, QQ:%s" % (info_list[modify_num]['name'],
info_list[modify_num]['tel'],info_list[modify_num]['qq']))
info_list[modify_num]['name'] = input("请输入新的姓名:")
info_list[modify_num]['tel'] = input("请输入新的手机号:")
info_list[modify_num]['qq'] = input("请输入新QQ:")
else:
print("输入序号有误,请重新输入")
def search_info():
"""查询名片信息"""
search_name = input("请输入要查询的名片姓名:")
for temp_info in info_list:
if temp_info['name'] == search_name:
print("查询到的信息如下:")
print("name:%s, tel:%s, QQ:%s" % (temp_info['name'],
temp_info['tel'], temp_info['qq']))
break
else:
print("没有您要找的信息....")
def print_all_info():
"""遍历名片信息"""
print("序号\t姓名\t\t手机号\t\tQQ")
i = 0
for temp in info_list:
# temp是一个字典
print("%d\t%s\t\t%s\t\t%s" % (i, temp['name'], temp['tel'], temp['qq']))
i += 1
def main():
"""用来控制整个流程"""
while True:
# 1. 打印功能
print_menu()
# 2. 获取用户的选择
num = input("请输入要进行的操作(数字)")
# 3. 根据用户选择,做相应的事情
if num == "1":
# 添加名片
add_new_info()
elif num == "2":
# 删除名片
del_info()
elif num == "3":
# 修改名片
modify_info()
elif num == "4":
# 查询名片
search_info()
elif num == "5":
# 遍历所有的信息
print_all_info()
elif num == "6":
# 退出系统
exit_flag = input("亲,你确定要退出么?~~~~(>_<)~~~~(yes or no) ")
if exit_flag == "yes":
break
else:
print("输入有误,请重新输入......")
# 程序的开始
main()