Python—装饰器

本文深入探讨Python装饰器,介绍装饰器的意义、实现原理和使用方式。通过实例解析装饰器的四种形式、万能装饰器以及如何处理多个装饰器的情况。同时,文章还讲解了静态方法和类方法的概念、使用场景及与装饰器的关系。

装饰器

首先看一个demo:

@func1
def func():
    print('aaa')

装饰器存在的意义

  • 不影响原有函数的功能
  • 可以添加新功能

一般常见的,比如拿到第三方的API接口,第三方不允许修改这个接口。这个时候,装饰器就派上用场。

装饰器本身也是一个函数,作用是为现有存在的函数,在不改变函数的基础上,增加一些功能进行装饰

它是以闭包的形式去实现的。

在使用装饰器函数时,在被装饰的函数的前一行,使用@装饰器函数名形式来进行装饰。

Demo:

现在在一个项目中,有很多函数,由于我们的项目越来越多,功能也越来越多,导致程序越来越慢。其中一个功能函数的功能,是实现一百万次的累加。

def my_count():
    s=0
    for i in range(1000001):
        s+=i
    print('sum:',s)

假如我们要计算函数的运行时间

import time
def my_count():
    s=0
    for i in range(1000001):
        s+=i
    print('sum:',s)
start=time.time()
my_count()
end=time.time()
print('运行时间为:',end-start)

sum: 500000500000
运行时间为: 0.06537032127380371

成功的实现时间的运算,但是假如有成千上万个函数,每个函数这么写一遍,非常麻烦,代码量也凭空增加很多

明显不符合开发原则,代码太过冗杂

考虑将上述代码封装成一块在进行操作

import time
def my_count():
    s=0
    for i in range(1000001):
        s+=i
    print('sum:',s)

def count_time(func):
    start=time.time()
    func()
    end=time.time()
    print('时间:',end-start)
count_time(my_count)

sum: 500000500000
运行时间为: 0.06537032127380371

经过修改后,定义一个函数来实现时间计算功能

初看比之前好很多,只是在使用时需要将我们对应的函数传入到时间计算函数中

但仍然影响了原来的使用

接下来思考,能不能在使用时不影响函数的原来的使用方式,同时进行时间计算

考虑闭包

import time
def count_time(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print('时间:', end - start)
    return wrapper
def my_count():
    s=0
    for i in range(1000001):
        s+=i
    print('sum:',s)
my_count=count_time(my_count)
my_count()

sum: 500000500000
时间: 0.10979533195495605

在使用时,让my_count函数重新指向count_time函数返回的函数引用,这样使用my_count时,就和原来的使用方式一样了

实际就是采用装饰器

import time
def count_time(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print('时间:', end - start)
    return wrapper
@count_time
def my_count():
    s=0
    for i in range(1000001):
        s+=i
    print('sum:',s)
my_count()

sum: 500000500000
时间: 0.12776541709899902

这样实现的好处,定义闭包函数后,只需要通过@装饰器函数名形式的装饰器语法,就可以将@装饰器函数名加到要装饰的函数前即可

这种不改变原有函数功,对函数进行拓展的形式,就称为装饰器

在执行@装饰器函数名时,就是将原函数传递到闭包中,然后原函数的引用指向闭包返回的装饰过的内部函数的引用

装饰器的几种形式

  • 无参无返回值
def setFunc(func):
    def wrapper():
        print('Start')
        func()
        print('end')
    return wrapper
@setFunc  # setFunc(show)
def show():
    print('show')
show()

Start
show
end
  • 无参有返回值
def setFunc(func):
    def wrapper():
        print('Start')
        return func()
        print('end')
    return wrapper
@setFunc  # setFunc(show)
def show():
    return 'show'

print(show())

Start
show
  • 有参无返回值
def setFunc(func):
    def wrapper(s):
        print('Start')
        func(s)
        print('end')
    return wrapper
@setFunc  # setFunc(show)
def show(s):
    print('Hello %s'%s)

show('City')

Start
Hello City
end
  • 有参有返回值
def setFunc(func):
    def wrapper(x,y):
        print('Start')
        return func(x,y)
        print('end')
    return wrapper
@setFunc  # setFunc(show)
# def show(s):
#     print('Hello %s'%s)

def myAdd(x,y):
    return x+y
print(myAdd(2,3))

Start
5

万能装饰器

根据被装饰函数的定义不同,分出了四种形式

能不能实现一种,适用于任何形式函数定义的装饰器

通过可变参数(*args/**kwargs)来接收不同的参数类型

def setFunc(func):
    def wrapper(*args,**kwargs):
        print('Wrapper context.')
        return func(*args,**kwargs)
    return wrapper
@setFunc
def func(name,age):
    print(name,age)
@setFunc
def fun(a,b,*c,**d):
    print(a+b)
    print(c)
    print(d)
func('Tom',18)
fun(1,2,1999,1,1,school='zucc')

Wrapper context.
Tom 18
Wrapper context.
3
(1999, 1, 1)
{'school': 'zucc'}

函数被多个装饰器所装饰

一个函数在使用时,通过一个装饰器来拓展可能并不能达到预期

一个函数被多个装饰器所装饰

# 装饰器1
def setFunc1(func):
    def wrapper1(*args,**kwargs):
        print('Wrapper Contxt 1 Start.'.center(40,'-'))
        func(*args,kwargs)
        print('Wrapper Contxt 1 end'.center(40,'-'))
    return wrapper1
# 装饰器2
def setFunc2(func):
    def wrapper2(*args,**kwargs):
        print('Wrapper Contxt 2 Start.'.center(40,'-'))
        func(*args,kwargs)
        print('Wrapper Contxt 2 end'.center(40,'-'))
    return wrapper2

@setFunc1   #--->F
@setFunc2   #--->g
def show(*args,**kwargs): #--->f
    print('show run')
show()   #F(g(f))
# 从下往上去装饰
# 从内往外去装饰

--------Wrapper Contxt 1 Start.---------
--------Wrapper Contxt 2 Start.---------
show run
----------Wrapper Contxt 2 end----------
----------Wrapper Contxt 1 end----------

总结:

  • 函数可以向普通变量一样,作为函数的参数或者返回值进行传递
  • 函数的内部可以定义另外一个函数。目的,隐藏函数功能的实现
  • 闭包实际上也是函数定义的一种形式
  • 闭包定义的规则,在外部函数内定义一个内部函数,内部函数使用外部函数的变量,并返回内部函数的引用
  • Python中,装饰器就是用闭包来实现的
  • 装饰器的作用,不改变现有函数的基础上,为函数增加功能
  • 装饰器的使用,通过@装饰器函数名的形式来给已有函数进行装饰,添加功能
  • 装饰器四种形式,根据参数的不同以及返回值的不同
  • 万能装饰器,通过可变参数(*args/**kwargs)来实现
  • 一个装饰器可以为多个函数提供装饰功能
  • 一个函数也可以被多个装饰器所装饰(了解)
  • 通过类实现装饰器,重写__init____call__函数
  • 类装饰器在装饰函数后,原来的引用不再是函数,而是装饰类的对象

静态方法和类方法

静态方法
  • 通过装饰器@staticmethod来进行装饰,静态方法既不需要传递类对象也不需要传递实例对象
  • 静态方法也可以通过实例对象类对象去访问
class Dog:
    type='狗'
    def __init__(self):
        name=None
    # 静态方法
    @staticmethod
    def introduce(): #静态方法不会自动传递实例对象和类对象
        print('犬科')
dog=Dog()
Dog.introduce()
dog.introduce()

犬科
犬科

静态方法是类中的函数,不需要实例

静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但和类本身没有关系,也就是说,在静态方法中,不会涉及类中属性和方法的操作。

—>可以理解,静态方法是一个独立的单纯的函数,仅仅是托管与某个类的名称空间中,便于维护和管理

使用场景
  • 当方法中 既不需要使用实例对象(如实例对象,实例属性),也不需要使用类对象 (如类属性、类方法、创建实例等)时,定义静态方法
  • 取消不需要的参数传递,有利于 减少不必要的内存占用和性能消耗
  • 如果在类外面写一个同样的函数来做这些事,打乱了逻辑关系,导致代码维护困难,使用静态方法。
类方法
  • 类对象所拥有的方法
  • 需要用装饰器@classmethod来标识其为类方法
  • 对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数
class Dog:
    __type='狗'
    # 类方法,用class来进行装饰
    @classmethod
    def get_type(cls):
        return cls.__type
print(Dog.get_type())
使用场景
  • 当方法中需要使用类对象(如访问私有类属性等)时,定义类方法
  • 类方法一般和类属性配合使用

注意:

类中定义了同名的对象方法,类方法以及静态方法时,调用方法会优先执行最后定义的方法

class Dog:
    def demo_method(self):
        print('对象方法')
    @classmethod
    def demo_method(cls):
        print('类方法')
    @staticmethod
    def demo_method():   # 最后被定义,调用时优先执行
        print('静态方法')
dog=Dog()
Dog.demo_method()
dog.demo_method()

静态方法
静态方法
__call__方法

引子

def func():
    print('hello')
func()
print(func())
# 调用--->call--->报错:不能被call

hello
hello
None

对应的,类实例对象的调用,需要使用到__call__特殊方法

class student:
    def __init__(self,name):
        self.name=name
    def __call__(self, classmate):
        print('我的名字是%s,我的同桌是%s'%(self.name,classmate))
stu=student('aaa')
stu('bbb')

我的名字是aaa,我的同桌是bbb

用类实现装饰器

通过__init____call__方法实现
class Test:
    def __init__(self,func):
        print('装饰器准备装饰')
        self.__func=func
    def __call__(self, *args, **kwargs):
        print('Wrapper Context.')
        print('before')
        self.__func(*args,**kwargs)
        print('after')
@Test      # 把被装饰的定义当做输入 先进入init再做之后的操作
def show():
    print('hello')
    
print('flag')
show()

装饰器准备装饰
flag
Wrapper Context.
before
hello
after
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值