I have a problem with the implementation of a decorator applied at this metaclass decorator I wrote:
def decorateAll(decorator):
class MetaClassDecorator(type):
def __new__(meta, classname, supers, classdict):
for name, elem in classdict.items():
if type(elem) is FunctionType:
classdict[name] = decorator(classdict[name])
return type.__new__(meta, classname, supers, classdict)
return MetaClassDecorator
This is the class in which I have used the metaclass:
class Account(object, metaclass=decorateAll(Counter)):
def __init__(self, initial_amount):
self.amount = initial_amount
def withdraw(self, towithdraw):
self.amount -= towithdraw
def deposit(self, todeposit):
self.amount += todeposit
def balance(self):
return self.amount
Everything seems to work great when I pass to the decorator metaclass a decorator implemented like this:
def Counter(fun):
fun.count = 0
def wrapper(*args):
fun.count += 1
print("{0} Executed {1} times".format(fun.__name__, fun.count))
return fun(*args)
return wrapper
But when I use a decorator implemented in this way:
class Counter():
def __init__(self, fun):
self.fun = fun
self.count = 0
def __call__(self, *args, **kwargs):
print("args:", self, *args, **kwargs)
self.count += 1
print("{0} Executed {1} times".format(self.fun.__name__, self.count))
return self.fun(*args, **kwargs)
I got this error:
line 32, in __call__
return self.fun(*args, **kwargs)
TypeError: __init__() missing 1 required positional argument: 'initial_amount'
Why? Using the two decorators implementation with others function doesn't show me problems. I think that the problem is tied to the fact that the methods to which I'm trying to decorate are class methods. Am I missing something?
A metaclass in Python is a class of a class that defines how a class behaves. A class is itself an instance of a metaclass. A class in Python defines how the instance of the class will behave. In order to understand metaclasses well, one needs to have prior experience working with Python classes.
In object-oriented programming, a metaclass is a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances. Not all object-oriented programming languages support metaclasses.
To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function. Here, when we decorate, multiply_together with integer_check, the integer function gets called.
In order to set metaclass of a class, we use the __metaclass__ attribute. Metaclasses are used at the time the class is defined, so setting it explicitly after the class definition has no effect.
You need to implement Counter
as a callable descriptor. When __get__
is executed on the descriptor, you simulate binding the descriptor to the instance which is passed to it. Plus storing the count on a per method/object basis.
This code:
import collections
import functools
import types
def decorateAll(decorator):
class MetaClassDecorator(type):
def __new__(meta, classname, supers, classdict):
for name, elem in classdict.items():
if type(elem) is types.FunctionType:
classdict[name] = decorator(classdict[name])
return type.__new__(meta, classname, supers, classdict)
return MetaClassDecorator
class Counter(object):
def __init__(self, fun):
self.fun = fun
self.cache = {None: self}
self.count = collections.defaultdict(int)
def __get__(self, obj, cls=None):
if obj is None:
return self
try:
return self.cache[obj]
except KeyError:
pass
print('Binding {} and {}'.format(self.fun, obj))
cex = self.cache[obj] = functools.partial(self.__call__, obj)
return cex
def __call__(self, obj, *args, **kwargs):
print("args:", obj, *args, **kwargs)
self.count[obj] += 1
print("{0} Exec {1} times".format(self.fun.__name__, self.count[obj]))
return self.fun(obj, *args, **kwargs)
class Account(object, metaclass=decorateAll(Counter)):
def __init__(self, initial_amount):
self.amount = initial_amount
def withdraw(self, towithdraw):
self.amount -= towithdraw
def deposit(self, todeposit):
self.amount += todeposit
def balance(self):
return self.amount
a = Account(33.5)
print(a.balance())
Produces the following output:
Binding <function Account.__init__ at 0x000002250BCD8B70> and <__main__.Account object at 0x000002250BCE8BE0>
args: <__main__.Account object at 0x000002250BCE8BE0> 33.5
__init__ Exec 1 times
Binding <function Account.balance at 0x000002250BCD8D90> and <__main__.Account object at 0x000002250BCE8BE0>
args: <__main__.Account object at 0x000002250BCE8BE0>
balance Exec 1 times
33.5
Which calls the __call__
method of the descriptor, stores the count on a per method by simulating a binding creating an object of type functools.partial
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With