I've written a metaclass which counts the number of instances for each of its children class.
So my meta has a dict like {classname: number_of_instances}
.
This is the implementation:
class MetaCounter(type):
_counter = {}
def __new__(cls, name, bases, dict):
cls._counter[name] = 0
return super().__new__(cls, name, bases, dict)
def __call__(cls, *args, **kwargs):
cls._counter[cls.__name__] += 1
print(f'Instantiated {cls._counter[cls.__name__]} objects of class {cls}!')
return super().__call__(*args, **kwargs)
class Foo(metaclass=MetaCounter):
pass
class Bar(metaclass=MetaCounter):
pass
x = Foo()
y = Foo()
z = Foo()
a = Bar()
b = Bar()
print(MetaCounter._counter)
# {'Foo': 3, 'Bar': 2} --> OK!
print(Foo._counter)
# {'Foo': 3, 'Bar': 2} --> I'd like it printed just "3"
print(Bar._counter)
# {'Foo': 3, 'Bar': 2} --> I'd like it printed just "2"
It works fine but I want to add a level of information hiding. That is, classes whom metaclass is MetaCounter should not have the counter of instances of other classes with the same meta. They should just have the information regarding the number of their instances.
So MetaCounter._counter
should display the entire dict, but Foo._counter
(let Foo be a class with MetaCounter as its metaclass) should just return the number of Foo instances.
Is there a way to achieve this?
I've tried to override __getattribute__
but it ended in messing around with the counting logic.
I've also tried to put _counter
as attribute in the dict
parameter of the __new__
method, but then it became also an instance member and I didn't want so.
I do not know enough metaclasses to say if it is a bad idea to do what you are trying but it is possible.
You can make MetaCounter
just keep references to its "metaclassed" classes and add a specific instance counter to each one by updating the dict
in MetaCounter.__new__
method:
class MetaCounter(type):
_classes = []
def __new__(cls, name, bases, dict):
dict.update(_counter=0)
metaclassed = super().__new__(cls, name, bases, dict)
cls._classes.append(metaclassed)
return metaclassed
Each time a new MetaCounter
class is instantiated, you increment the counter
def __call__(cls, *args, **kwargs):
cls._counter += 1
print(f'Instantiated {cls._counter} objects of class {cls}!')
return super().__call__(*args, **kwargs)
This ensures each MetaCounter
class owns its instance counter and does not know anything about other MetaCounter
classes.
Then, to get all the counters, you can add a _counters
static method to MetaCounter
, which returns the counter for each MetaCounter
class:
@classmethod
def _counters(cls):
return {klass: klass._counter for klass in cls._classes}
Then you are done:
print(MetaCounter._counters()) # {<class '__main__.Foo'>: 3, <class '__main__.Bar'>: 2}
print(Foo._counter) # 3
print(Bar._counter) # 2