Python 装饰器

2021/7/11 1:05:43

本文主要是介绍Python 装饰器,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Python 装饰器

学习就是要力求知其然,亦知其所以然

什么是装饰器?

python 中的装饰器有什么作用呢?

  • 面向切面编程(AOP)
  • 原有基础上增加额外的功能

上面说的作用,我们能想象到可以用来做,打印日志、事务处理、缓存、权限校验等功能。

装饰器类型

  1. 简单装饰器
  2. 带参数装饰器
  3. 类装饰器
  4. 内置装饰器

简单装饰器

需求:需要对某些函数调用前输出 “xx函数被调用用了”的日志

def foo():
    print('i am foo')

def bar():
    print("i am bar")
    
def gin():
    print("i am gin")

实现

  1. 直接在函数中添加 “xx 函数被调用了”

    def foo():
        print('foo is called')
        print('i am foo')
    
    • 扩展性差,需改信息的时候,每个函数都需要改动
  2. 装饰器

    # 定义一个装饰器
    def logging(func):
        def wrapper(*args, **kwargs):
            print('%s is called' % func.__name__)
            return func(*args, **kwargs)
    
        return wrapper
    
    # logging 装饰器修饰 gin() 函数
    @logging
    def gin():
        print("i am gin")
    

    我们调用一下 gin()

    gin is called
    i am gin
    

输出结果能够实现我们的需求,这个非常好,但是它的原理是什么呢??

原理

先说结果,这个和 @ 语法糖有关,@logging 放置在 gin() 函数上 其实就是等于 gin = logging(gin)

接下我们来验证一下 @ 语法糖是否是这样的

# foo 被包装成 logging.wrapper 类型
foo = logging(foo)
foo()

输出的结果和添加了 @logging 装饰器的一致。

带参数装饰器

需求:现在需求升级了,需要自定义打印不同的等级的日志。

我们先,能否在装饰器上加多个参数,表示等级呢? 答案是肯定的,而且越来越有意思了。

# 带等级的日志装饰器
def advanced_log(level):
    def decorate(func):
        def wrapper(*args, **kwargs):
            if level == 'warn':
                print('%s: %s is called' % (level, func.__name__))
            elif level == 'info':
                print('%s: %s is called' % (level, func.__name__))
            else:
                print('None: %s is called' % func.__name__)
            return func(*args, **kwargs)

        return wrapper

    return decorate


@advanced_log("warn")
def vag():
    print("i am vag")
    
vag()

输出的结果

warn: vag is called
i am vag

符合我们的需求。

原理

结果的是出来的,但是可能对它的原理还是很模糊。其实这里还是 @ 的语法糖,我们转换一下写法就清晰了。带参数装饰器 @advanced_log("warn") 修饰 vag() 函数就相当于 vag = advanced_log('info')(vag)

我们先验证一下这个结论再来分析

def goo():
    print("i am goo")
    
goo = advanced_log('warn')(goo)
goo()
warn: goo is called
i am goo

与装饰器输出的结果相同。

我们结合 advanced_log() 函数 分析一下 goo = advanced_log('warn')(goo)

  • advanced_log('warn') 返回的是 advanced_log() 中的函数 decorate(),所以简化成为goo = decorate(goo)
  • goo = decorate(goo), 这时就转换成为无参的装饰器了,又因为有参数warn,可以确定方法需要执行哪个分支
  • 在调用 goo() 函数时,decorate(goo)被执行,转化成decorate(goo)(),而 decorate(goo) 返回的是 wrapper,所以简化成 wrapper()
  • 继续 wrapper() 被执行输出日志(我们需要额外处理的逻辑)
  • 最后才调用的原始函数 goo()

小结

在这里我们小结一下,不难发现,python 能用装饰器的本质是 函数可以当做参数!!,而装饰器的本质是函数的包装。

小坑

我们仔细看一下第一个装饰器, logging 的函数,不难发现被装饰的函数被另一个函数 wrapper 包装起来了,返回的是 wrapper,然后 @logging 装饰器再将 wrapper 赋值给被装饰的函数。

这样就产生了一个问题,被装饰的函数还是原来的函数吗?

其实答案是否定的,因为都被 wrapper 替换。这样可以不行,如果被修饰的函数 gin 中有文档说明,或者某些判断需要依赖函数的__name__属性,被替换就乱了套了,所以官方出了一个新的装饰器 @wraps,起作用是将被装饰的函数的属性,如函数名__name____doc__ 等信息复制到被包装的函数 wrapper中。

验证不添加 @wraps 的情况

def logg(func):
    #@wraps(func)
    def wrapper(*args, **kwargs):
        print(func.__name__, 'was called')
        return func(*args, **kwargs)

    return wrapper

def yog(x):
    """does some thing"""
    print('i am yog')
wrapper
None

这可不是我们想要的!

验证添加 @wraps 的情况

去掉 #@wraps(func) 注释 =>@wraps(func)

yog
does some thing

输出正常!!

@wraps 也是一个带参数的装饰器这里就不展开了

类装饰器

类装饰器依靠内部的是类内部 __call__() 方法,当使用 @ 语法糖,将类作为装饰器附加到函数上,当函数被调用,就会转换成调用类的 __call__() 方法

假设有类 Koo,存在 Koo() 等于 Koo.__call__()()代表了可调用,如果类名 + "()" 代表调用类中的 __call__()方法

class Koo(object):
    def __init__(self, func):
        self._fun = func

    def __call__(self, *args, **kwargs):
        print('class decorator starting')
        self._fun()
        print('class decorator ending')

@Koo
def sar():
    print('I am sar')
    
sar()
print(sar)
class decorator starting
I am sar
class decorator ending
<__main__.Koo object at 0x0000020B7FD8B310>

原理

类装饰器 @Koo 修饰的函数 sar ,在函数 sar() 被调用时,该函数会被当做该类的初始化参数传入,并且该函数变量会执行该对象,即 sar = Koo(sar)

变量 sar 从指向函数,到指向对象。其实和第一种的简单修饰思路差不多,只不过一个返回的是函数包装,个返回的是对象包装。

内置装饰器

  1. 先了解 __get__ 特性 和 __set__(必须)
  2. 实例方法,第一个参数 self,可以被实例所调用
  3. 类方法是第一个参数为 cls ,可以被实例或者类直接调用
  4. 静态方法,参数无要求,可以被实例和类直接调用

@classmethod

@classmethod 的作用就是让一个方法变成类方法

class Hi(object):
    def __init__(self, value):
        self.value = value

    @classmethod
    def say_hi(cls):
        print("hello world!")

    def say_goodbye(self):
        print(self.value, "goodbye")


Hi.say_hi()

输出结果

hello world!

原理

@classmethod 用到的是 __get__ 和装饰器的知识,现在我们自定义一个类方法装饰器 ClassMethod

# 自定义的 类方法装饰器
class ClassMethod(object):
    def __init__(self, func):
        self.func = func

    # instance 是对象, owner 是类
    def __get__(self, instance, owner):
        def wrapper(*args, **kwargs):
            return self.func(owner, *args, **kwargs)

        return wrapper


class Hi(object):
    def __init__(self, value):
        self.value = value

    @classmethod
    def say_hi(cls):
        print(cls)
        print("hello world!")

    # say_goodbye = ClassMethod(say_goodbye)
    # 这里故意写成 owner 为参数名是想 ClassMethod 中 __get__ 方法中的 owner 对应 方便理解
    @ClassMethod
    def say_goodbye(owner):
        print(owner)
        print("goodbye")


Hi.say_hi()
Hi.say_goodbye()

输出结果

<class '__main__.Hi'>
hello world!
<class '__main__.Hi'>
goodbye

我们自己实现了一个类方法装饰器,原理看得非常透彻,我们归纳一下,其实 @ClassMethod装饰在say_goodbye方法,相当于say_goodbye = ClassMethod(say_goodbye), say_goodbye 指向了ClassMethod类型对象,又根据 ClassMethod 类中定义了 __get__ ,调用Hi.say_goodbye() 相当于调用 __get__里面的包装的方法

@staticmethod

我们用一个例子想感受一下静态方法

class Coco(object):
    @staticmethod
    def static_co():
        print('i am static method')


coco = Coco()
Coco.static_co()
coco.static_co()

输出

i am static method
i am static method

原理

@staticmethod 的原理与 @classmethod 的原理一样,我们实现自己的静态方法装饰器

# 自定义的 静态方法装饰器
class StaticMethod(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        def wrapper(*args, **kwargs):
            # 与类方法的区别是是否需要传入 owner
            return self.func(*args, **kwargs)

        return wrapper


class Coco(object):
    @staticmethod
    def static_co():
        print('i am static method co')
    @StaticMethod
    def static_coco():
        print('i am static method coco')


coco = Coco()
Coco.static_co()
coco.static_co()

Coco.static_coco()
coco.static_coco()

输出结果

i am static method co
i am static method co
i am static method coco
i am static method coco

原理基本与 @classmethod 一样,唯一的区别是在定义包装原有方法的时候是否传入 ower 即类对象。

@property

对象调用方法可以想调用属性一样。

class Person(object):
    def __init__(self, name):
        self._name = name

    @property
    def my_name(self):
        return self._name

    @my_name.setter
    def my_name(self, name):
        self._name = name
        return self._name


p = Person('gin')
print(p.my_name)  # p.name()
p.my_name = 'yyy'  # p.set_name('yyy')
print(p.my_name)
gin
yyy

结果也验证了通过@property装饰的方法,可以像属性一样调用。

原理

通过手写一个自定义的属性装饰器 @Property(主要大写)

class Property(object):
    def __init__(self, fget=None, fset=None):
        self.fget = fget
        self.fset = fset

    def __get__(self, instance, owner):
        if instance is not None:
            return self.fget(instance)
        return self

    def __set__(self, instance, value):
        self.fset(instance, value)

    def setter(self, func):
        self.fset = func  # 更新属性
        return self


class Person(object):
    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def my_name(self):
        return self._name

    @my_name.setter
    def my_name(self, name):
        self._name = name
        return self._name

	# age = Property(age)
    @Property
    def age(self):
        return self._age

    # Property(age).setter
    @age.setter
    def age(self, age):
        self._age = age
        return self._age


p = Person('gin', 18)
print(p.my_name)  # p.name()
print(p.age)
p.my_name = 'yyy'  # p.set_name('yyy')
p.age = 19
print(p.my_name)
print(p.age)
gin
18
yyy
19

结果和原有的属性装饰器功能一致。@Property 的原理总体和 @classmethod@staticmethod 思路是一直的都是利用了 __get____set__等方法。

总结

装饰器语法解析就是被装饰的函数或者方法 test = 装饰器 ( test )

下面例子会解析成 test = C(B(A(test)))

@A
@B
@C
def test():
    pass

参考

https://www.cnblogs.com/rxybk/p/13284852.html

https://blog.csdn.net/weixin_30432179/article/details/99093631

代码

# 装饰器

# 函数装饰器

# 简单装饰器
from functools import wraps


# 在方法调用前输出 xxx 被调用了

def logging(func):
    def wrapper(*args, **kwargs):
        print('%s is called' % func.__name__)
        return func(*args, **kwargs)

    return wrapper


def foo():
    print('foo is called')
    print('i am foo')


@logging
def gin():
    print("i am gin")


def bar():
    print("i am bar")


# foo 被包装成 logging.wrapper 类型
foo = logging(foo)

# 验证数据
bar()

# 重点
# @ 语法糖   @logging <==> gin = logging(gin)
gin()

foo()

print("-----------------------------分割线--------------------")


# 带参数装饰器 高级


def advanced_log(level):
    def decorate(func):
        def wrapper(*args, **kwargs):
            if level == 'warn':
                print('%s: %s is called' % (level, func.__name__))
            elif level == 'info':
                print('%s: %s is called' % (level, func.__name__))
            else:
                print('None: %s is called' % func.__name__)
            return func(*args, **kwargs)

        return wrapper

    return decorate


@advanced_log("warn")
def vag():
    print("i am vag")


def goo():
    print("i am goo")


# 验证数据
# 重点
# @ 语法糖   @advanced_log("warn") <==> yan = advanced_log('warn')(yan)
vag()

goo = advanced_log('warn')(goo)
goo()

# 总结 可以用装饰器这种方式 本质是 函数可以当做参数传递

print('-------------------------------分割线----------------')


# 问题: 装饰器 是包装原有的函数,这样会导致一个问题,就是原函数的原信息不见了,比如
# docstring、__name__、参数列表等


# functools.wraps 解决 原有函数元信息被修改的问题
# 原理 wraps 本质也是一个装饰器,它把原函数的原信息拷贝到装饰器函数中,这样装饰器函数就和原有的函数的原信息一致了
def logg(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(func.__name__, 'was called')
        return func(*args, **kwargs)

    return wrapper


@logg
def yog():
    """does some thing"""
    print('i am yog')


# yog
print(yog.__name__)
# does some thing
print(yog.__doc__)

print('-----------------------分割线-------------------------------')


# 类装饰器 :还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法

class Koo(object):
    def __init__(self, func):
        self._fun = func

    def __call__(self, *args, **kwargs):
        print('class decorator starting')
        self._fun()
        print('class decorator ending')


# 重点: 类装饰器 @Koo <==>  sar = Koo(sar)()

@Koo
def sar():
    print('I am sar')


# sar = Koo(sar)

sar()
print(sar)

# 分割线
print("---------------分割线--------------------")


# 内置装饰器

# 自定义的 类方法装饰器
class ClassMethod(object):
    def __init__(self, func):
        self.func = func

    # instance 是对象, owner 是类
    def __get__(self, instance, owner):
        def wrapper(*args, **kwargs):
            return self.func(owner, *args, **kwargs)

        return wrapper


class Hi(object):
    def __init__(self, value):
        self.value = value

    @classmethod
    def say_hi(cls):
        print(cls)
        print("hello world!")

    # say_goodbye = ClassMethod(say_goodbye)
    # 这里故意写成 owner 为参数名是想 ClassMethod 中 __get__ 方法中的 owner 对应 方便理解
    @ClassMethod
    def say_goodbye(owner):
        print(owner)
        print("goodbye")


Hi.say_hi()
Hi.say_goodbye()


# 静态方法

# 自定义的 静态方法装饰器
class StaticMethod(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        def wrapper(*args, **kwargs):
            # 与类方法的区别是是否需要传入 owner
            return self.func(*args, **kwargs)

        return wrapper


class Coco(object):
    @staticmethod
    def static_co():
        print('i am static method co')

    @StaticMethod
    def static_coco():
        print('i am static method coco')


coco = Coco()
Coco.static_co()
coco.static_co()

Coco.static_coco()
coco.static_coco()


# 属性装饰器
class Property(object):
    def __init__(self, fget=None, fset=None):
        self.fget = fget
        self.fset = fset

    def __get__(self, instance, owner):
        if instance is not None:
            return self.fget(instance)
        return self

    def __set__(self, instance, value):
        self.fset(instance, value)

    def setter(self, func):
        self.fset = func  # 更新属性
        return self


class Person(object):
    def __init__(self, name, age):
        self._name = name
        self._age = age

    @property
    def my_name(self):
        return self._name

    @my_name.setter
    def my_name(self, name):
        self._name = name
        return self._name

    # age = Property(age)
    @Property
    def age(self):
        return self._age

    # Property(age).setter
    @age.setter
    def age(self, age):
        self._age = age
        return self._age


p = Person('gin', 18)
print(p.my_name)  # p.name()
print(p.age)
p.my_name = 'yyy'  # p.set_name('yyy')
p.age = 19
print(p.my_name)
print(p.age)




这篇关于Python 装饰器的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程