问题描述
我写了一个 metaclass ,它计算每个子类的实例数量。
所以我的 meta 有一个像{classname: number_of_instances}
这样的 dict 。
这是实现:
class MetaCounter(type):
_counter = {}
def __new__(cls,name,bases,dict):
cls._counter[name] = 0
return super().__new__(cls,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"
它工作正常,但我想添加一个信息隐藏级别。也就是说,元类为 MetaCounter 的类不应具有具有相同元数据的其他类的实例的计数器。他们应该只具有有关其实例数量的信息。
因此MetaCounter._counter
应该显示整个 dict ,但是Foo._counter
(让 Foo 是一个以 MetaCounter 作为其 metaclass )应该只返回 Foo 实例的数量。
有没有办法做到这一点?
我尝试覆盖__getattribute__
,但是最终弄乱了计数逻辑。
我还尝试将_counter
作为属性放在dict
方法的__new__
参数中,但是后来它又成为实例成员,所以我不想这样做。
解决方法
我不知道足够多的元类来说明做您尝试的事情是否是个坏主意,但是有可能。
您可以使MetaCounter
仅保留对其“元分类”类的引用,并通过更新dict
方法中的MetaCounter.__new__
向每个实例添加一个特定的实例计数器:
class MetaCounter(type):
_classes = []
def __new__(cls,name,bases,dict):
dict.update(_counter=0)
metaclassed = super().__new__(cls,dict)
cls._classes.append(metaclassed)
return metaclassed
每次实例化一个新的MetaCounter
类时,您都会增加计数器
def __call__(cls,*args,**kwargs):
cls._counter += 1
print(f'Instantiated {cls._counter} objects of class {cls}!')
return super().__call__(*args,**kwargs)
这确保每个MetaCounter
类都拥有其实例计数器,并且不了解其他MetaCounter
类。
然后,要获取所有计数器,可以向_counters
添加MetaCounter
静态方法,该方法将为每个MetaCounter
类返回计数器:
@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
,
这是一个有效的示例,其中使用描述符来根据{em> instance 是否访问了_counter
属性(该类)或 class (在这种情况下为元类)
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,value):
self.counter[instance] = value
class MetaCounter(type):
_counter = descr()
def __new__(cls,dict):
new_class = super().__new__(cls,dict)
cls._counter[new_class] = 0
return new_class
def __call__(cls,**kwargs)