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

后端 未结 3 1005
南笙
南笙 2020-12-07 22:51

I\'ve seen many examples of Python decorators that are:

  • function style decorators (wrapping a function)
  • class style decorators (implementing __i
相关标签:
3条回答
  • 2020-12-07 22:58

    You don't need to mess around with descriptors. It's enough to create a wrapper function inside the __call__() method and return it. Standard Python functions can always act as either a method or a function, depending on context:

    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
    

    A bit of explanation about what's going on when this decorator is used like this:

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

    The first line creates an instance of MyDecorator and passes "some other func!" as an argument to __init__(). Let's call this instance my_decorator. Next, the undecorated function object -- let's call it bare_func -- is created and passed to the decorator instance, so my_decorator(bare_func) is executed. This will invoke MyDecorator.__call__(), which will create and return a wrapper function. Finally this wrapper function is assigned to the name some_other_function.

    0 讨论(0)
  • 2020-12-07 23:10

    In your list of types of decorators, you missed decorators that may or may not take arguments. I think this example covers all your types except "function style decorators (wrapping a function)"

    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
    
    0 讨论(0)
  • 2020-12-07 23:23

    You're missing a level.

    Consider the code

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

    It is identical to this code

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

    So MyDecorator.__init__ gets called with "foo baby!" and then the MyDecorator object gets called with the function bar.

    Perhaps you mean to implement something more like

    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
    
    0 讨论(0)
提交回复
热议问题