Pythonでの__init__の継承とオーバーライド


129

私は「Dive Into Python」を読んでいて、クラスの章でこの例を示しています:

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

その後、作成者は、__init__メソッドをオーバーライドする場合は__init__、正しいパラメーターを使用して親を明示的に呼び出す必要があると述べています。

  1. そのFileInfoクラスに複数の祖先クラスがある場合はどうなりますか?
    • すべての祖先クラスの__init__メソッドを明示的に呼び出す必要がありますか?
  2. また、オーバーライドしたい他のメソッドに対してこれを行う必要がありますか?

3
オーバーロードはオーバーライドとは別の概念であることに注意してください。
Dana the Sane

回答:


158

この本は、サブクラスからスーパークラスへの呼び出しに関して少し古くなっています。また、組み込みクラスのサブクラス化に関しては少し古くなっています。

最近はこんな感じです:

class FileInfo(dict):
    """store file metadata"""
    def __init__(self, filename=None):
        super(FileInfo, self).__init__()
        self["name"] = filename

次の点に注意してください。

  1. 私たちは、直接組み込みクラス、サブクラス化するようなことができdictlisttuple、など

  2. このsuper関数は、このクラスのスーパークラスを追跡し、それらのスーパークラスの関数を適切に呼び出します。


5
より良い本/チュートリアルを探すべきですか?
リロール

2
多重継承の場合、super()はあなたに代わってそれらをすべて追跡しますか?
ダナ

実際にはdict .__ init __(self)ですが、何も問題はありません。super(...)呼び出しは、より一貫した構文を提供するだけです。(多重継承でどのように機能するかはわかりませんが、スーパークラスのinitが1つだけ見つかるかもしれません)
David Z

4
super()の目的は、多重継承を処理することです。欠点は、実際には多重継承が依然として非常に簡単に壊れることです(< fuhm.net/super-harmfulを参照)。
STH

2
はい。コンストラクター引数を受け取る複数の継承および基本クラスの場合、通常は手動でコンストラクターを呼び出すことに気づきます。
Torsten Marek、

18

継承が必要な各クラスでは、子クラスの開始時に初期化が必要な各クラスのループを実行できます...コピーできる例の方が理解しやすいかもしれません...

class Female_Grandparent:
    def __init__(self):
        self.grandma_name = 'Grandma'

class Male_Grandparent:
    def __init__(self):
        self.grandpa_name = 'Grandpa'

class Parent(Female_Grandparent, Male_Grandparent):
    def __init__(self):
        Female_Grandparent.__init__(self)
        Male_Grandparent.__init__(self)

        self.parent_name = 'Parent Class'

class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
#---------------------------------------------------------------------------------------#
        for cls in Parent.__bases__: # This block grabs the classes of the child
             cls.__init__(self)      # class (which is named 'Parent' in this case), 
                                     # and iterates through them, initiating each one.
                                     # The result is that each parent, of each child,
                                     # is automatically handled upon initiation of the 
                                     # dependent class. WOOT WOOT! :D
#---------------------------------------------------------------------------------------#



g = Female_Grandparent()
print g.grandma_name

p = Parent()
print p.grandma_name

child = Child()

print child.grandma_name

2
forループインChild.__init__が必要なようではありません。例からそれを削除しても、子供はまだ「おばあちゃん」と印刷します。祖父母のinitはParentクラスで処理されませんか?
アダム

4
おばあちゃんのinitは、Parentのinitによって既に処理されていると思いませんか。
johk95

15

あなたは本当にしていない持って呼び出すために__init__、基本クラス(ES)の方法をしていますが、通常は必要基底クラスが仕事にクラスメソッドの残りのために必要とされるが、いくつかの重要な初期化を行いますので、それを行うこと。

他の方法の場合、それはあなたの意図に依存します。基本クラスの動作に何かを追加したいだけの場合は、基本クラスのメソッドを独自のコードに追加で呼び出します。基本的に動作を変更したい場合は、基本クラスのメソッドを呼び出さず、すべての機能を派生クラスに直接実装しないでください。


4
技術的な完全性のために、親のinitを呼び出さないようにしようとすると、threading.Threadなどの一部のクラスは巨大なエラーをスローします
David Berger、

5
この「基地のコンストラクタを呼び出す必要がない」という話はすべて非常にイライラします。私の知っている言語で呼び出す必要はありません。それらのすべては、メンバーを初期化しないことにより、ほぼ同じ方法で台無しにされます(またはされません)。基本クラスを初期化しないことを提案することは、非常に多くの点で間違っています。クラスが今初期化を必要としない場合、将来それが必要になります。コンストラクタは、クラス構造/言語構成のインターフェースの一部であり、正しく使用する必要があります。正しい使い方は、派生物のコンストラクタでいつか呼び出すことです。だからそうしなさい。
AndreasT 2013年

2
「基本クラスを初期化しないように提案することは、多くの点で間違っています。」基本クラスを初期化しないことを提案した人はいません。答えを注意深く読んでください。それはすべて意図です。1)基本クラスのinitロジックをそのままにする場合は、派生クラスのinitメソッドをオーバーライドしないでください。2)基本クラスからinitロジックを拡張する場合は、独自のinitメソッドを定義し、それから基本クラスのinitメソッドを呼び出します。3)基本クラスのinitロジックを置き換える場合は、基本クラスからメソッドを呼び出さずに、独自のinitメソッドを定義します。
ウォンバトンファイア2018

4

FileInfoクラスに複数の祖先クラスがある場合は、それらの__init __()関数をすべて呼び出す必要があります。デストラクタである__del __()関数についても同じことを行う必要があります。


2

はい、__init__各親クラスを呼び出す必要があります。両方の親に存在する関数をオーバーライドする場合、関数についても同じことが言えます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.