Python子类计数器

问题描述:

我有这个python代码。结果是TopTest: attr1=0, attr2=1为X这很好,但结果是SubTest: attr1=2, attr2=3 Y,我不太明白。Python子类计数器

基本上,我有一个class属性,它是一个计数器,它运行在__init__ method。当我启动Y时,计数器被设置为2,并且仅在属性被分配之后。我不明白为什么它从2开始。不应该子类复制超类和计数器重新启动在0?

class AttrDisplay: 
    def gatherAttrs(self):   
    attrs = []   
    for key in sorted(self.__dict__):    
     attrs.append('%s=%s' % (key, getattr(self, key)))   
    return ', '.join(attrs) 
    def __repr__(self):   
    return '[%s: %s]' % (self.__class__.__name__, self.gatherAttrs()) 

class TopTest(AttrDisplay): 
    count = 0   
    def __init__(self):    
     self.attr1 = TopTest.count    
     self.attr2 = TopTest.count+1    
     TopTest.count += 2 

class SubTest(TopTest): 
    pass 

X, Y = TopTest(), SubTest()   
print(X)        
print(Y)       

SubTest一个新的实例被创建TopTest.__init__()被称为 - 因为SubTest继承TopTest.__init__() - 这增加两个TopTest.count

而且,由于SubTest从来没有定义一个类级别的count变量,当执行SubTest.count,Python的回落,并使用TopTest.count

此行为可以通过重新定义count本地到SubTest来解决。

class SubTest(TopTest): 
    count = 0 
+0

'你可以通过重新定义里面SubTest'计数解决这个问题 - 但是要修改'TopTest.count '在init中,'SubTest.count'将永远是0 – then0rTh

+0

@ then0rTh正确。我不确定我是否遵守。这不是OP想要的行为吗? –

+0

我以为'SubTest.count'应该计算'SubTest'的实例,但我可能是错的 – then0rTh

您近了 - 当您查找对象的属性时,您不一定查找属于该对象本身的属性。相反,查找遵循Python的method resolution order,这不是完全简单。在这种情况下,然而,仅执行三个步骤:

  1. 检查Y有一个名为count属性。
  2. 它不,所以检查它的类SubTest是否有一个名为count的属性。
  3. 它没有,所以检查其父母TopTest是否有一个名为count的属性。它确实如此,因此访问它。

简而言之,当你访问Y.count你实际*问TopTest.count


还有,你必须在你的代码中的错误的事实 - 而不是它自己的子测试增量TopTest的计数和。你的问题的标题说“子类计数器”,但由于你在__init__(),我假设你正在寻找一个实例计数器(以计数子类我相当确定you'd need to use metaclasses)。对于self.__class__这是一个完美的用例,它是一个包含对象类的属性!当你调用SubTest()

def __init__(self): 
    self.attr1 = self.__class__.count 
    self.attr2 = self.__class__.count + 1    
    self.__class__.count += 2 

使用,SubTest.count将会增加,而不是TopTest.count:为了使用它。

+0

不是'cls = type(self)'better? – then0rTh

+1

@ then0rTh你说得对,我只是习惯于使用'super()',哈哈。在这种情况下'super()'实际上会获得'TopTest'!我继续前进,并将其改为'__class__',其工作方式与type()相同(并且还支持旧式类)。 – obskyr

您可以访问并明确使用TopTest.count,并且您的子类将坚持这种明确性。您可能需要考虑使用type(self).count,那么每个实例将使用自己的类的变量,该变量可以在每个子类中变成不同的变量。

为了让你的子类有它自己的类变量,只需添加一个count = 0它的定义:

class SubTest(TopTest): 
    count = 0 
+1

'self.count + = 2'会创建实例变量,使类变量保持原样 – then0rTh

+0

对,我的错误。我要解决它。 – Alfe

+1

_我知道我只批评,但:_为了避免使用双下划线,你可以使用'count = [0]',但_应该使用'type(self)'。对于已经有解决方案的问题,1元素列表只是丑陋的解决方法。 – then0rTh

如果你希望每个班有它自己的类变量含蓄,您可以使用元类来加入这个变量。

class MetaCount(type): 
    def __new__(cls, name, bases, attrs): 
     new_cls = super(MetaCount, cls).__new__(cls, name, bases, attrs) 
     new_cls.count = 0 
     return new_cls 

class Parent(metaclass=MetaCount): 
    def __init__(self): 
     self.attr1 = self.count    
     self.attr2 = self.count + 1    
     type(self).count += 2 # self.count += 2 creates an *instance* variable 

class Child(Parent): 
    pass 


p, c = Parent(), Child()   
print(p.count) # 2       
print(c.count) # 2 
+0

此代码在Python 3中失败。[这是因为Python 3改变了指定元类的方式](https://*.com/questions/39013249/metaclass-in-python3-5)。您需要在Parent中执行'Parent(metaclass = MetaCount):'而不是'__metaclass__ = MetaCount'。 –

+0

@Christian更新,谢谢,我的机器运行2.7,我懒得REPL –

它看起来像你想保留为TopTest每个子类的每个实例的计数器,但你不想被宣布为每个子类一个新的count类变量重复自己。你可以做到这一点使用一元类:

class TestMeta(type): 
    def __new__(cls, name, bases, attrs): 
     new_class = super().__new__(cls, name, bases, attrs) 
     new_class.count = 0 
     return new_class 

class TopTest(AttrDisplay, metaclass=TestMeta): 
    def __init__(self): 
     self.attr1 = self.count 
     self.attr2 = self.count + 1 
     self.increment_count(2) 
    @classmethod 
    def increment_count(cls, val): 
     cls.count += val 

class SubTest(TopTest): 
    pass 

xy对象的count属性现在应该是独立的,和TopTestSubTest后续实例将递增count

>>> x, y = TopTest(), SubTest() 
>>> x.attr2 
1 
>>> y.attr2 
1 
>>> y2 = SubTest() 
>>> y2.attr2 
3 

然而,元类可能会引起混淆,应该只在真正需要时才使用。在您的特定情况下,它就会简单得多刚刚重新定义了count类属性为TopTest每个子类:

class SubTest(TopTest): 
    count = 0