Inheritance and Overriding __init__ dengan python

129

Saya sedang membaca 'Dive Into Python' dan dalam bab tentang kelas-kelas itu memberikan contoh ini:

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

Penulis kemudian mengatakan bahwa jika Anda ingin mengganti __init__metode, Anda harus secara eksplisit memanggil induk __init__dengan parameter yang benar.

  1. Bagaimana jika FileInfokelas itu memiliki lebih dari satu kelas leluhur?
    • Apakah saya harus secara eksplisit memanggil semua __init__metode kelas leluhur ?
  2. Juga, apakah saya harus melakukan ini pada metode lain yang ingin saya timpa?
liewl
sumber
3
Perhatikan bahwa Overloading adalah konsep terpisah dari Overriding.
Dana the Sane

Jawaban:

158

Buku ini sedikit tertanggal sehubungan dengan panggilan subclass-superclass. Ini juga sedikit tanggal sehubungan dengan subkelas kelas built-in.

Sepertinya ini saat ini:

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

Perhatikan yang berikut ini:

  1. Kita bisa langsung subclass built-in kelas, seperti dict, list, tuple, dll

  2. The supermenangani fungsi melacak superclasses ini kelas dan memanggil fungsi di dalamnya secara tepat.

S.Lott
sumber
5
haruskah saya mencari buku / tutorial yang lebih baik?
liewl
2
Jadi dalam kasus pewarisan berganda, apakah super () melacak semuanya atas nama Anda?
Dana
dict .__ init __ (self), sebenarnya, tetapi tidak ada yang salah dengan itu - panggilan super (...) hanya menyediakan sintaksis yang lebih konsisten. (Saya tidak yakin bagaimana cara kerjanya untuk beberapa pewarisan, saya pikir itu mungkin hanya menemukan satu init superclass )
David Z
4
Maksud dari super () adalah bahwa ia menangani multiple inheritance. Kerugiannya adalah, bahwa dalam praktiknya pewarisan berganda masih sangat mudah rusak (lihat < fuhm.net/super-harmful ).
sth
2
Ya, dalam kasus multiple inheritance dan base class yang mengambil argumen konstruktor, Anda biasanya menemukan diri Anda memanggil konstruktor secara manual.
Torsten Marek
18

Di setiap kelas yang Anda butuhkan untuk mewarisi, Anda dapat menjalankan satu lingkaran dari setiap kelas yang perlu diinisiasi saat memulai kelas anak ... contoh yang dapat disalin mungkin lebih baik dipahami ...

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
Bug kode
sumber
2
Sepertinya tidak Child.__init__diperlukan for for loop . Ketika saya menghapusnya dari contoh saya masih anak mencetak "Nenek". Bukankah kakek nenek tidak ditangani oleh Parentkelas?
Adam
4
Kurasa inpar's sudah ditangani oleh init Induk, bukan?
johk95
15

Anda tidak benar - benar harus memanggil __init__metode kelas dasar (es), tetapi Anda biasanya ingin melakukannya karena kelas dasar akan melakukan beberapa inisialisasi penting di sana yang diperlukan agar metode kelas lainnya bekerja.

Untuk metode lain, itu tergantung pada niat Anda. Jika Anda hanya ingin menambahkan sesuatu ke perilaku kelas dasar Anda akan ingin memanggil metode kelas dasar tambahan ke kode Anda sendiri. Jika Anda ingin secara mendasar mengubah perilaku, Anda mungkin tidak memanggil metode kelas dasar dan mengimplementasikan semua fungsi secara langsung di kelas turunan.

sth
sumber
4
Untuk kelengkapan teknis, beberapa kelas, seperti threading.Thread, akan menimbulkan kesalahan besar jika Anda pernah mencoba menghindari memanggil init induk .
David Berger
5
Saya menemukan semua ini "Anda tidak harus memanggil konstruktor pangkalan" berbicara sangat menjengkelkan. Anda tidak harus menyebutnya dalam bahasa apa pun yang saya tahu. Semuanya akan mengacaukan (atau tidak) dengan cara yang sama, dengan tidak menginisialisasi anggota. Menyarankan untuk tidak menginisialisasi kelas dasar adalah salah dalam banyak hal. Jika suatu kelas tidak membutuhkan inisialisasi sekarang, ia akan membutuhkannya di masa depan. Konstruktor adalah bagian dari antarmuka struktur kelas / konstruk bahasa, dan harus digunakan dengan benar. Benar digunakan adalah untuk memanggilnya kadang-kadang di konstruktor turunan Anda. Jadi lakukanlah.
AndreasT
2
"Menyarankan untuk tidak menginisialisasi kelas dasar adalah salah dalam banyak hal." Tidak ada yang menyarankan untuk tidak menginisialisasi kelas dasar. Baca jawabannya dengan cermat. Ini semua tentang niat. 1) Jika Anda ingin membiarkan logika init kelas dasar seperti apa adanya, Anda tidak menimpa metode init di kelas turunan Anda. 2) Jika Anda ingin memperluas logika init dari kelas dasar, Anda mendefinisikan metode init Anda sendiri dan kemudian memanggil metode init kelas dasar dari itu. 3) Jika Anda ingin mengganti logika init kelas dasar, Anda mendefinisikan metode init Anda sendiri tanpa memanggilnya dari kelas dasar.
wombatonfire
4

Jika kelas FileInfo memiliki lebih dari satu kelas leluhur maka Anda harus memanggil semua fungsi __init __ () mereka. Anda juga harus melakukan hal yang sama untuk fungsi __del __ (), yang merupakan destruktor.

moinudin
sumber
2

Ya, Anda harus menelepon __init__untuk setiap kelas induk. Hal yang sama berlaku untuk fungsi, jika Anda mengganti fungsi yang ada di kedua orang tua.

vezult
sumber