【问题标题】:Python subclass counterPython 子类计数器
【发布时间】:2017-11-24 12:28:48
【问题描述】:

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

基本上,我有一个类属性,它是一个计数器,它在__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)                         

【问题讨论】:

    标签: python counter subclass init


    【解决方案1】:

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

    要让您的子类拥有自己的类变量,只需在其定义中添加count = 0

    class SubTest(TopTest):
        count = 0
    

    【讨论】:

    • self.count += 2 将创建实例变量,保持类变量不变
    • 对,我的错。我会修复它。
    • 我知道我只会批评,但是:为了避免使用双下划线,您可以使用count = [0],但应该 使用type(self)。对于已经有解决方案的问题,1 元素列表只是丑陋的解决方法。
    • 我喜欢这种建设性的批评 ;-) 并再次删除了参考资料。显式使用类变量可能比使用穷人的引用更具 Pythonic。
    【解决方案2】:

    您已经接近了 - 当您查找对象的属性时,您不一定要查找属于对象本身的属性。相反,查找遵循 Python 的 method resolution order,这...并不完全简单。但是,在这种情况下,只执行三个步骤:

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

    简单地说,当你访问Y.count时,你实际上是在访问TopTest.count


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

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

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

    【讨论】:

    • cls = type(self) 不是更好吗?
    • @then0rTh 你说得对,我只是习惯使用super(),哈哈。在这种情况下,super() 实际上会得到TopTest!我已经把它改成了__class__,它和type() 一样好用(并且还支持旧式类)。
    【解决方案3】:

    看起来您希望为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
    

    但是,元类可能会造成混淆,只有在真正需要时才应使用它们。在您的特定情况下,只需为 TopTest 的每个子类重新定义 count 类属性会简单得多:

    class SubTest(TopTest):
        count = 0
    

    【讨论】:

      【解决方案4】:

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

      由于SubTest 从未定义类级别的count 变量,所以当SubTest.count 被执行时,Python 回退并使用TopTest.count

      可以通过将 count 本地重新定义为 SubTest 来修复此行为。

      class SubTest(TopTest):
          count = 0
      

      【讨论】:

      • You can fix this by redefining count inside SubTest - 但你在 init 中修改 TopTest.countSubTest.count 将永远为 0
      • @then0rTh 对。我不确定我是否跟随。这不是 OP 想要的行为吗?
      • 我认为SubTest.count 应该计算SubTest 的实例,但我可能错了
      【解决方案5】:

      如果您希望每个类都隐式拥有自己的类变量,您可以使用元类来添加此变量。

      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  
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-07
      • 1970-01-01
      • 2015-03-12
      • 1970-01-01
      相关资源
      最近更新 更多