Explanation of a decorator class in python

后端 未结 2 1412
无人共我
无人共我 2021-02-06 17:50

While reading up about some python module I encountered this decorator class:

# this decorator lets me use methods as both static and instance methods
class omni         


        
相关标签:
2条回答
  • 2021-02-06 17:57

    omnimethod does what it says in the comment; it will let you call some_function as either a 'static function' on a class or as a function on an instance of the class. @property is a standard decorator (see the python docs) that exposes a function in a way that makes it look like a simple instance variable.

    class B:
      @omnimethod
      def test(self):
        print 1
    
      @property
      def prop(self):
        return 2
    
    >>> b = B()
    >>> b.test()
    1
    >>> B.test()
    1
    >>> b.prop
    2
    
    0 讨论(0)
  • 2021-02-06 18:24

    that omnimethod is very clever. It uses some very subtle tricks to do it's job. Let's start at the beginning.

    You probably already know that decorator syntax is just sugar for function application, ie:

    @somedecorator
    def somefunc(...):
        pass
    
    # is the same thing as    
    
    def somefunc(...):
        pass
    somefunc = somedecorator(somefunc)
    

    so somefunc is actually an omnimethod instance, not the function that had been defined. what's interesting about this is that omnimethod also implements the descriptor interface. If a class attribute defines a __get__ method, then whenever that attribute is mentioned, the interpreter instead calls __get__ on that object, and returns that instead of returning the attribute itself.

    the __get__ method is always called with instance as first argument, and the class of that instance as second argument. If the attribute was actually looked up from the class itself, then instance will be None.

    The last bit of trickery is functools.partial, which is the python way of function currying. when you use partial, you pass it a function and some arguments, and it returns a new function, that when called, will call the original function with the original arguments in addition to whichever arguments you passed in later. omnimethod uses this technique to populate the self parameter to the function it wraps.

    Here's what that looks like. a regular method can be called when you read it from an instance but you can't use it from the class itself. you get an unbound TypeError

    >>> class Foo(object):
    ...     def bar(self, baz):
    ...         print self, baz
    ... 
    >>> f = Foo()
    >>> f.bar('apples')
    <__main__.Foo object at 0x7fe81ab52f90> apples
    >>> Foo.bar('quux')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unbound method bar() must be called with 
    Foo instance as first argument (got str instance instead)
    >>> Foo.bar(None, 'quux')
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unbound method bar() must be called with
    Foo instance as first argument (got NoneType instance instead)
    >>> 
    

    Python provides a bultin decorator classmethod (and also staticmethod, but nevermind that), that will allow you to use it at the class level, but it never gets to see the instance. it always recieves the class as it's first argument.

    >>> class Foo(object):
    ...     @classmethod
    ...     def bar(cls, baz):
    ...         print cls, baz
    ... 
    >>> f = Foo()
    >>> Foo.bar('abc')
    <class '__main__.Foo'> abc
    >>> f.bar('def')
    <class '__main__.Foo'> def
    >>> 
    

    By it's bit of cleverness, omnimethod gives you a little bit of both.

    >>> class Foo(object):
    ...     @omnimethod
    ...     def bar(self, baz):
    ...         print self, baz
    ... 
    >>> f = Foo()
    >>> Foo.bar('bananas')
    None bananas    
    >>> f.bar('apples')
    <__main__.Foo object at 0x7fe81ab52f90> apples
    >>> 
    
    0 讨论(0)
提交回复
热议问题