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

Python Class Based Decorator with parameters that can decorate a method or a function

我见过很多关于python装饰的例子:

  • 函数样式装饰器(包装函数)
  • 类风格装饰(实现__init____get____call__)
  • 不接受参数的修饰符
  • 接受参数的修饰符
  • "方法友好"的装饰器(即可以在类中装饰方法)
  • "功能友好"的装饰师(可以装饰简单的功能
  • 既可以修饰方法又可以修饰函数的修饰器
  • 小精灵

    但是我从来没有见过一个能完成上述所有工作的例子,而且我很难从各种答案中综合出具体的问题(比如这个,这个,或者这个(我见过的最好的答案之一)),如何综合上述所有问题。

    我想要的是一个基于类的修饰器,它可以修饰一个方法或函数,并且至少需要一个附加参数。也就是说,以下内容可以工作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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()

    我希望看到:

    1
    2
    3
    4
    5
    6
    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。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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
                fn(*args, **kwargs)
                print"In my decorator after call, with arg %s" % self.arg
            return decorated

    解释一下当这个装饰器像这样使用时会发生什么:

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

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


    你错过了一个关卡。

    考虑一下代码

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

    与此代码相同

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

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

    也许你的意思是实现一些更像

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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


    在修饰符类型的列表中,缺少可能接受参数或不接受参数的修饰符。我认为这个例子涵盖了除"函数样式装饰器(包装函数)"之外的所有类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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