1.函数闭包
import time
def print_odds():
'''
输出0到100之间所有的奇数,并统计函数执行的时间
'''
start_time = time.perf_counter() # 起始时间
for i in range(100):
if i%2 == 1:
print(i)
end_time = time.perf_counter()
print('程序的运行时间为{}'.format(end_time - start_time))
可以发现上述代码有两个功能,但他们耦合在一起了 很乱
缺点:不方便修改,容易引起bug
能不能将两个功能分开,将辅助功能从主要功能里面抽离开来?
解决方法:使用修饰器和函数闭包
# 解决方法1: 将函数分开
def count_time(func):
start_time = time.perf_counter() # 起始时间
func()
end_time = time.perf_counter()
print('程序的运行时间为{}'.format(end_time - start_time))
def print_odds():
for i in range(100):
if i%2 == 1:
print(i)
# 函数调用
# count_time(print_odds())
# 缺点:需要通过辅助函数来调用主要功能函数,不方便
上述通过辅助函数调用主要函数,在代码可读性上很差
能不能调用主要函数的时候自动完成对时间的统计?
1.1函数闭包定义
函数闭包: 一个函数;其参数和返回值都是函数
闭包函数的返回值是对传入函数增强的结果
- 用于增强函数功能
- 面向切面编程(AOP)
def print_odds():
for i in range(100):
if i%2 == 1:
print(i)
def count_time_wrapper(func):
'''
闭包,用于函数增强,给func增加统计时间的功能
'''
def imporved_func():
start_time = time.perf_counter() # 起始时间
func()
end_time = time.perf_counter()
print('程序的运行时间为{}'.format(end_time - start_time))
return imporved_func
# 调用函数增强函数,返回增强函数
print_odds = count_time_wrapper(print_odds)
print_odds() # 调用增强函数
1
.
.
.
99
程序的运行时间为0.00022759998682886362
闭包缺点:上述闭包每次调用函数,都需要进行显示增强
引入装饰器!!
2.装饰器
2.1简化闭包–引入装饰器
def count_time_wraper(func):
'''
闭包,用于函数增强,给func增加统计时间的功能
'''
def imporved_func():
start_time = time.perf_counter() # 起始时间
func()
end_time = time.perf_counter()
print('程序的运行时间为{}'.format(end_time - start_time))
return imporved_func
# 在主要函数中,整个装饰器,增强下面的函数
@count_time_wraper
def print_odd():
for i in range(100):
if i%2 == 1:
print(i)
print_odd() # 调用增强函数
1
.
.
.
99
程序的运行时间为0.00018939998699352145
装饰器在第一次调用被装饰函数时进行增强
- 什么时候增强?::在第一次调用的时候
- 增强几次?::只增强一次
2.2语法糖
@增强函数就是语法糖
上述主要功能函数没有参数和返回值,如果主要函数存在参数和返回值呢?
# 主要函数有返回值
def count_time_wraper(func):
'''
闭包,用于函数增强,给func增加统计时间的功能
'''
def imporved_func():
start_time = time.perf_counter() # 起始时间
func()
end_time = time.perf_counter()
print('程序的运行时间为{}'.format(end_time - start_time))
return imporved_func
def print_odd_8():
num = 0
for i in range(100):
if i%2 == 1:
num += 1
return num
print('增强前')
print_odd_8()
增强前
50
print('增强后')
print_odd_8 = count_time_wraper(print_odd_8)
print_odd_8()
增强后
程序的运行时间为1.0199961252510548e-05
对于有返回值的函数,调用闭包函数增强,不能成功返回,但是成功增强了辅助功能
# 对于有参数的主要函数被增强呢?
def count_time_wraper(func):
'''
闭包,用于函数增强,给func增加统计时间的功能
'''
def imporved_func():
start_time = time.perf_counter() # 起始时间
func()
end_time = time.perf_counter()
print('程序的运行时间为{}'.format(end_time - start_time))
return imporved_func
def print_odd_limit(limit = 100):
num = 0
for i in range(100):
if i%2 == 1:
num += 1
return num
print('增强前')
print_odd_limit(limit = 100)
增强前
50
print('增强后')
print_odd_limit = count_time_wraper(print_odd_limit)
print_odd_limit(limit = 100)
增强后
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[7], line 3
1 print('增强后')
2 print_odd_limit = count_time_wraper(print_odd_limit)
----> 3 print_odd_limit(limit = 100)
TypeError: count_time_wraper.<locals>.imporved_func() got an unexpected keyword argument 'limit'
对于含有参数的函数,使用闭包增强后,不能成功接收参数
原因:对于增强函数里面,返回的是没有参数和返回值的函数名称
# 解决方法,修改闭包函数
def count_time_wraper(func):
'''
闭包,用于函数增强,给func增加统计时间的功能
'''
def imporved_func(*args,**kwargs):
# 我们不知道原函数会传什么参数,使用*args和**kwargs
start_time = time.perf_counter() # 起始时间
reslut = func(*args,**kwargs) # 直接传入func中
end_time = time.perf_counter()
print('程序的运行时间为{}'.format(end_time - start_time))
return reslut
return imporved_func
def print_odd_last(limit = 100):
num = 0
for i in range(100):
if i%2 == 1:
num += 1
return num
print('增强前')
print_odd_last(limit = 100)
增强前
50
print('增强后')
print_odd_last = count_time_wraper(print_odd_last)
print_odd_last(limit = 100)
增强后
程序的运行时间为1.179997343569994e-05
50
2.3补充:*args 和 **kwargs
在定义函数的时候,参数*args在前,**kargs在后,组合起来可以传入任何参数。
- *args参数:可接受任意个位置参数,当函数调用时,所有未使用(未匹配)的位置参数会在函数内自动组装进一个tuple对象中,此tuple对象会赋值给变量名args。
- **kwargs参数:可接受任意个关键字参数,当函数调用时,所有未使用(未匹配)的关键字参数会在函数内组装进一个dict对象中,此dict对象会赋值给变量名kwargs。
# *args传入任意位置参数,并组成元组赋值给args
def add(*args):
print(args)
print(type(args))
result = 0
for i in args:
result += i
print(result)
add(1,2,3,4,5)
(1, 2, 3, 4, 5)
<class 'tuple'>
15
# **kargs传入任意的关键词参数,会将变量名名和赋值装进一个dict中,并赋值给kargs
def add(*args,**kargs):
print(kargs)
print(type(kargs))
result = 0
for i in args:
result += i
print(result)
add(a = 1,b = 2,c = 3,d = 4)
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
<class 'dict'>
0
2.4函数闭包的通用模式
# 保留了参数列表和返回值的函数闭包
def general_wrapper(func):
def improved_func(*args,**kargs):
# 附加功能
'''
代码块
'''
return_result = func(*args,**kargs) # 对应函数
# 附加功能
'''
代码块
'''
return return_result
return improved_func # 返回函数名称
# 多个装饰器的情况
def wrapper1(func):
print('装饰wrapper1')
def improve_func():
print('调用wrapper1')
func()
return improve_func
def wrapper2(func):
print('装饰wrapper2')
def improve_func():
print('调用wrapper2')
func()
return improve_func
@wrapper1
@wrapper2
def origin_func():
pass
origin_func()
装饰wrapper2
装饰wrapper1
调用wrapper1
调用wrapper2
上述结果原因:
等价于origin_func = wrapper1(wrapper2(origin_func))
因此会先对wrapper2进行装饰,然后在装饰wrapper1;
对wrapper2装饰后,函数主体变成了:
print('调用wrapper2')
func()
对wrapper1装饰后,函数主体变成了:
print('调用wrapper1')
print('调用wrapper2')
func()
2.5python给提供的内置装饰器
增加格外的功能
- @property
- @变量名.setter
- @classmethod
- @staticmethod
2.5.1@staticmethod
定义在类中的静态函数,可以用类名和对象名调用
不用self,就和普通函数一样,只不过这个函数只能在类中调用
静态方法不绑定到实例或类。它们被包含在一个类中只是因为它们在逻辑上属于那个类。
class time:
def __init__(self,time):
self.time = time
@staticmethod
def get_time():
return '当前时刻'
# 就是类里面的普通方法,只能被类和类的对象调用
time.get_time()
'当前时刻'
2.5.2@classmethod
python中cls代表是类的本身,相对应的self则是类的一个实例对象
classmethod修饰的方法,第一个参数代表类本身
class Dog():
count = 0 # 类属性
def __init__(self,weight):
Dog.count += 1 # 类属性加一
self.weight = weight # 对象属性赋值
@classmethod
def dog_count(cls):
print('这个类调用了{}次'.format(cls.count))
dog1 = Dog(123)
dog2 = Dog(456)
Dog.dog_count()
这个类调用了2次
2.5.3@property
对于一些依附于其他属性计算得到的属性,我们不能直接对他进行修改,而是应该通过定义函数来进行获得
class rectangle:
def __init__(self,length,width):
self.length = length
self.width = width
def get_area(self):
return self.width*self.length
my_rectrangle = rectangle(5,4)
my_rectrangle.get_area()# 每次获取这个面积都要调用这个方法
20
如何优化上述过程?
使用@property将方法变成类的属性
将方法作为属性使用,并且不能被修改
my_rectrangle.area = 20
上面这个写法一定会报错,原因是因为使用了property方法,被定义后,不能被修改。
如果想修改则新增一个方法用于修改,并用@变量名.setter
进行装饰
用@property重构了属性area
class rectangle:
def __init__(self,length,width):
self.length = length
self.width = width
@property
def area(self):
return self.width*self.length
my_rectrangle = rectangle(5,4)
print(my_rectrangle.area) # 作为对象的属性
my_rectrangle.length = 10
print(my_rectrangle.area) # 作为对象的属性,自动会根据长度修改面积
20
40
2.5.4@变量名.setter
如果想对@property修饰过的变量进行修改,则需要重新定义一个修改方法,并用上述修饰器修饰
可以在这里面对修改的值进行检测,不满足条件抛出异常
class student:
def __init__(self,name,score):
self.name = name
self._score = score # 在变量名称前加一个_,没有任何用处,只是写代码的规范而已
# 希望他是一个私有变量,但他不是真实的私有变量
# 真实的私有变量前面有两个__下划线
@property
def score(self):
return self._score
@score.setter
def score(self,score):
self._score = score
student = student('daiyaxun',100)
# property定义的方法名称会当作一个属性,
# 而@方法名.setter会修改property定义的属性
class student:
def __init__(self,name,score):
self.name = name
# self.score是下面的property修饰的方法属性
# 如果对他进行修改,则会调用对应的setter修饰的方法
self.score = score
@property
def score(self):
return self.__score # 私有属性
@score.setter
def score(self,score):
print('正在修改__score私有属性,开始判断是否满足修改条件')
if score < 0:
raise Exception('修改的得分不能小于0')
if score >100:
raise Exception('修改的得分不能超过满分')
self.__score = score
# 修改异常检测
student = student('daiyaxun',-1)
student.score = 1000
正在修改__score私有属性,开始判断是否满足修改条件
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
Cell In[51], line 1
----> 1 student = student('daiyaxun',-1)
2 student.score = 1000
Cell In[50], line 7, in student.__init__(self, name, score)
4 self.name = name
5 # self.score是下面的property修饰的方法属性
6 # 如果对他进行修改,则会调用对应的setter修饰的方法
----> 7 self.score = score
Cell In[50], line 17, in student.score(self, score)
15 print('正在修改__score私有属性,开始判断是否满足修改条件')
16 if score < 0:
---> 17 raise Exception('修改的得分不能小于0')
18 if score >100:
19 raise Exception('修改的得分不能超过满分')
Exception: 修改的得分不能小于0
3.Descriptor描述符
上述如果要对所有的变量进行异常检测,
代码重复的东西很多,很冗余,如何解决?->descriptor描述符
# 创建一个描述符
class RequireString:
def __set_name__(self,owner,name):
# 将变量进行保存
self.__property_name = name # 私有变量
# 对属性赋值
def __set__(self,instance,value):
'''
self: 描述符对象本身
instance:调用描述符对象的对象,例如下面的student
value: 即将给instance赋值的数据
'''
# instance是一个对象,__dict__存储了对象的属性
instance.__dict__[self.__property_name] = value
# 拿到属性值,返回instance.
def __get__(self,instance,owner):
return instance.__dict__[self.__property_name]
class student2:
# 这里假设RequireString是一个修饰符
first_name = RequireString() # 这句话会调用__set_name__,将first_name变量名作为name参数
second_name = RequireString()
# 上述这俩目前来看是个类变量,指向修饰符
student = student2()
# 下句话的作用:不是对类变量进行赋值,而是调用修饰符的__set__方法
# 把'jeck'set进去
student.first_name = 'jeck'
# first_name.__set__(student,'jeck')
student3 = student2()
student3.first_name = 'tomm'
print(student.first_name)
print(student3.first_name)
jeck
tomm