web-dev-qa-db-ja.com

Python、継承されたクラスメソッドのオーバーライド

FieldBackgroundの2つのクラスがあります。彼らはこのように少し見えます:

_class Field( object ):
    def __init__( self, a, b ):
        self.a = a
        self.b = b
        self.field = self.buildField()

    def buildField( self ):
        field = [0,0,0]
        return field

class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__( a, b )
        self.field = self.buildField( c )

    def buildField( self, c ):
        field = [c]
        return field

a, b, c = 0, 1, 2
background = Background( a, b, c )
_

このエラーは、フィールドのbuildField()を指しています:

_"TypeError: buildField() takes exactly 2 arguments (1 given)."
_

Background init()が最初に呼び出されることを期待していました。 "a、b"をFields init()に渡すには、Fieldでaとbを割り当て、次に3つの0を含むリストをfieldに割り当てます。次に、Backgroundのinit()を続行し、独自のbuildField()を呼び出して、cを含むリストでself.fieldをオーバーライドします。

私はsuper()を完全には理解していないようですが、Webやこの周辺で同様の継承の問題を見た後、私の問題の解決策を見つけることができませんでした。

クラスが継承されたメソッドをオーバーライドできるc ++のような動作を期待していました。どうすればこれまたは同様のことを達成できますか?.

これに関連して見つかったほとんどの問題は、二重アンダースコアを使用している人々でした。スーパーでの継承の私の経験は、継承されたクラスinit()を使用して、スーパークラスに異なる変数を渡すだけです。何も上書きする必要はありません。

43
andb.

C++の観点から見ると、ここには2つの誤解があるかもしれません。

まず、同じ名前で異なるシグネチャを持つメソッドは、C++のようにオーバーロードしません。 Backgroundオブジェクトの1つが引数なしでbuildFieldを呼び出そうとした場合、Fieldからの元のバージョンは呼び出されません。完全に非表示になっています。

2番目の問題は、スーパークラスで定義されたメソッドがbuildFieldを呼び出すと、サブクラスバージョンが呼び出されることです。 Pythonでは、C++ virtualメソッドのように、allメソッドが動的にバインドされます。

フィールドの__init__は、引数を取らないbuildFieldメソッドを持つオブジェクトを処理することを期待されていました。 1つの引数を取るbuildFieldメソッドを持つオブジェクトでメソッドを使用しました。

superの利点は、オブジェクトのタイプを変更しないため、スーパークラスのメソッドが呼び出す可能性のあるメソッドのシグネチャを変更しないことです。

21
Ian Clelland

Background init()が呼び出されることを期待していました。 "a、b"をFields init()に渡し、Fieldでaとbを割り当てます

ここまでは順調ですね。

次に、フィールドに3つの0を含むリストを割り当てます。

あ。ここでエラーが発生します。

    self.field = self.buildField()

この行はField.__init__内で発生しますが、selfBackgroundのインスタンスです。したがって、self.buildFieldは、Backgroundではなく、buildFieldFieldメソッドを検出します。

Background.buildFieldは1ではなく2つの引数を予期するため、

self.field = self.buildField()

エラーを発生させます。


それでは、どのようにPythonにFieldの代わりにbuildFieldBackgroundメソッドを呼び出すように指示しますか?

name mangling (二重アンダースコアで属性に名前を付ける)の目的は、この正確な問題を解決することです。

class Field(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.field = self.__buildField()

    def __buildField(self):
        field = [0,0,0]
        return field

class Background(Field):
    def __init__(self, a, b, c):
        super(Background, self).__init__(a, b)
        self.field = self.__buildField(c)

    def __buildField(self, c):
        field = [c]
        return field

a, b, c = 0, 1, 2
background = Background(a, b, c)

メソッド名__buildFieldField内で_Field__buildFieldに「マングル」されているため、Field.__init__内では、

    self.field = self.__buildField()

self._Field__buildField()を呼び出します。これは、Field__buildFieldメソッドです。同様に、

    self.field = self.__buildField(c)

Background.__init__の内部でBackground__buildFieldメソッドを呼び出します。

41
unutbu

Background init()が呼び出されることを期待していました

実際、Background init()が呼び出されています。

しかしバックグラウンドクラスを見てください。

class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__( a, b )
        self.field = self.buildField( c )

したがって、__init__の最初のステートメントは、super class(Field) initメソッドを呼び出して、selfを引数として渡します。このselfは、実際にはBackground class ..の参照です。

Fieldクラスで:-

class Field( object ):
    def __init__( self, a, b ):

        print self.__class__  // Prints `<class '__main__.Background'>`
        self.a = a
        self.b = b
        self.field = self.buildField()

buildField()メソッドは、実際にBackgroundクラスのメソッドを呼び出しています。これは、ここのselfBackgroundクラスのインスタンスであるためです(self.__class____init__メソッドでField classメソッドを印刷してみてください) ).. Backgroundクラスから__init__メソッドを呼び出して渡したとき。

だからエラーが発生します。

エラー「TypeError:buildField()は、正確に2つの引数(1つ指定)を取ります。

値を渡していないので、渡された値のみが暗黙のselfです。

16
Rohit Jain

super(Background, self).__init__( a, b )は以下を呼び出します:

_def __init__( self, a, b ):
    self.a = a
    self.b = b
    self.field = self.buildField()
_

Fieldにあります。ただし、ここでselfbackgroundインスタンスを指し、self.buildField()は実際にBackgroundbuildField()を呼び出しているため、そのエラーが発生します。


あなたのコードは次のように書かれるべきだと思われます:

_class Field( object ):
    def __init__( self, a, b ):
        self.a = a
        self.b = b
        self.field = Field.buildField()

    @classmethod
    def buildField(cls):
        field = [0,0,0]
        return field

class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__(a, b)
        self.field = Background.buildField(c)

    @classmethod
    def buildField(cls,c):
        field = [c]
        return field

a, b, c = 0, 1, 2
background = Background( a, b, c )
_

ベースコンストラクターの終了を許可できない場合、デザインに欠陥があることを通知します。

したがって、 classmethod decorator または staticmethod を使用して、クラスに属するbuildField()を分離することをお勧めします。 コンストラクターでこれらのメソッドを呼び出す必要がある場合.

ただし、基本クラスコンストラクターが内部からインスタンスメソッドを呼び出さない場合、この基本クラスのメソッドを安全に上書きできます。

3
K Z

Overridingについて話しますが、私には聞こえますchaining constructors or (methods)

また、overwriting propertiesのようにも聞こえます:

説明させてください:

  • フィールドという名前のプロパティは、[0,0,0]として初期化されます。 @propertyデコレータはより適切に見えます。

  • 次に、Background class over-writeこのプロパティ。


迅速で汚れたソリューション

私はあなたのビジネスロジックを知りませんが、時々スーパークラスの__init__メソッドをバイパスすることで、より多くの制御ができました:

#!/usr/bin/env python

class Field( object ):
    def __init__( self, a, b ):
        self.a = a
        self.b = b
        self.field = self.buildField()

    def buildField( self ):
        field = [0,0,0]
        return field

class Background( Field ):
    def __init__( self, a, b, c ):
        # super(Background, self).__init__( a, b )
        # Unfortunately you should repeat or move initializing a and b
        # properties here
        self.a = a
        self.b = b

        self.field = self.buildField( c )

    def buildField( self, c ):
        # You can access super class methods 
        assert super(Background, self).buildField() == [0,0,0]
        field = [c]
        return field


a, b, c = 0, 1, 2
bg = Background(a,b,c)
assert bg.field == [2]

プロパティを使用する

より簡潔な構文があります。

#!/usr/bin/env python

class Field( object ):

    @property
    def field(self):
        return [0,0,0]


    def __init__( self, a, b ):
        self.a = a
        self.b = b


class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__( a, b )
        self.c = c

        assert  (self.a, self.b, self.c) == (0,1,2)  # We assigned a and b in 
                                                   # super class's __init__ method

        assert super(Background, self).field == [0,0,0]
        assert self.field == [2]

    @property
    def field(self):
        return [self.c]


a, b, c = 0, 1, 2

background = Background( a, b, c )

print background.field
1
guneysus