2017-09-15 22 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の両方のカウンタがインクリメントしています019277?そして、なぜ私は初めてB()と呼ぶのですか?

私は既に、それぞれのサブクラスにcounter = 0を追加するだけで十分であることを知っていましたが、そのような振る舞いについての説明は見つかりませんでした。ありがとうございます!


私はいくつかのデバッグプリントを追加し、2つのシンプル限られたクラスを作成するため。これはかなり奇妙です:B()がまだ初期化されていない場合、それはA.counterと同じ変数を指すが、単一のオブジェクトを作成した後、それは別のものである

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

どのように来ますか?

+0

私はあなたの出力を再現することはできません。 'B2s 'の私の出力は常に' Bs'と同じです。 –

+0

あなたの質問は簡単な例で編集しました。これは面白い質問です、誰かがプロセス上のいくつかの光を放つことを願っています – Vinny

+0

@ラーイングあなたは正しいです、私は別の例の出力を貼った...今私はそれを修正! –

答えて

7

コードの問題点は、Countableのサブクラスには、独自のcounter属性がないことです。彼らはCountableから継承しているだけなので、Countablecounterが変更されると、子クラスのcounterも同様に変化します。

最小限の例では:Aは独自の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 

、すべてが期待どおりに動作します:これらのクラスのすべてが同じ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 

しかし

、なぜ我々は見ています出力に異なる数字がありますか?これは cls.counter = cls.counter + 1と同等です

cls.counter += 1 

:あなたが実際にこのコードで、後の子クラスにcounter属性を追加するためです。しかし、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 __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の新しいクラス変数が作成されると、これはなぜ起こりますか? –

関連する問題