python中装饰器和修饰符

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语法糖

@增强函数就是语法糖

image-20231019212714216

上述主要功能函数没有参数和返回值,如果主要函数存在参数和返回值呢?

# 主要函数有返回值
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'

对于含有参数的函数,使用闭包增强后,不能成功接收参数

原因:对于增强函数里面,返回的是没有参数和返回值的函数名称

image-20231019212824957

# 解决方法,修改闭包函数
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描述符

image-20231019212926453

# 创建一个描述符
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
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值