python装饰器:from zero to hero

装饰器作为 python 高级语法中的一部分,在用 python 构建框架等工具时越来越多地被用到,原因就是它可作为一种“插件”,在不破坏原来函数或者类的功能和调用的前提下,对它们进行功能上的管理:扩展或限制。

内网上有很多介绍Python装饰器的文章,比如:

python中闭包和装饰器的理解(关于python中闭包和装饰器解释最好的文章)。
【Python】一文弄懂python装饰器(附源码例子)
Python装饰器深度解析

这里整理一下我对Python装饰器的学习与理解过程,也参考了一些外网文章(会附上链接)。

一,First Class Objects

你可能听说过“Python中一切都是对象”,确实如此:

>>> dir(int)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']


>>> dir("ssttrr")
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']


>>> dir(list)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']


>>> def hello_world():
    	print("hello world!")
>>> dir(hello_world)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']


>>> class CarClass(object):
    def __init__(self, brand_name=None):
        self.brand = brand_name
    def car_run(self):
        print("%s's car is runnnig!" % (self.brand))
>>> dir(CarClass)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'car_run']
>>> dir(CarClass.car_run)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> car = CarClass("Mercedes-Benz")
>>> dir(car)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'brand', 'car_run']

Python中的所有可操作的东西都是对象,它们都是 objects 类的子类。

Python中还存在一等对象(First Class Objects)这个概念,它满足一下要求:

  • 在运行时创建
  • 能赋值给变量或数据结构中的元素
  • 能作为参数传给函数
  • 能作为函数的返回结果
  • 可以有属性和方法

一等对象实际上就是运行时创建的可调用对象。

那么,是不是所有对象都是一等对象?依据上面的说明,差不多是的。

Why do Python classes inherit object?
Python Objects Part I: This is an Object, That is an Object — Everything is an Object!
First-class objects in Python: generic code, wrappers, and factories

二,作为一等对象的函数

既然函数是一等对象,那么,除了能接收和返回一般数据类型与数据结构之外,它本身也能作为函数参数和返回值:

def doing_something(action='doing nothing!'):
    return action

def info(name, func, *args):
    info = name + ' is ' + func(*args)
    print(info)
    return func

print(info('Jack', doing_something, 'coming back!'))
>>> Jack is coming back!
>>> <function doing_something at 0x0000019876CCD168>

information = info
print(information)
>>> <function info at 0x0000019876CCD318>
information('Jack', doing_something, 'coming back!')
>>> Jack is coming back!

在调用 info 函数时,doing_something 函数作为参数被传入,并在 info 函数内部接收调用 info 函数时传入的’coming back!'参数,最后还被作为返回值由 info 函数返回。

总之记住,函数能作为函数的参数。

First-class objects in Python: generic code, wrappers, and factories

三,从普通函数到闭包函数

假如我们将上面的程序改为:

def info(name):
    acts_list = []
    acts = ""

    def doing_something(action):
        acts_list.append(action)
        print(name + ' is ' + acts.join(acts_list))	# str.join(list) 将列表内容拼接为字符串。

    return doing_something

现在 doing_something 函数成为 info 函数内部的一个函数,它依旧接收一个参数,然后操作了 info 函数内部的三个变量,最后作为 info 函数的返回值。

现在考虑一下以下程序会输出什么:

information = info('Jack')
print(information)
information('running away! ')
information('preparing for revenge! ')
information('coming back! ')
  • 第一行好理解,调用 info 函数并传入参数 ‘Jack’,调用的结果是返回 doing_something 函数,
  • 第二行也好理解,会打印 doing_something 函数对象在内存中的起始地址。
  • 第三行也像是在调用某个接收参数的函数,结合第一步的结果,应该就是调用 doing_something 函数并传入参数 'running away! ’,然后操作了三个它外层的变量name、acts_list 和 acts :将 action 加入 acts_list、将 acts_list 转为 acts ,打印由 name 和 acts 拼接的字符串 ‘Jack is running away! ’
  • 第四第五行如上。

所以应要是这样的输出:

>>> <function history.<locals>.doing_something at 0x00000232D292D318>
>>> Jack is running away! 
>>> Jack is running away! preparing for revenge! 
>>> Jack is running away! preparing for revenge! coming back!

从最终的效果来看,就是外层函数被调用后返回一个可调用对象——内部函数,再通过这个可调用对象多轮调用内层函数时,如果内层函数还使用到外层的变量,那么内层函数将获得这些变量中保存的历史值:name变量的值在历史中不变,而 acts_list 和 acts 在历史中不断增长。

实现上面这种现象的技术就叫做闭包(closure),内层的这个函数就叫闭包函数,

在编程语言中,闭包,也称为词法闭包(lexical closure)或函数闭包(function closure),是一种在具有一等函数的语言中实现词法作用域名称绑定的技术。在操作上,闭包是将函数与对应的环境存储在一起的记录。这里的环境是一个映射,它将(外层)函数的每个自由变量与创建闭包时绑定了名称的值或引用相关联。与普通函数不同,闭包函数允许函数在被调用时通过闭包技术捕获对应环境中的变量,无论闭包函数是在在不被调用还是在内部被调用。
——参考Wikipedia

用最简单的语言解释Python的闭包是什么?
理解Python闭包概念

四,从闭包函数到函数装饰器

现在我们知道了两个非常重要的内容:

  • 一等对象可作为函数参数。
  • 闭包函数能获取外部环境。

现在我们把它们简单结合一下:

import time

def info(func):
    def doing_something():
        start = time.time()
        func()
        end = time.time()
        print("the war lasted for {} seconds. ".format(end-start))

    return doing_something

def Jack():
    print("I am a king, one day my enemies came to attack my country...")
    time.sleep(2)

information = info(Jack)
information()
>>>
I am a king, one day my enemies came to attack my country...
the war lasted for 2.0077943801879883 seconds. 

这里我们通过调用闭包函数计算出了目标函数的执行时长。
实现的效果就是:

information = info(Jack)
information()
等价于
info(Jack)()
等价于
Jack = info(Jack)
Jack()

其实这就是一个简单的装饰器(decorator):外层函数接收一个函数参数,然后在闭包函数中调用它,最后外层函数返回内层函数。它们共同定义一个函数装饰器,外层函数名就是装饰器名。

Python提供了一个语法糖来简化上面那三次调用过程:如果在目标函数定义的上方使用@装饰器名,就能对这个函数提供装饰器实现的额外的功能:

@info
def Jack():
    print("I am a king, one day my enemies came to attack my country...")
    time.sleep(2)

Jack()
>>> 
I am a king, one day my enemies came to attack my country...
the war lasted for 2.0026628971099854 seconds. 

函数装饰器的就是一个嵌套函数,内层的闭包函数处理外层函数接收的函数参数,外层参数返回内层的闭包函数。

总之,最简单的装饰函数的函数装饰器是这样的:

def decorator(func):
    def wrapper():
        func()
    return wrapper
    
@ decorator
def func_name():
	# function body

if __name__ == '__main__':
	func_name()

五,更通用的函数装饰器

(一)装饰器如何处理目标函数的参数

根据前面对闭包使用中三步调用的等价关系:

def decorator(func):
    def wrapper():
        func()
    return wrapper

def func_name():
	# ...
var_name = decorator(func_name)	# 调用外部函数,接收目标函数
var_name()	# 调用内部闭包函数,在其中调用目标函数
等价于
@ decorator	# 装饰器提供其余功能
def func_name():
	# ...
func_name()	# 直接执行目标函数

如果目标函数接收参数的话,为了不影响目标函数的正常执行,在装饰器中的闭包函数中同样应该接收这些参数,然后在调用目标函数时传进去:

def decorator(func):
    def wrapper(arg):
        func(arg)
    return wrapper

def func_name(arg):
	# ...
var_name = decorator(func_name)	# 调用外部函数,接收目标函数
var_name(arg_name)	# 调用内部闭包函数,接收参数,在其中调用目标函数时将这个参数传入
等价于
@decorator
def func_name(arg):
	# ...
func_name(arg_name)

看个例子:

import time

def decorator(func):
    def wrapper(enemy):
        start = time.time()
        func(enemy)
        end = time.time()
        print("the war lasted for {} seconds. ".format(end - start))
    return wrapper

@decorator
def Jack(enemy):
    print("I am a king,my enemy %s came to attack me five days ago" % enemy)
    time.sleep(2)


if __name__ == '__main__':
    enemy_name = 'Tapio Karin'
    Jack(enemy_name)
>>>
I am a king,my enemy Tapio Karin came to attack me five days ago
the war lasted for 2.0126254558563232 seconds. 

有时目标函数可能接收多个多种参数,为了避免在装饰器中的闭包函数中重复写参数列表,通常可用变长参数 *agrs, **kwargs 的形式代替装饰器中要写的参数列表,这能让装饰器更加通用。

  • *args将所有位置参数收集到元祖变量 args 中;
  • **kwargs将所有关键字参数收集到字典变量 kwargs 中;
def decorator(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
    return wrapper

示例,打印函数接收到的参数的类型与值:

def decorator(func):
    def wrapper(*args, **kwargs):
        print("=" * 40)
        print("函数名: ", func.__name__)
        if args or kwargs:
            print('接收到参数:')
            for arg in args:
                print(type(arg), arg)
            for kwarg in kwargs:
                print(type(kwarg), kwarg)
        print("-" * 40)
        print("正在执行该函数...")
        func(*args, **kwargs)
        print("=" * 40)
    return wrapper

@decorator
def Jack(enemy):
    print("I am a king,my enemy %s came to attack me five days ago" % enemy)
    time.sleep(2)


if __name__ == '__main__':
    enemy_name = 'Tapio Karin'
    Jack(enemy_name)
>>>
========================================
函数名:  Jack
接收到参数:
<class 'str'> Tapio Karin
----------------------------------------
正在执行该函数...
I am a king,my enemy Tapio Karin came to attack me five days ago
========================================

(二)装饰器如何处理目标函数的返回值

注意到没有前面的装饰器示例中,目标函数都是没有返回值的。假设目标函数有返回值,但装饰器没有对它进行处理的话,用户调用目标函数时将无法获得其返回值。

既然在闭包函数中需要“引用并传入”目标函数的参数,那么为了让目标函数能够正常返回返回值,我们要做的就是“返回其返回”——让闭包函数返回目标函数的返回值:

def decorator(func):
    def wrapper():
    	result = func()
    	return result
    	# 或者直接返回对目标函数的调用
        # return func()
    return wrapper
  • 不管目标函数是否有 return,这种方式都起作用,进一步提高了装饰器的通用性。

例如:

def decorator(func):
    def wrapper():
        print("=" * 40)
        print("函数名: ", func.__name__)
        print("-" * 40)
        print("正在执行该函数...")
        return func()
    return wrapper

@decorator
def Jack():
    print("I am a king,my enemy came to attack me five days ago")
    time.sleep(2)
    return "In the end, I won the war"



if __name__ == '__main__':
    result = Jack()
    print(result)
>>>
========================================
函数名:  Jack
----------------------------------------
正在执行该函数...
I am a king,my enemy came to attack me five days ago
In the end, I won the war

到此为止,一个更通用的装饰器的最简结构就出现了:

def decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

试一下:

def decorator(func):
    def wrapper(*args, **kwargs):
        print("=" * 40)
        print("函数名: ", func.__name__)
        if args or kwargs:
            print('接收到参数:')
            for arg in args:
                print(type(arg), arg)
            for kwarg in kwargs:
                print(type(kwarg), kwarg)
        print("-" * 40)
        print("正在执行该函数...")
        return func(*args, **kwargs)
    return wrapper

@decorator
def Jack(enemy):
    print("I am a king,my enemy %s came to attack me five days ago" % enemy)
    time.sleep(2)
    return "In the end, I won the war"


if __name__ == '__main__':
    enemy_name = 'Tapio Karin'
    result = Jack(enemy_name)
    print(result)
>>>
========================================
函数名:  Jack
接收到参数:
<class 'str'> Tapio Karin
----------------------------------------
正在执行该函数...
I am a king,my enemy Tapio Karin came to attack me five days ago
In the end, I won the war

(三)装饰器本身也能接收参数

函数装饰器本身作为特殊的函数,本身也能接收参数。
现在,事情变得有点复杂:目标函数和装饰器本身都应该接收参数。实现此任务的想法是在现有装饰器之外添加另一层。

def decorator(decorator_arg):
    def _decorator(func):
        def wrapper(*args, **kwargs):
            print(decorator_arg)
            return func(*args, **kwargs)
        return wrapper
    return _decorator

@decorator(decorator_arg)
def func_name(arg):
	# ...
func_name(arg_name)

例如:

def decorator(decorator_arg):
    def _decorator(func):
        def wrapper(*args, **kwargs):
            if decorator_arg == "Tapio Karin":
                return func(*args, **kwargs)
            else:
                print("%s: Impossible, no one but Jack dared to invade my territory!" % func.__name__)
        return wrapper
    return _decorator


@decorator("Tapio Karin")
def Jack(enemy):
    print("Jack: I am a king,my enemy %s came to attack me five days ago..." % enemy)
    time.sleep(2)
    return "Jack: In the end, I won the war"

@decorator("Pylon Gouges")
def Tom(enemy):
    print("Tom: I am a king too,my enemy %s came to attack me five days ago" % enemy)
    time.sleep(2)
    return "In the end, I won the war"


if __name__ == '__main__':
    enemy_name = 'Tapio Karin'
    result = Jack(enemy_name)
    print(result)
    result = Tom(enemy_name)
    print(result)
>>>
Jack: I am a king,my enemy Tapio Karin came to attack me five days ago...
Jack: In the end, I won the war
Tom: Impossible, no one but Jack dared to invade my territory!
None

尽管我们让 Tapio Karin 这个家伙去侵略了Jack 和 Tom,但由于从 decorator 指定的可能性来看,Jack 和 Tom 对 Tapio Karin 的侵略表现出不同的反应。

这就是装饰器的强大之处,除了简单的扩展函数的功能之外,还能接收参数来限制函数的执行——只有满足指定条件才能执行目标函数。

尽管我们将这些个限制条件作为目标函数本身的参数来传入,但装饰器的使用能更加显式地指定限制条件。
实际的运用就像一些 web 框架中自带的装饰器一样,比如一些功能只能通过 POST 请求来实现,其它的请求发方法将不能实现功能。

但目前为止,一个更通用的装饰器完成了:

def decorator(decorator_arg):
    def _decorator(func):
        def wrapper(*args, **kwargs):
            # do something about decorator_arg
            # and return func(*args, **kwargs) or not
        return wrapper
    return _decorator

@decorator(decorator_arg)
def func_name(arg):
	# ...
	
func_name(arg_name)

(四)保持目标本来的名称

装饰器太好用了,但还是有个缺点:在装饰目标的同时,不能保持目标本身的名称。

示例:

def auth(decorator_arg):
    def _auth(func):
        def wrapper(*args, **kwargs):
            if decorator_arg == "Justice Johnson":
                print("In", func.__name__, end=", ")
                print(decorator_arg, "makes the verdict that", end=" ")
                return func(*args, **kwargs)
            else:
                print("In", func.__name__, end=", ")
                print(decorator_arg, "has no right to make a ruling.")
        return wrapper
    return _auth


@auth("Justice Johnson")
def ruling_of_2swar(war_criminal):
    print("%s should be responsible for the war." % war_criminal)

@auth("Watermelon—eater Furry")
def glorify_crimes_of_2swar(war_criminal):
    print("%s should not be responsible for the war." % war_criminal)

if __name__ == '__main__':
    prisoner_name = 'Tapio Karin'
    ruling_of_2swar(prisoner_name)
    glorify_crimes_of_2swar(prisoner_name)

    print(ruling_of_2swar.__name__)
    print(glorify_crimes_of_2swar.__name__)
>>>
In ruling_of_2swar, Justice Johnson makes the verdict that Tapio Karin should be responsible for the war.
In glorify_crimes_of_2swar, Watermelon—eater Furry has no right to make a ruling.
wrapper
wrapper
  • 经过装饰的函数在被调用之后,它们的名字都变成装饰器中闭包函数的名字了。

但我们可以用装饰器 @functools.wraps(func) 来保持目标函数的原来的函数名:

from functools import wraps

def auth(decorator_arg):
    def _auth(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
        ...
...
>>>
...
ruling_of_2swar
glorify_crimes_of_2swar

改进后,更通用的函数装饰器是:

from functools import wraps

def decorator(decorator_arg):
    def _decorator(func):
    	@wraps(func)
        def wrapper(*args, **kwargs):
            # do something about decorator_arg
            # and return func(*args, **kwargs) or not
        return wrapper
    return _decorator

@decorator(decorator_arg)
def func_name(arg):
	# ...
	
func_name(arg_name)

六,从作为一等对象的类到类装饰器

从更接近本质的角度来说,装饰器就是一个返回可调用对象的可调用对象,既然如此,同样作为一等对象的类也能实现装饰器。

(一)类装饰器的实现

为了使用类实现类装饰器, 我们需要使用类的可重载运算,并且使用类实例属性而不是外层环境中的映射。同样将函数作为参数来初始化类装饰器,然后在__call__中进行调用。

class ClsDecorator:
    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kwargs):
        return self._func(*args, **kwargs)
	
	

@ClsDecorator
def func_name(arg):
	# ...
	
func_name(arg_name)

来看一下具体表现:

class ClsDecorator:
    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kwargs):
        print(self._func)
        if args or kwargs:
            print("=" * 40)
            print('参数:')
            for arg in args:
                print(type(arg), arg)
            for kwarg in kwargs:
                print(type(kwarg), kwarg)
        return self._func(*args, **kwargs)


@ClsDecorator
def func_name(a, b):
    return a + b


func_name(1, 2)
>>>
<function func_name at 0x0000025A1FA3D438>
========================================
参数:
<class 'int'> 1
<class 'int'> 2

用类装饰器装饰某个函数:

  • 函数作为参数来实例化类装饰器。
  • 函数的所有参数传入__call__方法。然后在其中进行函数调用。
  • 当目标函数在外部被调用时, 实际上触发的是类装饰器中的__call__方法。

示例:打印函数调用的详情:

import time
from datetime import datetime


class FuncRunLog:
    def __init__(self, func):
        self._func = func
        self._start_time = None
        self._end_time = None
        self._duration = None

    def __call__(self, *args, **kwargs):
        print("*" * 40)
        print('函数名: {}'.format(self._func.__name__))
        print("调用时间:", datetime.now())
        if args or kwargs:
            print("=" * 40)
            print('参数:')
            for arg in args:
                print(type(arg), arg)
            for kwarg in kwargs:
                print(type(kwarg), kwarg)
        print("=" * 40)
        print("正在运行...")
        self._start_time = time.time()
        results = self._func(*args, **kwargs)
        self._end_time = time.time()
        self._duration = self._end_time - self._start_time
        if results:
            print("=" * 40)
            print('返回:')
            print(type(results), results)
        print("=" * 40)
        print("运行耗时: ", self._duration)
        print("*" * 40)


@FuncRunLog
def foo(name, age):
    info = name + " is " + str(age) + " yo."
    time.sleep(2)
    return info


foo("Jack", 2)
>>>
****************************************
函数名: foo
调用时间: 2021-12-05 12:55:54.764037
========================================
参数:
<class 'str'> Jack
<class 'int'> 2
========================================
正在运行...
========================================
返回:
<class 'str'> Jack is 2 yo.
========================================
运行耗时:  2.0147767066955566
****************************************

示例:记录函数调用的次数:

class tracer:
    def __init__(self, func):  # Remember original, init counter
        self.calls = 0
        self.func = func  # On later calls: add logic, run original

    def __call__(self, *args):
        self.calls += 1
        print('call %s to %s' % (self.calls, self.func.__name__))
        return self.func(*args)


@tracer  # Same as spam = tracer(spam)
def spam(a, b, c):  # Wrap spam in a decorator object
    return a + b + c


@tracer  # Same as spam = tracer(spam)
def foo(a, b, c):  # Wrap spam in a decorator object
    return a + b + c


print(spam)  # Really calls the tracer wrapper object
print(spam('a', 'b', 'c'))  # Invokes _call_in class
print(spam('d', 'e', 'f'))  # Invokes _call_in class

print(foo)
print(foo('a', 'b', 'c'))
print(foo('d', 'e', 'g'))
>>> 
<__main__.tracer object at 0x000001568A633E88>
call 1 to spam
abc
call 2 to spam
deg

<__main__.tracer object at 0x000001F74C823D48>
call 1 to foo
abc
call 2 to foo
deg
  • 这种方式装饰普通函数是没有问题的。

在不带参数的类装饰器中,可以像下面这样来保存目标函数原本的函数名:

class ClsDecorator:
    def __init__(self, func):
        self._func = func
        self.__name__ = func.__name__

尽管解决了上面的问题,但用类实现装饰器还是会产生来下面的问题:

  • 无法获取目标函数中关键字参数的值。

What does __ call __ do in Python?

(二)向类装饰器传参

向类装饰器传参的情况与上面非常不同:

class ClsDecorator:
    def __init__(self, arg):	# 注意与不接收参数的类装饰器的区别
        self._arg = arg	

    def __call__(self, func):
    	self._func = func
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper

@ClsDecorator(arg)
def func_name(arg):
	# ...
	
func_name(arg_name)

其实就像带参数的函数装饰器一样,多加了一层来接收参数。
来看一下具体表现:

class ClsDecorator:
    def __init__(self, ClsDecorator_arg):
        self._ClsDecorator_arg = ClsDecorator_arg

    def __call__(self, func):
        print(self._ClsDecorator_arg)
        print("*" * self._ClsDecorator_arg**2)
        print(func)

        def wrapper(*args, **kwargs):
            if args or kwargs:
                print('参数:')
                for arg in args:
                    print(type(arg), arg)
                for kwarg in kwargs:
                    print(type(kwarg), kwarg)
            return func(*args, **kwargs)

        return wrapper


@ClsDecorator(5)
def add(a, b):
    return a + b


sum = add(1, 2)
print(sum)
>>>
5
*************************
<function add at 0x00000214F4D4D438>
参数:
<class 'int'> 1
<class 'int'> 2
3

向类装饰器传参时:

  • 该参数将代替函数作为参数传递给__init __构造函数。
  • 函数将作为唯一参数传递给 __call__ 方法,函数的参数需要传给该方法中的wrapper函数,在wrapper函数中完成目标函数的调用。

示例:

class Judge:
    def __init__(self, decorator_arg):
        self._decorator_argc = decorator_arg

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            if self._decorator_argc == "Justice Johnson":
                print(self._decorator_argc, "said", end=" ")
                return func(*args, **kwargs)
            else:
                print(self._decorator_argc, "said", end=" ")
                return func(*args, **kwargs)
        return wrapper


@Judge("Justice Johnson")
def recored_of_2swar(name):
    info = "%s should be responsible for this war." % name
    print(info)

@Judge("Watermelon eater")
def Shirk_responsibility_for_wrong(name):
    info = "%s should not be responsible for this war." % name
    print(info)


if __name__ == '__main__':
    name = "Tapio Karin"
    recored_of_2swar(name)
    Shirk_responsibility_for_wrong(name)

>>>
Justice Johnson said Tapio Karin should be responsible for this war.
Watermelon eater said Tapio Karin should not be responsible for this war.

Python Class Decorators
Using Class Decorators in Python

(三)更通用的类装饰器

既然带参数的类装饰器与不带参数的类装饰器在装饰函数时差异如此之大,能否实现更通用的类装饰器来装饰函数呢?

回顾一下:

  • 如果类装饰器不带参数,目标函数作为参数传入__init__方法来实例化类装饰器,目标函数的所有参数传入__call__方法,在__call__方法中调用目标函数。
  • 如果类装饰器带参数,装饰器参数作为参数传入__init__方法来实例化类装饰器,目标函数作为参数传入__call__方法,目标函数的所有参数传入__call__方法中的wrapper函数,在wrapper函数中调用目标函数。

由于函数本身能作为参数传入另一个函数,这就导致__init__方法的参数既可能是一般数据类型又可能是一个函数,但好在一般情况之下传入装饰器的参数都是一般数据类型的邪恶的,所以可以判断传入__init____call__的参数的类型来判断类装饰器是否接收参数。

class ClsDecorator:
    def __init__(self, parameter_1):
        self._parameter_1 = parameter_1

    def __call__(self, *parameter_2, **parameter_3):
        if isfunction(self._parameter_1):
            return self._parameter_1(*parameter_2, **parameter_3)
        else:
            def wrapper(*args, **kwargs):
                return parameter_2[0](*args, **kwargs)
            return wrapper


@ClsDecorator(arg)
def func_name(arg):
	# ...
	
# @ClsDecorator
# def func_name(arg):
# 	# ...

if __name__ == '__main__':
   	func_name(arg_name)

来看一下具体表现:

from inspect import isfunction


class ClsDecorator:
    def __init__(self, parameter_1):
        self._parameter_1 = parameter_1

    def __call__(self, *parameter_2, **parameter_3):
        if isfunction(self._parameter_1):
            print("类装饰器无参数。")
            if parameter_2 or parameter_3:
                print("函数参数:")
                for arg in parameter_2:
                    print(type(arg), arg)
                for kwarg in parameter_3:
                    print(type(kwarg), kwarg)
            return self._parameter_1(*parameter_2, **parameter_3)
        else:
            print("=" * self._parameter_1 ** 2)
            print("类装饰器有参数:", self._parameter_1)
            def wrapper(*args, **kwargs):
                if parameter_2 or parameter_3:
                    print("函数有参数:")
                    for arg in args:
                        print(type(arg), arg)
                    for kwarg in kwargs:
                        print(type(kwarg), kwarg)
                return parameter_2[0](*args, **kwargs)
            return wrapper


@ClsDecorator
def add(a, b):
    return a + b


# @ClsDecorator(5)
# def add(a, b):
#     return a + b

if __name__ == '__main__':
    sum = add(1, 2)
    print(sum)

>>>
类装饰器无参数。
函数参数:
<class 'int'> 1
<class 'int'> 2
3

Python判断对象是否是function的三种方法

七,装饰器与被装饰对象

函数装饰器除了装饰函数外,还能装饰类以及类中的方法。
类装饰器了装饰函数外,也能装饰类以及类中的方法。

(一)函数装饰器装饰函数

比如,用函数装饰器计数函数执行时间:

import time

def decorator(func):
    def wrapper():
        print("函数 {} 开始啦!".format(func.__name__))
        start = time.time()
        func()
        end = time.time()
        print("函数 {} 结束啦啦!".format(func.__name__))
        print("执行函数 {} 共花费 {} 秒".format(func.__name__, end - start))
    return wrapper

@decorator
def test_func():
    time.sleep(3)

test_func()
>>>
函数 test_func 开始啦!
函数 test_func 结束啦啦!
执行函数 test_func 共花费 3.0109567642211914

比如django内置的login_required装饰器:

def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
    """
    Decorator for views that checks that the user is logged in, redirecting
    to the log-in page if necessary.
    """
    actual_decorator = user_passes_test(
        lambda u: u.is_authenticated,
        login_url=login_url,
        redirect_field_name=redirect_field_name
    )
    if function:
        return actual_decorator(function)
    return actual_decorator

示例:python 使用装饰器并记录log的示例代码

(二)函数装饰器装饰类方法

函数装饰器装饰类方法的实现与函数装饰器装饰函数的实现一致。

比如,用函数装饰器计数类方法执行时间:

import time

def decorator(func):
    def wrapper(*args, **kwargs):
        print("类方法 {} 开始啦!".format(func.__name__))
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print("类方法 {} 结束啦啦!".format(func.__name__))
        print("执行类方法 {} 共花费 {} 秒".format(func.__name__, end - start))
    return wrapper

class TestClass:
    def __init__(self):
        self.class_name = self.__name__

    @decorator
    def test_func(self):
        time.sleep(3)

tc = TestClass()
tc.test_func()
>>>
类方法 test_func 开始啦!
类方法 test_func 结束啦啦!
执行类方法 test_func 共花费 3.0107555389404297

(三)函数装饰器装饰类

def decorator(cls):
    def wrapper(*args, **kwargs):
        return cls(*args, **kwargs)

@decorator
class ClassTest:
    def __init__(self, *args, **kwargs):
        # ...

    def class_method(self, *args, **kwargs):
        # ...


if __name__ == '__main__':
    car = ClassTest()
    car.class_method()

比如:

def decorator(cls):
    def wrapper(*args, **kwargs):
        print("On fifth avenue, ", end="")
        return cls(*args, **kwargs)
    return wrapper


@decorator
class Car:
    def __init__(self, brand):
        self.brand = brand

    def car_run(self):
        print("a " + self.brand + " is on a rampage.")


if __name__ == '__main__':
    brand = "Mercedes Benz"
    car = Car(brand)
    car.car_run()
>>>
On fifth avenue, a Mercedes Benz is on a rampage.

(四)类装饰器装饰函数

示例:打印函数调用的详情:

import time
from datetime import datetime


class FuncRunLog:
    def __init__(self, func):
        self._func = func
        self._start_time = None
        self._end_time = None
        self._duration = None

    def __call__(self, *args, **kwargs):
        print("*" * 40)
        print('函数名: {}'.format(self._func.__name__))
        print("调用时间:", datetime.now())
        if args or kwargs:
            print("=" * 40)
            print('参数:')
            for arg in args:
                print(type(arg), arg)
            for kwarg in kwargs:
                print(type(kwarg), kwarg)
        print("=" * 40)
        print("正在运行...")
        self._start_time = time.time()
        results = self._func(*args, **kwargs)
        self._end_time = time.time()
        self._duration = self._end_time - self._start_time
        if results:
            print("=" * 40)
            print('返回:')
            print(type(results), results)
        print("=" * 40)
        print("运行耗时: ", self._duration)
        print("*" * 40)


@FuncRunLog
def foo(name, age):
    info = name + " is " + str(age) + " yo."
    time.sleep(2)
    return info


foo("Jack", 2)
>>>
****************************************
函数名: foo
调用时间: 2021-12-05 12:55:54.764037
========================================
参数:
<class 'str'> Jack
<class 'int'> 2
========================================
正在运行...
========================================
返回:
<class 'str'> Jack is 2 yo.
========================================
运行耗时:  2.0147767066955566
****************************************

示例:记录函数调用的次数:

class tracer:
    def __init__(self, func):  # Remember original, init counter
        self.calls = 0
        self.func = func  # On later calls: add logic, run original

    def __call__(self, *args):
        self.calls += 1
        print('call %s to %s' % (self.calls, self.func.__name__))
        return self.func(*args)


@tracer  # Same as spam = tracer(spam)
def spam(a, b, c):  # Wrap spam in a decorator object
    return a + b + c


@tracer  # Same as spam = tracer(spam)
def foo(a, b, c):  # Wrap spam in a decorator object
    return a + b + c


print(spam)  # Really calls the tracer wrapper object
print(spam('a', 'b', 'c'))  # Invokes _call_in class
print(spam('d', 'e', 'f'))  # Invokes _call_in class

print(foo)
print(foo('a', 'b', 'c'))
print(foo('d', 'e', 'g'))
>>> 
<__main__.tracer object at 0x000001568A633E88>
call 1 to spam
abc
call 2 to spam
deg

<__main__.tracer object at 0x000001F74C823D48>
call 1 to foo
abc
call 2 to foo
deg

示例:Python使用装饰器来实现重试

(五)类装饰器装饰类方法

类装饰器装饰普通函数是没有问题的,但装饰类方法就可能会出现问题:

  • 无法保存目标函数原本的函数名。
  • 无法获取目标函数中关键字参数的值。

比如上面那个类装饰器就没办法装饰类方法,因为对于其__call__方法将只传入一个 tracer 实例。

最好还是使用函数装饰器来装饰类函数。

(六)类装饰器装饰类

class decorator:
    # accept the class as argument
    def __init__(self, cls):
        self.cls = cls

    # accept the class's __init__ method arguments
    def __call__(self, *args, **kwargs):
        return self.cls(*args, **kwargs)


@decorator
class ClassTest:
    def __init__(self, *args, **kwargs):
        # ...

    def class_method(self, *args, **kwargs):
        # ...


if __name__ == '__main__':
    car = ClassTest()
    car.class_method()

比如:

class decorator:
    # accept the class as argument
    def __init__(self, cls):
        self.cls = cls

    # accept the class's __init__ method arguments
    def __call__(self, *args, **kwargs):
        if args or kwargs:
            print('参数:')
            for arg in args:
                print(type(arg), arg)
            for kwarg in kwargs:
                print(type(kwarg), kwarg)
        return self.cls(*args, **kwargs)


@decorator
class Car:
    def __init__(self, brand):
        self.brand = brand

    def car_run(self):
        print("a " + self.brand + " is on a rampage.")


if __name__ == '__main__':
    brand_name = "Mercedes Benz"
    car = Car(brand_name)
    car.car_run()

恶补了 Python 装饰器的八种写法,你随便问~

内容和语句并没经过仔细整理,想到哪写到哪,总算是写完了。如有问题,请指出。

请参考《Learning Python, 5th Edition》第三十二章 或者《Python Cookbook, Third Edition 3rd Edition》第九章获取更详细的装饰器相关的知识。两本书的中文翻译有点难理解,英语差不多的建议上英文版。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值