文章目录
- 第四章 Python函数使用
- 4.1. 函数介绍
- 4.2. 函数的定义与使用
- 4.2.1. 函数的定义
- 4.2.2. 调用
- 4.3. 函数的参数
- 4.4. 函数的返回值
- 4.4.1. 返回值介绍
- 4.4.2. None类型
- 4.5. 函数说明
- 4.5.1. 函数注释
- 4.5.2. 函数的4中定义方式
- 4.5.3. 函数的调用
- 4.6. 函数的嵌套调用
- 4.7. 函数的递归
- 4.8. 高级: 函数多返回值
- 4.9. 高级: 函数参数种类
- 4.9.1. 位置参数
- 4.9.2. 关键字参数
- 4.9.3. 缺省参数
- 4.9.4. 不定长参数
- 4.9.4.1. 位置传递
- 4.9.4.2. 关键字传递
- 4.10. 匿名函数
- 4.10.1. 函数作为参数传递
- 4.10.2. lambda匿名函数
- 4.11. 闭包
- 4.12. 装饰器
- 4.13. 函数综合案例
- 万年历
第四章 Python函数使用
4.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(" 不见满街漂亮妹,哪个归得程序员?")
执行之后,效果如下:
是不是还挺酷炫的!
但是问题来了,如果我想在一个程序的不同地方都出现这尊大佛,那么我应该怎么做?需要把那一堆的print再写一遍吗?
经过之前的学习可以知道代码的编写是遵守顺序、分支、循环这三个结构,特别是循环结构是可以让代码重复性的多次执行,但这个循环结构是否能解决上面代码的问题呢?回想一下,循环结构是对有特定循环次数单一文件中重复执行的代码。这里编写代码如果在多个文件中都要使用那么循环结构就无法满足我们的需求了,所以就是使用python中提供一个概念函数来完成这个操作
如果在开发程序时,需要某块代码多次,但是为了提高编写的效率以及代码的重用,所以把具有独立功能的代码块组织为一个小模块,这就是函数即函数是组织好的,可重复使用的,用来实现特定功能的代码段,就像使用过的print()
是Python的内资函数【提前写好,可以重复使用,实现将内容输出到控制台的特定功能的代码段】
4.2. 函数的定义与使用
4.2.1. 函数的定义
def 函数名(自变量变量名, 自变量变量名1, 自变量变量名2...):
函数功能体
return 函数功能运行结果
-
def 定义函数的关键字
-
函数名,类似于变量名,是我们自定义的名字,符合标识符命名规范,英文字母小写,单词和单词之间使用下划线隔开
get_max_value
-
() 固定的,来存放自变量的变量名,自变量进行分析的时候看功能中哪些数据是动态变化,就把这个数据提取出来,定义成一个变量名放在
()
中,在编程语言中称为形式参数
,简称形参
使用功能的时候, 给自变量赋值【给形参赋值】,给形参赋予的值称为
实际参数
, 简称实参
-
return 是应用在函数中的关键字,作用是
结束函数,并把结果返回到调用的位置
有些函数功能是没有设置返回值的,只是把
return
给省略了,当return
被省略的时候,函数功能的返回值默认为None
, 省略的时候等价于return None
特别说明:参数如不需要,可以省略、返回值如不需要,可以省略、函数必须先定义后使用
def printInfo():
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(" 不见满街漂亮妹,哪个归得程序员?")
4.2.2. 调用
定义了函数之后,就相当于有了一个具有某些功能的代码,想要让这些代码能够执行,需要调用它,调用函数很简单的,通过 函数名()即可完成调用
printInfo()
需要注意:
- 每次调用函数时,函数都会从头开始执行,当这个函数中的代码执行完毕后,意味着调用结束了
- 当然了如果函数中执行到了return也会结束函数
4.3. 函数的参数
函数参数的作用:在函数进行计算的时候,接受外部(调用时)提供的数据
如提供如下代码:
# 完成2个数字相加的功能 def add(): res = 1+2 print(f"1+2的结果是:{res}") add()
观察上面的函数可以发现功能非常局限,只能计算1 + 2。有没有可能实现:每一次使用函数,去计算用户指定的2个数字,而非每次都是1 + 2呢?可以的,使用函数的传入参数功能,即可实现
# 完成2个数字相加的功能 # 函数定义中,提供的x和y,称之为:形式参数(形参),表示函数声明将要使用2个参数【参数之间使用逗号进行分隔】 def add(x,y): res = x+y print(f"{x}+{y}的结果是:{res}") # 函数调用中,提供的5和6,称之为:实际参数(实参),表示函数执行时真正使用的参数值【传入的时候,按照顺序传入数据,使用逗号分隔】 add(3,4)
4.4. 函数的返回值
4.4.1. 返回值介绍
现实生活中的场景
开发中的场景
# 提供一个函数求两个数的和
def add(x,y):
# 定义两个数相加的函数功能,完成功能后,会将相加的结果返回给函数调用者
return x+y
# 调用add函数之后计算结果得到返回之后并赋值给sum变量
sum = add(1,2)
print(f"两个数的求和结果时:{sum}")
综上所述:所谓“返回值”,就是程序中函数完成一件事情后,最后给调用者的结果
PS:使用关键字:return 来返回结果,函数体在遇到return后就结束了,所以写在return后的代码不会执行。
4.4.2. None类型
思考:如果函数没有使用return语句返回数据,那么函数有返回值吗?
def printInfo():
print("~~~~~~人生苦短,我用python~~~~~~")
实际上是:有的
Python中有一个特殊的字面量:None,其类型是:<class ‘NoneType’>无返回值的函数,实际上就是返回了:None这个字面量
None表示:空的、无实际意义的意思函数返回的None,就表示,这个函数没有返回什么有意义的内容。也就是返回了空的意思。
def printInfo():
print("~~~~~~人生苦短,我用python~~~~~~")
# 如果函数中不是用return关键字作为结束或返回数据 默认回值是None
# None也可以主动使用return返回,效果等同于不写return语句
# return None
res = printInfo()
print(res) # 结果返回值是None
print(type(res))# 结果返回值是<class 'NoneType'>
None作为一个特殊的字面量,用于表示:空、无意义,其有非常多的应用场景。
# 用在函数无返回值上
def check_password(password):
if password == 123456:
return "success"
return None
# 用于if判断上 在if判断中,None等同于False 一般用于在函数中主动返回Nnoe,并配合if判断做相关处理
res = check_password(88888888)
if not res:
print("您输入的密码不正确")
# 用于声明无内容的变量上 定义变量,但暂时不需要变量有具体子,可以使用None来代替
name = None
4.5. 函数说明
4.5.1. 函数注释
函数是纯代码语言,想要理解其含义,就需要一行行的去阅读理解代码,效率比较低。
我们可以给函数添加说明文档,辅助理解函数的作用。
def add(x,y):
# 通过多行注释的形式,对函数进行说明解释
"""
计算两个的数的和
:param x: 形参x【可以提供具体说明】
:param y: 形参y【可以提供具体说明】
:return: 返回值【可以提供具体说明】
"""
res = x+y
return res
print(add(1,2))
4.5.2. 函数的4中定义方式
函数根据有没有参数,有没有返回值,可以相互组合,一共有4种
- 无参数,无返回值
- 无参数,有返回值
- 有参数,无返回值
- 有参数,有返回值
无参数,无返回值的函数
此类函数,不能接收参数,也没有返回值,一般情况下,打印提示灯类似的功能,使用这类的函数
def printMenu():
print('--------------------------')
print(' xx涮涮锅 点菜系统')
print('')
print(' 1. 羊肉涮涮锅')
print(' 2. 牛肉涮涮锅')
print(' 3. 猪肉涮涮锅')
print('--------------------------')
无参数,有返回值的函数
此类函数,不能接收参数,但是可以返回某个数据,一般情况下,像采集数据,用此类函数
# 获取温度
def getTemperature():
# 这里是获取温度的一些处理过程
# 为了简单起见,先模拟返回一个数据
return 24
temperature = getTemperature()
print('当前的温度为:%d'%temperature)
有参数,无返回值的函数
此类函数,能接收参数,但不可以返回数据,一般情况下,对某些变量设置数据而不需结果时或打印图形,用此类函数
# 打印最终用户名和密码
def printInfo(username,password)
print(f"用户名:{username},密码:{password}")
printInfo("wdf888",123456)
有参数,有返回值的函数
此类函数,不仅能接收参数,还可以返回某个数据,一般情况下,像数据处理并需要结果的应用,用此类函数
# 计算1~num的累积和
def calculateNum(num):
result = 0
i = 1
while i<=num:
result = result + i
i+=1
return result
result = calculateNum(100)
print('1~100的累积和为:%d'%result)
函数根据有没有参数,有没有返回值可以相互组合
定义函数时,是根据实际的功能需求来设计的,所以不同开发人员编写的函数类型各不相同
4.5.3. 函数的调用
调用的方式为:
函数名([实参列表])
调用时,到底写不写 实参
- 如果调用的函数 在定义时有形参,那么在调用的时候就应该传递参数
调用时,实参的个数和先后顺序应该和定义函数中要求的一致
如果调用的函数有返回值,那么就可以用一个变量来进行保存这个值
4.6. 函数的嵌套调用
所谓函数嵌套调用指的是一个函数里面又调用了另外一个函数
def testB():
print('---- testB start----')
print('这里是testB函数执行的代码...(省略)...')
print('---- testB end----')
def testA():
print('---- testA start----')
testB()
print('---- testA end----')
testA()
如果函数A中,调用了另外一个函数B,那么先把函数B中的任务都执行完毕之后才会回到上次 函数A执行的位置
4.7. 函数的递归
递归调用是一种特殊的调用形式,是函数自己调用自己。一个函数体内调用它自身,被称为函数递归。函数递归包含了一个隐式的循环,
它会重复执行某段代码,但这种重复执行无需循环控制
例如:从前有座山,山里有座庙,庙里有一个老和尚和小和尚,老和尚在给小和讲故事…
# 使用递归算法,求出任意数的和
def toSum(num):
if num == 1:
return 1
else:
return num+toSum(num-1)
sum = toSum(10)
print(sum)
"""
有六个人,第六个人说他比第五个人大3岁,第五个人说他比第四个人大3岁,第四个人说他比第三个人大3岁,
第三个人说他比第二个人大3岁,第二个人说他比第一个人大3岁,第一个人说自己13岁,求第六个人多大
"""
def sumAge(n):
if n == 1:
return 13
else:
return sumAge(n-1)+3
sum2 = sumAge(6)
print(sum2)
递归进阶操作:斐波那契数列
斐波那契数列(Fibonacci)最早由印度数学家戈帕拉(Gopala)提出,而第一个真正研究斐波那契数列的是意大利数学家 Leonardo Fibonacci,斐波那契数列的定义很简单,数列的前两位数字是1,之后的数由前两个数相加而得出,例如斐波那契数列的前10个数是:1, 1, 2, 3, 5, 8, 13, 21, 34, 55
def getFibonacci(index):
if index == 1 or index == 2:
return 1
return getFibonacci(index - 1) + getFibonacci(index - 2)
for i in range(1, 11):
print(getFibonacci(i), end=", ")
4.8. 高级: 函数多返回值
思考:如果一个函数中如果同时提供两个return会发生什么效果
def test():
return 1
return 2
num = test()
print(num)
执行代码可以发现只执行了第一个return,原因是因为return可以退出当前函数,导致return下方的代码不执行
编程中遇到需要在函数中同时返回多个值该如何操作呢?
def test():
# 在返回多个数据时 可以使用【return 数据1,数据2,...】的方式进行多个数据返回,同时还支持不同类型数据
return 1,"2"
# 在接收函数返回多个数据时,提供的存储数据变量需要按照返回值顺序,写对应顺序的多个变量接收即可,变量与变量之间使用【逗号】分隔
x,y = test()
print(x)
print(y)
总结:
- 一个函数中可以有多个return语句,但是只要有一个return语句被执行到,那么这个函数就会结束了,因此后面的return没有什么用处
- return后面可以是元组,列表、字典等,只要是能够存储多个数据的类型,就可以一次性返回多个数据
- 如果return后面有多个数据,那么默认是元组【其实可以使用一个变量接收return返回多个数据值,但是一个变量接收的结果默认是元组】即
y = test() print(y) -------------输出结果------------- (1, '2')
4.9. 高级: 函数参数种类
函数参数使用方式上的不同可以得到不同参数参数种类,函数有4种常见参数使用方式位置参数、关键字参数、缺省参数、不定长参数
4.9.1. 位置参数
位置参数:调用函数时根据函数定义的参数位置来传递参数即定义函数时设置的形参,没有特殊的标记的称为位置参数(也就是在定义函数时提供的参数)
注意:
- 传递的参数和定义的参数的顺序及个数必须一致
def show1(x):
print(x)
show1("位置参数")
4.9.2. 关键字参数
关键字参数:函数调用时通过“键=值”形式传递参数。即使赋值的顺序与定义顺序不一致,也会根据变量名定位到对应的形参,给形参赋值即可以让函数更加清晰、容易使用,同时也清除了参数的顺序需求
注意:
- 函数调用时,如果有位置参数时,位置参数必须在关键字参数的前面,但关键字参数之间不存在先后顺序
def show3(x,y,z):
print(f"{x}、{y}、{z}")
# 关键字参数指的是在对位置参数赋值的时候进行操作
# 正常赋值
show3(1,2,3)
#关键字参数赋值(不用满足按照顺序赋值的方式,但是参数名一定要提供并使用,`变量名=值`)
show3(z=1,x=4,y=20)
#混合赋值(关键字参数必须在位置参数传值之后)
show3(10,z=30,y=40)
4.9.3. 缺省参数
缺省参数:缺省参数也叫默认参数,用于定义函数,为参数提供默认值,调用函数时可不传该默认参数的值(注意:所有位置参数必须出现在默认参数前,包括函数定义和调用)
注意:
- 函数调用时,如果为缺省参数传值则修改默认参数值, 否则使用这个默认值
#比如:系统中的print函数 --》 def print(self, *args, sep=' ', end='\n', file=None) --》 end就是默认参数
def show2(x,y=10):
res = x+y
print(f"{x}+{y}的和是:{res}")
# 默认参数不赋值会使用定义时的默认值
show2(1)
# 也可以对默认参数进行赋值操作
show2(1,2)
4.9.4. 不定长参数
不定长参数:不定长参数也叫可变参数. 用于不确定调用的时候会传递多少个参数(不传参也可以)的场景。当调用函数时不确定参数个数时, 可以使用不定长参数。
不定长参数的类型:位置传递、关键字传递
4.9.4.1. 位置传递
传进的所有参数除了1之外都会被y变量收集,它会根据传进参数的位置合并为一个元组(tuple),y是元组类型,这就是位置传递
# 【*变量名】的方式就是可变参数
def show7(x,*y):
# y是可变参数(元组)可以在使用for循环来完成
print(f"{x},{y}")
# 这里除了1之外 都是赋值给可变参数y的值
show7(1,2,3,4,5,6,7)
4.9.4.2. 关键字传递
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kwargs检查。
注意:
- 参数是“键=值”形式的形式的情况下, 所有的“键=值”都会被kwargs接受, 同时会根据“键=值”组成字典
# 希望检查是否有city和job的参数
def show4(name,age,**kwargs):
if "city" in kwargs: #检查kwargs是否是需要的关键字参数
print(f"{name}、{age}、{kwargs}")
return
if "job" in kwargs:
print(f"{name}、{age}、{kwargs}")
return
print(f"{name}、{age}、{ktwargs}")
show4("zhangsan","19",city="北京")
show4("zhangsan","19",job="数仓工程师")
#可以传入不受限制的关键字参数
show4("zhangsan","19",city="北京", addr='昌平', phoneNumber=123456)
命名关键字参数:如果要限制关键字参数的名字,就可以用命名关键字参数,和关键字参数**kwargs不同
命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数
# 只接收city和job作为关键字参数
def show5(name, age, *, city, job):
print(name,age,city,job)
show5("李四","20",city="北京",job="BI工程师")
# PS:命名参数在定义时可以通过设置默认值从而简化调用
def show6(name, age, *, city="杭州", job):
print(name,age,city,job)
#因为city有默认是所以调用的时候可以不传
show6("李四","20",job="数据清洗工程师")
需要注意:如果存在关键字参数必须在可变参数的后面
def show8(x,*y,**z):
# y是可变参数可以在使用for循环来完成
print(f"{x},{y},{z}")
show8(1,2,3,4,5,6,j=1,i=2)
需要注意:命名关键字参数不能和可变参数共存
def show9(x,*y,*,z,a):
4.10. 匿名函数
4.10.1. 函数作为参数传递
之前学习函数中提供的形参的定义,可以接收数据传入到函数中进行使用,其实学习函数的本身,也可以作为参数传入另一个函数内
def test_func(add):
res = add(1,2)
print(f"计算结果为:{res}")
def add(x,y):
return x+y
test_func(add) # 执行计算结果为:3
"""
函数add,作为参数,传入了test_func函数中使用。
test_func需要一个函数作为参数传入,这个函数需要接收2个数字进行计算,计算逻辑由这个被传入函数决定
add函数接收2个数字对其进行计算,add函数作为参数,传递给了test_func函数使用
最终,在test_func函数内部,由传入的add函数,完成了对数字的计算操作
所以,这是一种,【计算逻辑的传递,而非数据的传递】。就像上述代码那样,【不仅仅是相加,相见、相除、等任何逻辑都可以自行定义并作为函数传入】。
"""
说明:
- 函数本身是可以作为参数,传入另一个函数中进行使用的。
- 将函数传入的作用在于:传入计算逻辑,而非传入数据。
4.10.2. lambda匿名函数
lambda匿名函数:又称为匿名函数,一般就是应用在作为一个参数传递到另外一个函数中也可以理解成对功能简单的函数的简化
函数的定义中:
- def关键字,可以定义带有名称的函数,有名称的函数,可以基于名称重复使用。
- lambda关键字,可以定义匿名函数(无名称)无名称的匿名函数,只可临时使用一次。
匿名函数定义语法:
lambda 传入参数:函数体(一行代码)
- lambda 是关键字,表示定义匿名函数
- 传入参数表示匿名函数的形式参数,如:x, y 表示接收2个形式参数
- 函数体,就是函数的执行逻辑,要注意:只能写一行,无法写多行代码
def test_func(add):
res = add(1,2)
print(f"计算结果为:{res}")
# 使用def和使用lambda,定义的函数功能完全一致,只是lambda关键字定义的函数是匿名的,无法二次使用
test_func(lambda x,y:x+y)
说明:
- 匿名函数用于临时构建一个函数,只用一次的场景
- 匿名函数的定义中,函数体只能写一行代码,如果函数体要写多行代码,不可用lambda匿名函数,应使用def定义带名函数
4.11. 闭包
解释:如果在一个函数的内部定义了另一个函数,外部的我们叫他外函数,内部的我们叫他内函数。
闭包:在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。
一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
# 将函数作为返回值返回,也是一种高阶函数
# 这种高阶函数我们也称为叫做闭包,通过闭包可以创建一些只有当前函数能访问的变量
# 可以将一些私有的数据藏到的闭包中
def outer():
a = 10
# 函数内部再定义一个函数
def inner():
print('我是outer', a)
# 将内部函数 inner作为返回值返回
return inner
# r是一个函数对象,是调用fn()后返回的函数对象
# 这个函数实在fn()内部定义,并不是全局函数
# 所以这个函数总是能访问到fn()函数内的变量
# 外函数返回了内函数的引用
fn = outer()
# r()相当于调用了inner()函数
print("outer引用地址:", outer)
print("inner引用地址:", fn)
fn()
"""
输出结果:
outer引用地址: <function outer at 0x0000000002BB5948>
inner引用地址: <function outer.<locals>.inner at 0x0000000002BB58B8>
我是outer 10
"""
说明上述代码:
对于闭包,在外函数
outer
中 最后return inner,我们在调用外函数fn = outer()
的时候,outer
函数返回了inner
函数对象,inner
函数对象是一个函数的引用,这个引用被存入了fn
对象中。所以接下来我们再进行fn()
的时候,相当于运行了inner
函数。注意:
一个函数,如果函数名后紧跟一对括号,相当于调用这个函数。如果不跟括号,相当于只是一个函数的名字,里面存了函数所在位置的引用。
在闭包中外函数把临时变量绑定给内函数
一个函数结束的时候,会把自己的临时变量都释放还给内存,之后变量都不存在了。一般情况下,确实是这样的。但是闭包是一个特别的情况。外部函数发现,自己的临时变量会在将来的内部函数中用到,自己在结束的时候,返回内函数的同时,会把外函数的临时变量送给内函数绑定在一起。所以外函数已经结束了,调用内函数的时候仍然能够使用外函数的临时变量。
def outer(num):
a = num
# 函数内部再定义一个函数
def inner():
print('我是outer', a)
# 将内部函数 inner作为返回值返回
return inner
fn1 = outer(10)
fn2 = outer(20)
print("inner引用地址:", fn1)
fn1()
print("inner引用地址:", fn2)
fn2()
"""
输出结果:
inner引用地址: <function outer.<locals>.inner at 0x00000000026B58B8>
我是outer 10
inner引用地址: <function outer.<locals>.inner at 0x00000000026B5828>
我是outer 20
"""
# 注意两个inner的地址不一样,一个是8B8,一个是828。
上面的代码中,两次调用外部函数
outer
,分别传入的值是10和20。内部函数只定义了一次,可以发现调用的时候,内部函数是能识别外函数的临时变量是不一样的。Python中一切都是对象,虽然函数只定义了一次,但是外函数在运行的时候,实际上是按照里面代码执行的,外函数里创建了一个函数,我们每次调用外函数,它都创建一个内函数,虽然代码一样,但是却创建了不同的对象,并且把每次传入的临时变量数值绑定给内函数,再把内函数引用返回。
所以我们每次调用外函数,都返回不同的实例对象的引用,他们的功能是一样的,但是它们实际上不是同一个函数对象。【其实这也间接的解释了为什么函数可以作为参数传递】
闭包中内函数修改外函数局部变量
Python语法当中,一个函数可以随意读取全局数据,但是要修改全局数据的时候有两种方法:
global
声明全局变量。全局变量是可变类型数据的时候可以修改。- 在闭包内函数也是类似的情况。在内函数中想修改闭包变量(外函数绑定给内函数的局部变量)的时候,在Python中,可以用
nonlocal
关键字声明一个变量, 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。
def outer(num):
a = num
b = 10 # a和b都是闭包变量
print("原始a值为", a)
# inner内函数
def inner():
# 内函数中想修改闭包变量
# nonlocal关键字声明变量
nonlocal a
a += b
print('我是outer的a', a)
# 将内部函数 inner作为返回值返回
return inner
fn1 = outer(10)
fn1()
"""
输出结果:
原始a值为 10
我是outer的a 20
"""
需要注意:还有一点需要注意,闭包变量实际上只有一份,每次调用一份闭包变量。
def outer(num):
a = num
b = 10 # a和b都是闭包变量
print("原始a值为", a)
# inner内函数
def inner():
# 内函数中想修改闭包变量
# nonlocal关键字声明变量
nonlocal a
a += b
print('我是outer的a', a)
# 将内部函数 inner作为返回值返回
return inner
fn1 = outer(10)
fn1()
fn1()
fn1()
"""
输出结果:
原始a值为 10
我是outer的a 20 --》可以看到第二次第二次调用fn1()方法,a的值有增加了10。
我是outer的a 30
我是outer的a 40
"""
4.12. 装饰器
我们现在想要在程序中定义两个函数,分别用类打印九九乘法表和计算N以内的数字累加的和。
# 定义功能: 打印九九乘法表
def print_nine_table():
for line in range(1, 10):
for column in range(1, line + 1):
print(f"{column} * {line} = {column * line}", end="\t")
print()
# 定义功能,完成N以内的数字的计算
def get_sum(n):
sum = 0
for i in range(1, n + 1):
sum += i
print(sum)
现在来了个需求,我需要统计九九乘法表的方法需要执行多长时间!
time_start = time.time()
print_nine_table()
time_end = time.time()
print(f"任务耗时: {time_end - time_start}")
一个函数的执行时间统计可以这样去统计,可是如果是多个函数呢?例如有10个函数,都需要统计计算时间改怎么办?是不是需要重复的去定义time_start、time_end以及去计算差值呢?其实没有必要,我们只需要将这些重复的部分单独提取出来即可。那么中间需要执行的参数怎么办?可以使用函数参数来实现。
def get_function_time(function, *args, **kwargs):
time_start = time.time()
result = function(*args, **kwargs)
time_end = time.time()
print(f"任务耗时: {time_end - time_start}")
return result
get_function_time(print_nine_table)
get_function_time(get_sum, 10)
基本的功能解决了,如果想要通过调用原来函数的名字,既能实现原来的功能,又能统计消耗的时间,又该怎么做呢?例如: print_nine_table() 既能打印九九乘法表,又能打印消耗的时间。此时就需要新建一个变量,用来临时存储之前的功能。
other = print_nine_table
print_nine_table = get_function_time
print_nine_table(other)
other = get_sum
get_sum = get_function_time
get_sum(other, 20)
此时又出现了重复的操作: 定义第三方变量,赋值、调用,因此,能不能把这个过程再简化一下?
def transform(func):
return get_function_time
print_nine_table = transform(print_nine_table)
print_nine_table(get_sum, 10)
此时出现问题,如果我将transform的返回值使用print_nine_table接收,那么就不能求print_nine_table本身的时间了。
如何在函数A中,获取到函数B的数据?嵌套函数!
def transform(func):
def get_function_time(*args, **kwargs):
time_start = time.time()
result = func(*args, **kwargs)
time_end = time.time()
print(f"任务耗时: {time_end - time_start}")
return result
return get_function_time
print_nine_table = transform(print_nine_table)
print_nine_table()
get_sum = transform(get_sum)
get_sum(20)
终于写完了,但是太麻烦了!
# Python提供了@语法糖的替换操作!
# 相当于 test_transform = transform(test_transform)
@transform
def test_transform():
print("test_transform")
"""
装饰器案例:
APP中有点赞、评论等操作,
设计装饰器,在执行点赞、评论操作的时候,先让用户登录,登录成功可以继续操作
"""
def login_check(func):
def inner(*args, **kwargs):
username = input("请输入用户名: ")
password = input("请输入密码: ")
if username == "shawn" and password == "123456":
func(*args, **kwargs)
else:
print("登录失败,再见!")
return inner
@login_check
def comment(content):
print(f"发布评论: {content}")
comment("HAHAHA")
4.13. 函数综合案例
万年历
def print_calendar(days, week):
"""
打印万年历
:param days: 这个月有多少天
:param week: 这个月的第一天是星期几,以0表示星期日
"""
# 打印表头
print("星期日\t\t星期一\t\t星期二\t\t星期三\t\t星期四\t\t星期五\t\t星期六")
# 定义一个变量,计数器
counter = 0
# 打印第一行的空白
for i in range(week):
print(end="\t\t\t")
counter += 1
# 打印数字
for i in range(1, days + 1):
print(i, end="\t\t\t")
counter += 1
if counter % 7 == 0:
print()
def is_leap_year(year):
"""
判断是否是闰年
:param year: 年份
:return: True => 闰年, False => 平年
"""
return year % 4 == 0 and year % 100 != 0 or year % 400 == 0
def get_month_days(year, month):
"""
计算一个月有多少天
:param year: 年份
:param month: 月份
:return: 有多少天
"""
if month in [1, 3, 5, 7, 8, 10, 12]:
return 31
elif month in [4, 6, 9, 11]:
return 30
elif month == 2:
# Python中三目运算符的实现,如果is_leap_year成立,返回29,否则返回28
return 29 if is_leap_year(year) else 28
# if is_leap_year(year):
# return 29
# else:
# return 28
def get_total_days(year, month):
"""
计算这个月的第一天,距离1900年1月1日相差多少天
:param year: 年份
:param month: 月份
:return: 相差的天数
"""
total = 0
# 整年计算,计算1900-01-01到year-01-01相差多少天
for y in range(1900, year):
total += (366 if is_leap_year(year) else 365)
# 计算year-month-01是这一年的第几天
for m in range(1, month):
total += get_month_days(year, m)
return total + 1
year = int(input("请输入一个年份: "))
month = int(input("请输入一个月份: "))
# 计算这个月的第一天是星期几
week = get_total_days(year, month) % 7
# 计算这个月有多少天
days = get_month_days(year, month)
# 打印万年历
print_calendar(days, week)