decorator inside class & decorated classmethod without 'self' gives strange results
Exa开发者_开发百科mple 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.
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.
@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)
精彩评论