计算 Python 中使用元类的实例数

Count number of instances using a metaclass in Python

我写了一个 metaclass,它计算每个子 class 的实例数。 所以我的 meta 有一个像 {classname: number_of_instances}.

这样的 dict

这是实现:

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"

它工作正常,但我想添加一个信息隐藏级别。也就是说,metaclassMetaCounter 的 classes 不应该有其他 [=53] 实例的计数器=]es 具有相同的元。他们应该只知道有关 他们的 实例数量的信息。 所以 MetaCounter._counter 应该显示整个 dict,但是 Foo._counter(设 Foo 是 class 和 MetaCounter 作为它的 metaclass) 应该只是 return Foo 的数量实例。

有办法实现吗?

我试图重写 __getattribute__,但结果却搞乱了计数逻辑。

我也试过将_counter作为属性放在__new__方法的dict参数中,但后来它也变成了一个实例成员,我不想所以。

我对元class的了解还不够多,不能说做你正在尝试的事情是否是个坏主意,但这是可能的。

您可以让 MetaCounter 只保留对其“元classed”classes 的引用,并通过更新 dict 为每个实例添加一个特定的实例计数器在 MetaCounter.__new__ 方法中:

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

每次实例化新的 MetaCounter class 时,您都会递增计数器

    def __call__(cls, *args, **kwargs):
        cls._counter += 1
        print(f'Instantiated {cls._counter} objects of class {cls}!')
        return super().__call__(*args, **kwargs)

这确保每个 MetaCounter class 拥有自己的实例计数器并且不知道其他 MetaCounter classes.

然后,要获得所有计数器,您可以向 MetaCounter 添加一个 _counters 静态方法,其中 returns 每个 MetaCounter class 的计数器]:

    @classmethod
    def _counters(cls):
        return {klass: klass._counter for klass in cls._classes}

那么你就完成了:

print(MetaCounter._counters())  # {<class '__main__.Foo'>: 3, <class '__main__.Bar'>: 2}
print(Foo._counter)  # 3
print(Bar._counter)  # 2

这是一个工作示例,使用 描述符 根据 实例是否访问它来修改 _counter 属性的行为(class)或 class(本例中的 metaclass)

class descr:

    def __init__(self):
        self.counter = {}
        self.previous_counter_value = {}

    def __get__(self, instance, cls):
        if instance:
            return self.counter[instance]
        else:
            return self.counter

    def __set__(self, instance, value):
        self.counter[instance] = value
    

class MetaCounter(type):
    
    _counter = descr()

    def __new__(cls, name, bases, dict):
        new_class = super().__new__(cls, name, bases, dict)
        cls._counter[new_class] = 0
        return new_class


    def __call__(cls, *args, **kwargs):
        cls._counter += 1
        print(f'Instantiated {cls._counter} objects of class {cls}!')
        return super().__call__(*args, **kwargs)