基于 Python 类的装饰器,带有可以装饰方法或函数的参数

Posted

技术标签:

【中文标题】基于 Python 类的装饰器,带有可以装饰方法或函数的参数【英文标题】:Python Class Based Decorator with parameters that can decorate a method or a function 【发布时间】:2012-03-14 02:07:18 【问题描述】:

我见过很多 Python 装饰器的例子:

函数样式装饰器(包装函数) 类风格装饰器(实现__init____get____call__) 不带参数的装饰器 接受参数的装饰器 “方法友好”的装饰器(即可以装饰类中的方法) “功能友好”的装饰器(可以装饰普通功能 可以装饰方法和函数的装饰器

但我从未见过一个可以完成上述所有操作的示例,而且我无法从特定问题的各种答案(例如 this one、this one 或 this one (which has one of the best answers I've ever seen on SO))综合起来,如何结合以上所有内容。

我想要的是一个基于类的装饰器,它可以装饰方法或函数至少需要一个附加参数。即,以便以下工作:

class MyDecorator(object):
    def __init__(self, fn, argument):
        self.fn = fn
        self.arg = argument

    def __get__(self, ....):
        # voodoo magic for handling distinction between method and function here

    def __call__(self, *args, *kwargs):
        print "In my decorator before call, with arg %s" % self.arg
        self.fn(*args, **kwargs)
        print "In my decorator after call, with arg %s" % self.arg


class Foo(object):
    @MyDecorator("foo baby!")
    def bar(self):
        print "in bar!"


@MyDecorator("some other func!")
def some_other_function():
    print "in some other function!"

some_other_function()
Foo().bar()

我希望看到:

In my decorator before call, with arg some other func!
in some other function!
In my decorator after call, with arg some other func!
In my decorator before call, with arg foo baby!
in bar!
In my decorator after call, with arg foo baby!

编辑:如果重要的话,我使用的是 Python 2.7。

【问题讨论】:

“带参数的装饰器”只是一个带参数并返回装饰器的函数。 又为什么需要分别处理方法和函数呢?只需传递所有参数即可。 @katrielalex,一个方法作为一个普通函数开始它的生命,并作为一个存储在类中。当您查找一个方法时,它变成了一个绑定方法,其中函数的第一个参数将是您查找该方法的实例。当您拥有自己的类的实例而不是函数的对象时,它们不会自动执行此操作。 @Katriel 可能有一些非常特殊的情况,您必须以不同的方式处理方法和“常规”函数的装饰。 【参考方案1】:

你不需要乱用描述符。在__call__() 方法中创建一个包装函数并返回它就足够了。标准 Python 函数始终可以充当方法或函数,具体取决于上下文:

class MyDecorator(object):
    def __init__(self, argument):
        self.arg = argument

    def __call__(self, fn):
        @functools.wraps(fn)
        def decorated(*args, **kwargs):
            print "In my decorator before call, with arg %s" % self.arg
            result = fn(*args, **kwargs)
            print "In my decorator after call, with arg %s" % self.arg
            return result
        return decorated

关于当这个装饰器被这样使用时发生了什么的一点解释:

@MyDecorator("some other func!")
def some_other_function():
    print "in some other function!"

第一行创建MyDecorator 的实例并将"some other func!" 作为参数传递给__init__()。我们称这个实例为my_decorator。接下来,未装饰的函数对象——我们称之为bare_func——被创建并传递给装饰器实例,因此my_decorator(bare_func)被执行。这将调用MyDecorator.__call__(),它将创建并返回一个包装函数。最后这个包装函数被分配给名称some_other_function

【讨论】:

我认为 OP 缺少的关键方面是还有另一个级别的可调用对象:调用 MyDecorator 并调用其结果,返回我们存储在类/模块中的对象(稍后多次调用)。 @MikeGraham:我在帖子中添加了一些解释。 @MikeGraham:完全正确,我不明白第二级间接。 在 MyDecorator.__call__ 内部,functools.wraps 用于处理一些内务细节 - 将函数名称、文档字符串和参数列表从包装函数 (bare_func) 复制到包装函数。 SO问题“What does functools.wraps do?”中对 functools.wraps 有很好的解释 @Matt 谢谢,我已经更新了答案。 (请注意,该部分是从问题中复制的。)【参考方案2】:

你错过了一个关卡。

考虑代码

class Foo(object):
    @MyDecorator("foo baby!")
    def bar(self):
        print "in bar!"

和这段代码是一样的

class Foo(object):
    def bar(self):
        print "in bar!"
    bar = MyDecorator("foo baby!")(bar)

所以MyDecorator.__init__"foo baby!" 调用,然后MyDecorator 对象被函数bar 调用。

也许你的意思是实现更像

import functools

def MyDecorator(argument):
    class _MyDecorator(object):
        def __init__(self, fn):
            self.fn = fn

        def __get__(self, obj, type=None):
            return functools.partial(self, obj)

        def __call__(self, *args, **kwargs):
            print "In my decorator before call, with arg %s" % argument
            self.fn(*args, **kwargs)
            print "In my decorator after call, with arg %s" % argument

    return _MyDecorator

【讨论】:

【参考方案3】:

在您的装饰器类型列表中,您错过了可能带参数或不带参数的装饰器。我认为这个示例涵盖了除“函数样式装饰器(包装函数)”之外的所有类型

class MyDecorator(object):

    def __init__(self, argument):
        if hasattr('argument', '__call__'):
            self.fn = argument
            self.argument = 'default foo baby'
        else:
            self.argument = argument

    def __get__(self, obj, type=None):
        return functools.partial(self, obj)

    def __call__(self, *args, **kwargs):
        if not hasattr(self, 'fn'):
            self.fn = args[0]
            return self
        print "In my decorator before call, with arg %s" % self.argument
        self.fn(*args, **kwargs)
        print "In my decorator after call, with arg %s" % self.argument


class Foo(object):
    @MyDecorator("foo baby!")
    def bar(self):
        print "in bar!"

class Bar(object):
    @MyDecorator
    def bar(self):
        print "in bar!"

@MyDecorator
def add(a, b):
    print a + b

【讨论】:

只回答不使用本地函数或类的,这正是我需要的! 感谢 Vinayak!带有我正在寻找的可选参数的装饰器。 这真是太棒了!我已经对其进行了测试,它确实可以满足需要。但是我能请你解释一下它是如何工作的吗?这是相当神秘的。 get 在什么时候被调用,由谁调用?为什么需要类型参数,它是什么?我从 functools 文档中理解了部分内容,但是 get 文档非常通用,我无法弄清楚在装饰和使用装饰函数期间是谁在调用它。 @BerndWechner:get 将使 MyDecorator 成为描述符。请参考:docs.python.org/3/howto/descriptor.html#descriptor-protocol f = Foo() f.bar() # 这将调用 MyDecorator_object.__get__(f, Foo)

以上是关于基于 Python 类的装饰器,带有可以装饰方法或函数的参数的主要内容,如果未能解决你的问题,请参考以下文章

python 之装饰器

了解使基于类的装饰器支持实例方法的技术[重复]

Python-装饰

Python装饰器

python 装饰器

python 基于类的装饰器。