views:

44

answers:

2

Example code:

# -*- coding: utf-8 -*-
from functools import wraps

class MyClass(object):
  def __init__(self):
    pass

  #decorator inside class
  def call(f):
    @wraps(f)
    def wrapper(*args):
      print 'Wrapper: ', args
    return wrapper

  #decorated 'method' without self
  @call
  def myfunc(a): 
    pass

c = MyClass()
c.myfunc(1)

Returns:

Wrapper:  (<test3.MyClass object at 0xb788a34c>, 1)

Is this normal? Can someone explain?

If this is a feature I would use it in my library.

+1  A: 

This is perfectly normal.

The function myfunc is replacecd by an instance of wrapper. The signature of wrapper is (*args). because it is a bound method, the first argument is the instance of MyClass which is printed out after the string `Wrapper: '.

What's confusing you?

It's worth noting that if you use call as a decorator from outside of MyClass, it will generate a TypeError. One way around this is to apply the staticmethod decorator to it but then you can't call it during class construction.

It's a little bit hacky but I address how to have it both ways here.

update after comment

it gets the instance as the first argument regardless of if you type self in the parameter list because after the class is created, and an instance instantiated, it is a bound method. when you call it in the form

@instance.call
def foo(bar):
    return bar + 1

it expands to

def foo(bar):
    return bar + 1
foo = instance.call(f)

but note that you are calling it on an instance! This will automatically expand to a call of the form

def foo(bar):
    return bar + 1
foo = MyClass.call(instance, f)

This is how methods work. But you only defined call to take one argument so this raises a TypeError.

As for calling it during class construction, it works fine. but the function that it returns gets passed an instance of MyClass when it is called for the same reason that I explained above. Specifically, whatever arguments you explicity pass to it come after the implicit and automatic placement of the instance that it is called upon at the front of the argument list.

aaronasterling
I didn't specify 'self' in myfunc, but the decorator got the 'self' value at call. If I put the decorator out of the class, it fails (what is perfectly normal:).
bevotam
but maybe you are right, and it's expected. I'll check the code of 'wraps'.
bevotam
@bencevoltam `wraps` has nothing to do with this! see my updates.
aaronasterling
Ok, thanx. Now it's clear.
bevotam
+1  A: 
@call
def myfunc(a):
    ...

is equivalent to

def myfunc(a):
    ...
myfunc=call(myfunc)

The orginial myfunc may have expected only one argument, a, but after being decorated with call, the new myfunc can take any number of positional arguments, and they will all be put in args.

Notice also that

def call(f)

never calls f. So the fact that

def myfunc(a)

lacks the normal self argument is not an issue. It just never comes up.

When you call c.myfunc(1), wrapper(*args) gets called.

What is args? Well, since c.myfunc is a method call, c is sent as the first argument, followed by any subsequent arguments. In this case, the subsequent argument is 1. Both arguments are sent to wrapper, so args is the 2-tuple (c,1).

Thus, you get

Wrapper:  (<test3.MyClass object at 0xb788a34c>, 1)
unutbu