Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #63513 > unrolled thread

Understanding decorator and class methods

Started byaxis.of.weasel@gmail.com
First post2014-01-08 11:56 -0800
Last post2014-01-08 19:20 -0500
Articles 3 — 3 participants

Back to article view | Back to comp.lang.python


Contents

  Understanding decorator and class methods axis.of.weasel@gmail.com - 2014-01-08 11:56 -0800
    Re: Understanding decorator and class methods Rotwang <sg552@hotmail.co.uk> - 2014-01-08 23:17 +0000
    Re: Understanding decorator and class methods Terry Reedy <tjreedy@udel.edu> - 2014-01-08 19:20 -0500

#63513 — Understanding decorator and class methods

Fromaxis.of.weasel@gmail.com
Date2014-01-08 11:56 -0800
SubjectUnderstanding decorator and class methods
Message-ID<d4d38119-b5b8-415a-bb7b-5d544907df3e@googlegroups.com>
can someone please explain why the following works, in contrast to the second example?

def decorator(func):
    def on_call(*args):
        print args  
        return func(args)
    return on_call

class Foo:
    @decorator
    def bar(self, param1):
        print 'inside bar'

f=Foo()
f.bar(4)  # from where is the decorator getting the Foo instance?



I understand why the following works/does not work

class decorator2:
    def __init__(self, func):
        self.func=func
    def __call__(self, *args):
        self.func(*args)

class Foo2:
    @decorator2
    def bar2(self, param): pass


f2 = Foo2()
Foo2.bar2(f2, 4) # works, Foo2 instance and param are passed to decorator2 call
f2.bar2(4) # does not work, Foo2 instance is missing, decorator2 cannot invoke method bar

[toc] | [next] | [standalone]


#63531

FromRotwang <sg552@hotmail.co.uk>
Date2014-01-08 23:17 +0000
Message-ID<lakma4$o7i$1@dont-email.me>
In reply to#63513
On 08/01/2014 19:56, axis.of.weasel@gmail.com wrote:
> can someone please explain why the following works, in contrast to the second example?
>
> def decorator(func):
>      def on_call(*args):
>          print args
>          return func(args)
>      return on_call
>
> class Foo:
>      @decorator
>      def bar(self, param1):
>          print 'inside bar'
>
> f=Foo()
> f.bar(4)  # from where is the decorator getting the Foo instance?
>
>
>
> I understand why the following works/does not work
>
> class decorator2:
>      def __init__(self, func):
>          self.func=func
>      def __call__(self, *args):
>          self.func(*args)
>
> class Foo2:
>      @decorator2
>      def bar2(self, param): pass
>
>
> f2 = Foo2()
> Foo2.bar2(f2, 4) # works, Foo2 instance and param are passed to decorator2 call
> f2.bar2(4) # does not work, Foo2 instance is missing, decorator2 cannot invoke method bar

 From http://docs.python.org/3/reference/datamodel.html:

Instance methods

     An instance method object combines a class, a class instance and
     any callable object (normally a user-defined function).

     [...]

     User-defined method objects may be created when getting an
     attribute of a class (perhaps via an instance of that class), if
     that attribute is a user-defined function object or a class method
     object.

     [...]

     Note that the transformation from function object to instance
     method object happens each time the attribute is retrieved from the
     instance. In some cases, a fruitful optimization is to assign the
     attribute to a local variable and call that local variable. Also
     notice that this transformation only happens for user-defined
     functions; other callable objects (and all non-callable objects)
     are retrieved without transformation.


Notice the last sentence in particular. After being decorated by 
decorator2 Foo2.bar2 is not a user-defined function (i.e. an instance of 
types.FunctionType), so is not transformed into a method upon being 
accessed through an instance. I suppose you could create a class that 
mimics the behaviour of methods, though I don't know why you would want 
to. The following is tested with 3.3.0; I expect someone who knows more 
than I will probably be along soon to point out why it's stupid.

class decorator3:
     def __init__(self, func):
         self.func = func
     def __call__(self, *args, **kwargs):
         print('Calling func(self, *%r, **%r)' % (args, kwargs))
         return self.func(self.__self__, *args, **kwargs)
     def __get__(self, instance, owner):
         self.__self__ = instance
         return self

class Foo3:
     @decorator3
     def bar3(self, param):
         return self, param

 >>> f3 = Foo3()
 >>> f3.bar3('param')
Calling func(self, *('param',), **{})
(<__main__.Foo3 object at 0x0000000002BDF198>, 'param')

[toc] | [prev] | [next] | [standalone]


#63545

FromTerry Reedy <tjreedy@udel.edu>
Date2014-01-08 19:20 -0500
Message-ID<mailman.5216.1389226841.18130.python-list@python.org>
In reply to#63513
On 1/8/2014 2:56 PM, axis.of.weasel@gmail.com wrote:
> can someone please explain why the following works, in contrast to the second example?

Because function attributes of classes become instance methods, with 
special behavior, when accessed via an instance of the class.

> def decorator(func):
>      def on_call(*args):
>          print args
>          return func(args)

This has to be func(*args) (as in second example) or one gets
TypeError: bar() missing 1 required positional argument: 'param1'
Did you re-type instead of pasting?

>      return on_call
>
> class Foo:
>      @decorator
>      def bar(self, param1):
>          print 'inside bar'
>
> f=Foo()
> f.bar(4)  # from where is the decorator getting the Foo instance?

from args.
f.bar(4) == Foo.bar(f, 4) == on_call(*args), which prints args (tuple 
f,4) and calls func(*args) == Foo.<real bar>(f, 4) which prints 'inside bar'

> I understand why the following works/does not work
>
> class decorator2:
>      def __init__(self, func):
>          self.func=func
>      def __call__(self, *args):
>          self.func(*args)
>
> class Foo2:
>      @decorator2
>      def bar2(self, param): pass

Using a class decorator to decorate an instance method of another class 
is asking for trouble. As explained below, the result is no longer an 
instance method.

> f2 = Foo2()
> Foo2.bar2(f2, 4) # works, Foo2 instance and param are passed to decorator2 call
> f2.bar2(4) # does not work, Foo2 instance is missing, decorator2 cannot invoke method bar

Remember that
   @deco
   def f(): pass
is essentially equivalent to
   def f(): pass
   f = deco(f)

Decorator decorator replaces a function with a function. So the wrapped 
bar is still seen as an instance method, so f.bar(x) gets the magic 
instance method translation to Foo.bar(f, x). Decorator2 replaces 
function bar with a callable instance of itself, which is *not* a 
'function' and which therefore is not seen as an instance method, but 
merely a callable attribute of Foo2. So f.bar == Foo.bar, and you would 
need f2.bar(f2, 4).

-- 
Terry Jan Reedy

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web