我是靠谱客的博主 踏实自行车,最近开发中收集的这篇文章主要介绍Python3装饰器介绍函数装饰器函数装饰器装饰类和方法实现__call__的类装饰器装饰函数装饰器类装饰方法和类,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

介绍

Python中的装饰器是一种可调用的对象(它可以是函数、或实现__call__的类),它将被装饰对象(函数、或类)作为参数传入,再经过执行所需要的处理后,返回另一个可调用对象。装饰器的本质就是把一个可调用对象变成另一个可调用对象。装饰器可以由以下多种用法。

  1. 函数装饰器装饰函数
  2. 函数装饰器装饰类
  3. 函数装饰器装饰方法(类中定义的函数)
  4. 实现__call__的类装饰器装饰函数
  5. 实现__call__的类装饰器装饰类
  6. 实现__call__的类装饰器装饰方法(类中定义的函数)

函数装饰器

本节介绍常见的用一个函数装饰器,装饰另一个函数的情况。
装饰器的功能为修改、加工被装饰函数的功能,为了解装饰器的简单原理,我们一步步描述如何修饰函数对象。

  1. 函数赋值
    Python中函数也是一种对象,它可以赋值给变量。
def func():
    print("hello func.")

def changed_func():
    print("hello changed func.")

func()
## output: hello func.
func = changed_func
func()
## output: hello changed func.

从上面的代码可以看到,我们可以通过函数赋值给变量的方法,使func可以调用不同的实际函数。

  1. 将函数作为参数
    因为函数亦是对象,因此可以作为函数参数传入。
def decorate(func):
    return changed_func

def func():
    print("hello func.")

def changed_func():
    print("hello changed func.")

func()
## output: hello func.
func = decorate(func)
func()
## output: hello changed func.

我们可以通过将func传到decorate函数中,让decorate函数帮忙返回修改后的函数changed_func,此时func()和第一种直接赋值的方法类似,具备了新的功能,成为了新的函数changed_func。

  1. 装饰器方法
    将上小节中func传入decorate函数赋值的方法,改造成Python的装饰器用法。
def decorate(func):
    def changed_func(*args, **kwargs):
        print("hello changed func.")
        return func(*args, **kwargs)

    return changed_func


@decorate
def func():
    print("hello func.")


func()

""" output
hello changed func.
hello func.
"""

通过Python的语法糖@,可以在func定义时对函数进行修饰,使func变为新的函数,这里将changed_func函数定义到了decorate函数里使装饰器函数更加内聚紧凑,如果像前面两节独立定义changed_func的方式,也不影响功能。
前面两种修改函数对象的方法都是在程序运行时完成的,而装饰器作为Python的一种语法,就像第2小节decorate赋值的方法: func = decorate(func)一样,对func完成了赋值操作,只不过它是在模块导入的时候就已经完成了这个操作。
因此@decorate可以理解为func = decorate(func)。装饰器decorate返回的是一个闭包,当调用func()时,相当于调用了changed_func(),关于闭包可以参考Python3中的命名绑定、解析与函数闭包

装饰器执行的顺序

我们通过新建一个Python文件”test.py“,分别通过导入模块和运行test.py文件观察装饰器执行顺序。
test.py文件:

def decorate(func):
    print("decorate func: %s" % func)
    return func

@decorate
def func1():
    print("hello func1.")

@decorate
def func2():
    print("hello func2.")

if __name__ == '__main__':
    func1()
    func2()

当在某一个模块中使用from test import *的导入模块语句时,程序的输出为:

decorate func: <function func1 at 0x10d44a950>
decorate func: <function func2 at 0x10e785378>

而直接运行 python3 test.py时程序的输出为:

decorate func: <function func1 at 0x10aa37840>
decorate func: <function func2 at 0x10aa37950>
hello func1.
hello func2.

可以看到装饰器decorate在模块导入时就已经执行,并将被装饰函数:func1、func2修饰。而在运行的执行func1和func2时,func1和func2已经被装饰器修饰,因此可以理解为装饰器是一种静态修饰函数功能的方法。

装饰器保留函数元信息

def decorate(func):
    def changed_func(*args, **kwargs):
        print("hello changed func.")
        return func(*args, **kwargs)

    return changed_func


@decorate
def func():
    print("hello func.")


print(func.__name__)

""" output
changed_func
"""

我们可以看到,当装饰器decorate装饰了函数func时,func相当于变成了changed_func,因此在打印func.__name__时显示的是函数changed_func的名字。我们可以通过Python标准库中的functools.wraps装饰器,使changed_func函数保留原始函数func的信息。

from functools import wraps


def decorate(func):
    @wraps(func)
    def changed_func(*args, **kwargs):
        print("hello changed func.")
        return func(*args, **kwargs)

    return changed_func


@decorate
def func():
    print("hello func.")


print(func.__name__)
func.__wrapped__()

""" output
func
hello func.
"""

通过wraps装饰器,func函数拥有了__wrapped__属性,该属性就是不被装饰器修饰的func本身,因此调用该函数只是调用了func函数而没有被装饰器修饰。

带参数的装饰器

from functools import wraps


def decorate(arg1):
    print(arg1)

    def _decorate(func):
        @wraps(func)
        def changed_func(*args, **kwargs):
            print("hello changed func.")
            return func(*args, **kwargs)

        return changed_func

    return _decorate


@decorate("test")
def func():
    print("hello func.")


func()

""" output
test
hello changed func.
hello func.
"""

@decorate('test)相当于func = decorate(‘test’)(func)
除此之外还有一些函数装饰器的用法不过多介绍,其实只要记住装饰器的本质就可以了,参考以下链接:

  1. 可自定义属性的装饰器
  2. 带可选参数的装饰器
  3. 将装饰器定义为类的一部分

函数装饰器装饰类和方法

为类提供装饰器,起到了一些元类的作用,可以重写或修改类的行为,具体可以参考:使用装饰器扩充类的功能。
为方法提供函数装饰器可以参考:为类和静态方法提供装饰器。
它的缺点时不能运用到继承中,下面通过元类的方式简单实现可以继承的装饰器。元类相关信息可以参考:Python3元类

class DecoratorInheritMeta(type):
    def __new__(metacls, *args, **kwargs):
        _, bases, namespace = args
        need_inherit_map = {k: v for k, v in namespace.items() if hasattr(v, '__wrapper__')}
        for base in bases:
            for k, v in getattr(base, '__need_inherit_map__', {}).items():
                if k in namespace.keys():
                    namespace[k] = v.__wrapper__(namespace[k])
                else:
                    need_inherit_map[k] = v

        cls = super().__new__(metacls, *args)
        cls.__need_inherit_map__ = need_inherit_map

        return cls


class DecoratorInheritMetaMixin(metaclass=DecoratorInheritMeta):

    @staticmethod
    def inherit_decorator(decorator):
        def decorate(func):
            wrapper = decorator(func)
            wrapper.__wrapper__ = decorator

            return wrapper

        return decorate


def decorate(func):
    def changed_func(*args, **kwargs):
        print("hello changed func.")
        return func(*args, **kwargs)

    return changed_func


class A(DecoratorInheritMetaMixin):
    @DecoratorInheritMetaMixin.inherit_decorator(decorate)
    def func(self):
        print("hello func A.")


class B(A):
    def func(self):
        print('hello func B.')


A().func()

B().func()

""" output
hello changed func.
hello func A.
hello changed func.
hello func B.
"""

实现__call__的类装饰器装饰函数

除了函数可以作为装饰器,任何实现了__call__方法的类也可以作为装饰器,当类装饰器装饰函数时,该函数则变为装饰器的对象,拥有对象的相关属性和方法。例如:

from functools import wraps


class Decorate():
    def __init__(self, func):
        wraps(func)(self)
        self.func = self.__wrapped__
        self.ncalls = 0

    def __call__(self, *args, **kwargs):
        self.ncalls += 1
        return self.func(*args, **kwargs)

    def total_calls(self):
        return self.ncalls


@Decorate
def func():
    print("hello func.")


func()
func()
func()

print('Total call times by func: ', func.total_calls())

""" output
hello func.
hello func.
hello func.
Total call times by func:  3
"""

通过以上代码可以实现计算函数func执行的次数,装饰器类Decorate在装饰func时,生成对象(执行__init__方法),这里调用’wraps(func)(self)’,目的和之前的@wraps(func)一样,保留func的元信息,只不过这里的self相当于之前函数装饰器时的内部函数。通过实现__call__方法,可以把Decorate的对象变为可调用对象,这里执行被装饰函数func以及其他工作。func由于被Decorate修饰,具有Decorate对象的方法,因此可以通过调用func.total_calls方法,实现得到func被调用总次数的功能。通过使用类装饰器的方法,不但可以实现函数装饰器闭包的功能, 还可以更清晰地看到被装饰后拥有哪些属性和方法,可以使装饰器更加灵活。

装饰器类装饰方法和类

当装饰器类修饰方法时需要特别注意。按照装饰器的核心,装饰器将被装饰的可执行对象变为装饰器类的对象,而由于函数这个对象存在__get__方法(为了类对象的方法访问,在类中定义的函数本身是个描述器),因此我们也需要为装饰器类实现__get__方法,实现对象方法调用的描述器功能。

import types
from functools import wraps


class Decorate():
    def __init__(self, func):
        wraps(func)(self)
        self.func = self.__wrapped__
        self.ncalls = 0

    def __call__(self, *args, **kwargs):
        self.ncalls += 1
        return self.func(*args, **kwargs)

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return types.MethodType(self, instance)

    def total_calls(self):
        return self.ncalls


class Test:
    @Decorate
    def func(self):
        print("hello func.")


t = Test()

t.func()
t.func()
t.func()

print('Total call times by func: ', t.func.total_calls())

""" output
hello func.
hello func.
hello func.
Total call times by func:  3
"""

__get__方法实现了描述器的功能,为了使在调用对象t.func()时实现绑定方法的返回,如不实现该函数,则在调用t.func()不会绑定func中的self。

至于使用装饰器类直接装饰类的场景还没有遇到过,而且当用这种方法时,被装饰的类会变为装饰器类的对象,在继承时会出现问题,因此这种方法暂不考虑。

最后

以上就是踏实自行车为你收集整理的Python3装饰器介绍函数装饰器函数装饰器装饰类和方法实现__call__的类装饰器装饰函数装饰器类装饰方法和类的全部内容,希望文章能够帮你解决Python3装饰器介绍函数装饰器函数装饰器装饰类和方法实现__call__的类装饰器装饰函数装饰器类装饰方法和类所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(66)

评论列表共有 0 条评论

立即
投稿
返回
顶部