【问题标题】:Inheritance of class variables in pythonpython中类变量的继承
【发布时间】:2018-02-24 12:46:07
【问题描述】:

试图理解 python 中的 oop 我遇到了这种让我困惑的情况,我无法找到令人满意的解释...... 我正在构建一个 Countable 类,它有一个计数器属性,用于计算已初始化的类实例的数量。我希望在初始化给定类的子类(或子子类)时也增加这个计数器。这是我的实现:

class Countable(object):
    counter = 0
    def __new__(cls, *args, **kwargs):
        cls.increment_counter()
        count(cls)
        return object.__new__(cls, *args, **kwargs)

    @classmethod
    def increment_counter(cls):
        cls.counter += 1
        if cls.__base__ is not object:
            cls.__base__.increment_counter()

count(cls) 在哪里用于调试目的,后来我把它写下来。

现在,让我们有一些它的子类:

class A(Countable):
    def __init__(self, a='a'):
        self.a = a

class B(Countable):
    def __init__(self, b='b'):
        self.b = b

class B2(B):
    def __init__(self, b2='b2'):
        self.b2 = b2

def count(cls):
    print('@{:<5}  Countables: {}  As: {}  Bs: {}  B2s: {}'
          ''.format(cls.__name__, Countable.counter, A.counter, B.counter, B2.counter))

当我运行如下代码时:

a = A()
a = A()
a = A()
b = B()
b = B()
a = A()
b2 = B2()
b2 = B2()

我得到以下输出,这对我来说看起来很奇怪:

@A      Countables:  1  As: 1  Bs: 1  B2s: 1
@A      Countables:  2  As: 2  Bs: 2  B2s: 2
@A      Countables:  3  As: 3  Bs: 3  B2s: 3
@B      Countables:  4  As: 3  Bs: 4  B2s: 4
@B      Countables:  5  As: 3  Bs: 5  B2s: 5
@A      Countables:  6  As: 4  Bs: 5  B2s: 5
@B2     Countables:  7  As: 4  Bs: 6  B2s: 6
@B2     Countables:  8  As: 4  Bs: 7  B2s: 7

为什么一开始 A 和 B 的计数器都在递增,尽管我只调用了 A()?为什么在我第一次调用B() 之后它的行为就像预期的那样?

我已经发现要具有我想要的行为,在每个子类中添加 counter = 0 就足够了,但我无法找到解释为什么它会这样......谢谢!


我添加了一些调试打印,并且为简单起见将类创建限制为两个。这很奇怪:

>>> a = A()
<class '__main__.A'> incrementing
increment parent of <class '__main__.A'> as well
<class '__main__.Countable'> incrementing
@A      Counters: 1  As: 1  Bs: 1  B2s: 1
>>> B.counter
1
>>> B.counter is A.counter
True
>>> b = B()
<class '__main__.B'> incrementing
increment parent of <class '__main__.B'> as well
<class '__main__.Countable'> incrementing
@B      Counters: 2  As: 1  Bs: 2  B2s: 2
>>> B.counter is A.counter
False

为什么 B() 还没有初始化,它指向与 A.counter 相同的变量,但在创建单个对象后它是不同的?

【问题讨论】:

  • 我无法重现您的输出。我对B2s 的输出始终与Bs 相同。
  • 我用问题的简化示例编辑了您的问题。这是一个有趣的问题,希望有人能解释一下这个过程
  • @Rawing 你是对的,我粘贴了另一个示例的输出......现在我修复它!
  • 你知道python有__subclasses__,它会给你一个类的子类吗? stackoverflow.com/a/3862957/7432

标签: python python-2.7 inheritance class-variables


【解决方案1】:

您的代码的问题是Countable 的子类没有自己的counter 属性。他们只是从Countable 继承它,所以当Countablecounter 发生变化时,看起来子类的counter 也发生了变化。

小例子:

class Countable:
    counter = 0

class A(Countable):
    pass # A does not have its own counter, it shares Countable's counter

print(Countable.counter) # 0
print(A.counter) # 0

Countable.counter += 1

print(Countable.counter) # 1
print(A.counter) # 1

如果A 有自己的counter 属性,那么一切都会按预期工作:

class Countable:
    counter = 0

class A(Countable):
    counter = 0 # A has its own counter now

print(Countable.counter) # 0
print(A.counter) # 0

Countable.counter += 1

print(Countable.counter) # 1
print(A.counter) # 0

但如果所有这些类共享相同的counter,为什么我们会在输出中看到不同的数字?那是因为您稍后实际上将 counter 属性添加到子类,使用以下代码:

cls.counter += 1

这相当于cls.counter = cls.counter + 1。但是,了解cls.counter 所指的内容很重要。在cls.counter + 1 中,cls 还没有自己的counter 属性,所以这实际上为您提供了父类的counter。然后该值增加,cls.counter = ...counter 属性添加到直到现在还不存在的子类。它本质上相当于写cls.counter = cls.__base__.counter + 1。你可以在这里看到它的实际效果:

class Countable:
    counter = 0

class A(Countable):
    pass

# Does A have its own counter attribute?
print('counter' in A.__dict__) # False

A.counter += 1

# Does A have its own counter attribute now?
print('counter' in A.__dict__) # True

那么解决这个问题的方法是什么?你需要一个metaclass。这使您可以在创建每个 Countable 子类时为其赋予其自己的 counter 属性:

class CountableMeta(type):
    def __init__(cls, name, bases, attrs):
        cls.counter = 0  # each class gets its own counter

class Countable:
    __metaclass__ = CountableMeta

# in python 3 Countable would be defined like this:
#
# class Countable(metaclass=CountableMeta):
#    pass

class A(Countable):
    pass

print(Countable.counter) # 0
print(A.counter) # 0

Countable.counter += 1

print(Countable.counter) # 1
print(A.counter) # 0

【讨论】:

  • 我只想在 Python3.6+ 中添加这一点,也可以将__init_subclass__() hook 用于相同目的(为每个子类添加counter 属性)。
  • 或者(在 Python 2.7.x+ 和 3.x 中)使用类装饰器。
  • 然而,在第一个对象的创建完成后(a = A()),我得到了id(Countable.counter) == id(A.counter)。如果赋值为 A 类创建了一个新的类变量,为什么会发生这种情况??
  • @blue_note 整数是不可变的,因此检查它们的 id 没有多大意义。重要的不是两个类是否共享同一个 int 实例,而是A 是否有自己的属性来影响Countable 的属性。如果 int 是可变的,那么共享相同 int 实例的两个类将是一个问题,但它们不是。有关比较整数 id 的更多信息,另请参阅 this question
  • @blue_note 是的,它 (CPython) 对 -5 到 256 之间的整数执行此操作。
猜你喜欢
  • 2013-02-11
  • 1970-01-01
  • 2012-11-04
  • 1970-01-01
  • 1970-01-01
  • 2021-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多