2017-09-15 72 views
7

试图理解python中的oop我进入这种困惑我的情况,我无法找到令人满意的解释... 我正在构建一个Countable类,它有一个计数器属性,用于统计该类的多少个实例已被初始化。我想要在给定类的子类(或子类)初始化时增加此计数器。下面是我的实现:python中的类变量的继承

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但创建一个对象后,这是一个不同?

+0

我无法重现你的输出。我的'B2s'输出总是和'Bs'一样。 –

+0

我用这个问题的简单例子编辑了你的问题。这是一个有趣的问题,希望有人能对这个过程有所了解 – Vinny

+0

@Rawing你是对的,我粘贴另一个例子的输出......现在我修复它! –

回答

7

您的代码存在的问题是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 + 1cls还没有自己的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 __new__(cls, name, bases, attrs): 
     new_class = super(CountableMeta, cls).__new__(cls, name, bases, attrs) 
     new_class.counter = 0 # each class gets its own counter 
     return new_class 

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 
+1

我只是在Python3.6 +中添加的,也可以使用['__init_subclass __()hook'](https://docs.python.org/3/ reference/datamodel.html#customizing-class-creation)(为每个子类添加一个“counter”属性)。 – plamut

+0

或者(在Python 2.7.x +和3.x中)使用类装饰器。 –

+0

然而,在第一个对象创建完成后('a = A()'),我得到了'id(Countable.counter)== id(A.counter)'。为什么会发生这种情况,如果赋值为类A创建一个新的类变量? –