2017-09-15 118 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創建一個新的類變量? –