简体   繁体   English

python中类变量的继承

[英]Inheritance of class variables in python

Trying to understand oop in python I came into this situation that puzzles me, and I wasn't able to find a satisfactory explanation... I was building a Countable class, which has a counter attribute that counts how many instances of the class have been initialized. 试图在python中理解oop我遇到了困扰我的情况,我无法找到令人满意的解释......我正在构建一个Countable类,它有一个计数器属性,可以计算该类有多少个实例已初始化。 I want this counter to be increased also when a subclass (or subsubclass) of the given class is initialized. 我希望在初始化给定类的子类(或子类)时也增加此计数器。 Here is my implementation: 这是我的实现:

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()

where count(cls) is there for debugging purposes, and later i write it down. 其中count(cls)用于调试目的,后来我把它写下来。

Now, let's have some subclasses of this: 现在,让我们有一些这样的子类:

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))

when I run a code like the following: 当我运行如下代码时:

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

I obtain the following output, which looks strange to me: 我获得了以下输出,这对我来说很奇怪:

@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

Why at the beginning both the counter of A and B is incrementing, despite I am calling only A() ? 为什么在开始时A和B的计数器都在递增,尽管我只调用A() And why after the first time I call B() it behaves like expected? 为什么在我第一次调用B()它表现得像预期的那样?

I already found out that to have a behavior like I want it is sufficient to add counter = 0 at each subclass, but I was not able to find an explanation of why it behaves like that.... Thank you! 我已经发现有一个像我想要的行为就足以在每个子类中添加counter = 0 ,但我无法找到它为什么表现的原因....谢谢!


I added few debug prints, and for simplicity limited class creation to two. 我添加了一些调试打印,为简单起见,将类创建限制为两个。 This is pretty strange: 这很奇怪:

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

How come when B() is not initialized yet, it points to the same variable as A.counter but after creating single object it is a different one? 为什么B()还没有被初始化,它指向与A.counter相同的变量但是在创建单个对象之后它是另一个?

The problem with your code is that subclasses of Countable don't have their own counter attribute. 您的代码的问题是Countable子类没有自己的counter属性。 They're merely inheriting it from Countable , so when Countable 's counter changes, it looks like the child class's counter changes as well. 它们只是从Countable继承它,所以当Countablecounter改变时,看起来子类的counter也会改变。

Minimal example: 最小的例子:

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

If A had its own counter attribute, everything would work as expected: 如果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

But if all of these classes share the same counter , why do we see different numbers in the output? 但是如果所有这些类共享同一个counter ,为什么我们在输出中看到不同的数字? That's because you actually add the counter attribute to the child class later, with this code: 那是因为您实际上稍后将counter属性添加到子类,使用以下代码:

cls.counter += 1

This is equivalent to cls.counter = cls.counter + 1 . 这相当于cls.counter = cls.counter + 1 However, it's important to understand what cls.counter refers to. 但是,理解cls.counter所指的是很重要的。 In cls.counter + 1 , cls doesn't have its own counter attribute yet, so this actually gives you the parent class's counter . cls.counter + 1cls还没有自己的counter属性,所以这实际上为你提供了父类的counter Then that value is incremented, and cls.counter = ... adds a counter attribute to the child class that hasn't existed until now. 然后该值递增,并且cls.counter = ...将一个counter属性添加到直到现在才存在的子类。 It's essentially equivalent to writing cls.counter = cls.__base__.counter + 1 . 它基本上等同于编写cls.counter = cls.__base__.counter + 1 You can see this in action here: 你可以在这里看到这个:

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

So what's the solution to this problem? 那么这个问题的解决方案是什么? You need a metaclass . 你需要一个元类 This gives you the possibility to give each Countable subclass its own counter attribute when it is created: 这使您可以在创建时为每个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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM