class Deco:
def __init__(self, name):
self.name = name
def __call__(self, test_class):
def inner_func(whatisit):
return whatisit
test_class.method = inner_func
return test_class
class TestClass:
def __init__(self, name):
self.name = name
@Deco('deco')
class TestClassWrapped:
def __init__(self, name):
self.name = name
test = TestClass('test')
test = Deco('deco')(test)
test_wrapped = TestClassWrapped('test')
print(test.method('whatisit')) >> whatisist
print(test_wrapped == test_wrapped.method()) >> True
Why do test.method
and test_wrapped.method
return different results ?
It seems that the first argument in test_wrapped.method
is self
, while it isn't for test.method
. Why does it differ from one to the other?
Walking through your code step-by-step:
You create a regular TestClass
named test
.
You manually call Deco
and provide it with test
, with the line test = Deco('deco')(test)
.
This makes your code go through the __call__
function, which modifies the passed class test
to set its method
attribute to the nested function. It then returns it, and so test
now contains a successfully modified TestClass
: calling test.method('whatisit')
will successfully return 'whatisit'
. Importantly, you're NOT accessing a method here : you're accessing a FUNCTION through an ATTRIBUTE. self
is passed to every method of classes in Python, but since this isn't a method, it doesn't come into play here. Try printing type(test.method)
, you'll see <class 'function'>
and not <class 'method'>
. Importantly, you've passed an INSTANCE of a TestClass
, not the class definition itself : and only this instance named test
has had its method
attribute set.
You then create a TestClassWrapped
named test_wrapped
. Upon creating it, it enters the __call__
once more, passing it TestWrappedClass
as the test_class
parameter. Importantly, you've passed a DEFINITION of the TestWrappedClass
, not an instance of it. Setting method
here will modify it for every instance of TestWrappedClass
you'll later create, and can even be accessed without instantiating anything. Try calling TestClassWrapped.method("abc")
: it will print abc
without instantiating a TestClassWrapped
at all. Interestingly, when set in this way, it's not set as an attribute but as a method! Try printing type(test_wrapped.method)
. This is what I believe to be the source of confusion.
In the case of print(test_wrapped.method())
, you have to remember that every method of instantiated classes take self
as their first parameter. This means that test_wrapped.method()
will return self
: hence why test_wrapped == test_wrapped.method()
. Note that this doesn't apply to methods called from a class definition, like I've shown earlier. TestClassWrapped.method("abc")
HAS to take a parameter of some sort (like abc
), else it will complain it's lacking an argument.
So this is why test.method('whatisit')
returns 'whatisit'
and doesn't take self
as parameter, and why test_wrapped.method()
does return self
.