python子类计数模块

Python subclass counter

我有这个python代码。结果是x的TopTest: attr1=0, attr2=1,很好,但结果是y的SubTest: attr1=2, attr2=3,我不太明白。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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)

您可以显式地访问和使用TopTest.count,您的子类将坚持这种明确性。您可能会考虑使用type(self).count,然后每个实例将使用自己的类的变量,该变量可以在每个子类中成为不同的变量。

要使子类具有自己的类变量,只需在其定义中添加一个count = 0

1
2
class SubTest(TopTest):
    count = 0


你很接近——当你查找一个对象的属性时,你不一定要查找一个属于对象本身的属性。相反,查找遵循python的方法解析顺序,这…不是很简单。然而,在这种情况下,只执行三个步骤:

  • 检查Y是否具有名为count的属性。
  • 它没有,所以检查它的类SubTest是否具有名为count的属性。
  • 它没有,所以检查它的父级TopTest是否有一个名为count的属性。是的,所以访问。
  • 简单地说,当您访问Y.count时,实际上是访问TopTest.count

    还有一个事实是,您的代码中有一个bug——子测试增加了TopTest的计数,而不是它自己的计数。你问题的标题是"子类计数器",但是由于你在计算__init__(),我假设你正在寻找一个实例计数器(为了计算子类,我相当确定你需要使用元类)。对于包含对象类的属性self.__class__来说,这是一个完美的用例!为了使用它:

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

    这样,当您调用SubTest()时,SubTest.count将递增,而不是TopTest.count


    您似乎希望为TopTest的每个子类的每个实例保留一个计数器,但您不希望通过为每个子类声明一个新的count类变量来重复自己的操作。您可以使用元类来实现这一点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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

    1
    2
    3
    4
    5
    6
    7
    8
    >>> x, y = TopTest(), SubTest()
    >>> x.attr2
    1
    >>> y.attr2
    1
    >>> y2 = SubTest()
    >>> y2.attr2
    3

    然而,元类可能会令人困惑,只有在它们确实是必要的时候才应该使用。在您的特定情况下,只需为TopTest的每个子类重新定义count类属性就简单多了:

    1
    2
    class SubTest(TopTest):
        count = 0

    如果您希望每个类隐式地拥有它自己的类变量,那么可以使用元类来添加该变量。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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


    当创建SubTest的新实例时,调用TopTest.__init__(),因为SubTest继承了TopTest.__init__(),它使TopTest.count增加了2。

    由于SubTest从来没有定义类级的count变量,所以当执行SubTest.count时,python会回滚并使用TopTest.count

    这种行为可以通过重新定义count局部到SubTest来解决。

    1
    2
    class SubTest(TopTest):
        count = 0