Category Catalog: General Catalog of "Systematic Learning Python"
When we wrote the first class-based tracer
function decorator in the previous article, we simply assumed that it should also apply to any method - the decorated method should work the same, and the instance self
parameters should be Contained directly in *args
front of. But the only practical drawback to this assumption is that it is flat-out wrong! When applied to a class method, tracer
the first version of does not work because self
it is an instance of the decorator class, and the instance of the main class being decorated is not included *args
. This is true in both python3.X and Python2.X.
Now we can see this in the context of actual working code. Assume the class-based tracking decorator is as follows:
class tracer:
def __init__(self, func):
self.calls = 0
self.func = func
def __call__(self, *args, **kwargs):
self.calls += 1
print('call %s to %s' % (self.calls, self.func.__name__))
return self.func(*args, **kwargs)
@tracer
def spam(a, b, c):
print(a + b + c)
We can get the following output:
However, the decoration of the class level method fails:
class Person:
def __init__(self, name, pay):
self.name = name
self.pay = pay
@tracer
def giveRaise(self, percent):
self.pay *= (1.0 + percent)
@tracer
def lastName(self):
return self.name.split()[-1]
We can get the following output:
The root of the problem here is that the parameter of tracer
the class __call__
method self
is an tracer
instance, or an Person
instance? In fact, we need both: tracer
to record the decorator state and Person
to point to the original method. In fact, self
it must be tracer
an object that provides access to tracer
state information (its calls
sum func
); this is true whether decorating a simple function or decorating a method.
Unfortunately, when we __call__
rebind the decorated method name to a class instance object, Python only self
passes tracer
the instance; it doesn't pass the body in the argument list at all Person
. Furthermore, since tracer 不知道我们要利用方法调用处理的
has no information about the Person` instance, there is no way to create a bound method with the instance and dispatch the call correctly. This isn't a bug, but it's a very noteworthy detail.
Finally, the previous list ended up passing too few parameters to the decorated method and resulted in an error. Verify this by adding a line to the decorator __call__
method that prints all arguments - as we can see, self
there is an tracer
instance, while Person
the instance is completely missing:
as mentioned earlier, this happens because only Python only passes the implicit body instance when a method name is bound to a simple function self
; when it is an instance of a callable class, it passes self
an instance of that class. Technically, Python creates a bound method object that contains the principal instance only if the method is a simple function, rather than a callable instance of another class.
References:
[1] Mark Lutz. Python Learning Manual[M]. Machinery Industry Press, 2018.