cara yang benar untuk menggunakan super (pengoper argumen)

100

Jadi saya mengikuti Python's Super Dianggap Berbahaya , dan pergi untuk menguji contohnya.

Namun, Contoh 1-3 , yang seharusnya menunjukkan cara pemanggilan yang benar supersaat menangani __init__metode yang mengharapkan argumen berbeda, tidak berhasil.

Inilah yang saya dapatkan:

~ $ python example1-3.py 
MRO: ['E', 'C', 'A', 'D', 'B', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B
Traceback (most recent call last):
  File "Download/example1-3.py", line 27, in <module>
    E(arg=10)
  File "Download/example1-3.py", line 24, in __init__
    super(E, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 14, in __init__
    super(C, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 4, in __init__
    super(A, self).__init__(*args, **kwargs)
  File "Download/example1-3.py", line 19, in __init__
    super(D, self).__init__(arg, *args, **kwargs)
  File "Download/example1-3.py", line 9, in __init__
    super(B, self).__init__(*args, **kwargs)
TypeError: object.__init__() takes no parameters

Tampaknya itu objectsendiri melanggar salah satu praktik terbaik yang disebutkan dalam dokumen, yaitu metode yang digunakan superharus menerima *argsdan **kwargs.

Sekarang, jelas Tuan Knight mengharapkan contohnya bekerja, jadi apakah ini sesuatu yang telah diubah dalam versi terbaru Python? Saya memeriksa 2.6 dan 2.7, dan gagal pada keduanya.

Jadi bagaimana cara yang benar untuk mengatasi masalah ini?

cha0site
sumber
2
Cara yang saya sukai adalah: Hierarki pewarisan datar dan sederhana.
millimoose
19
Anda juga harus membaca "Python super () dianggap super" untuk mendapatkan tampilan yang seimbang :)
Björn Pollex
@ BjörnPollex: Terima kasih! Link anda memberikan jawaban atas pertanyaan: Anda dapat menulis "root class" yang mewarisi dari object, dan itu membuat yakin untuk panggilan object's __init__benar.
cha0site
5
Perhatikan bahwa __init__on objectmengabaikan parameter apa pun secara diam-diam pada Python 2.5. Ini berubah di Python 2.6.
Wilfred Hughes
1
@Wilfred: Hai, Terima kasih telah menjawab pertanyaan yang sebenarnya! Sekarang saya tahu mengapa esai itu ketinggalan zaman!
cha0site

Jawaban:

105

Terkadang dua kelas mungkin memiliki beberapa nama parameter yang sama. Dalam hal ini, Anda tidak dapat mengeluarkan pasangan nilai kunci **kwargsatau menghapusnya *args. Sebagai gantinya, Anda dapat menentukan Basekelas yang tidak seperti object, menyerap / mengabaikan argumen:

class Base(object):
    def __init__(self, *args, **kwargs): pass

class A(Base):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(Base):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(arg, *args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(arg, *args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(arg, *args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10)

hasil

MRO: ['E', 'C', 'A', 'D', 'B', 'Base', 'object']
E arg= 10
C arg= 10
A
D arg= 10
B

Perhatikan bahwa untuk bekerja, Baseharus kelas kedua dari belakang di MRO.

unutbu
sumber
4
Tidak harus Basemenelepon super(Base, self).__init__()?
cha0site
4
Untuk bekerja, Baseharus datang di akhir MRO (kecuali object). Menelepon object.__init__tidak berarti apa-apa, jadi tidak apa-apa untuk tidak menelepon super(Base, self).__init__(). Faktanya, saya pikir mungkin lebih jelas untuk tidak memasukkan super(Base, self).__init__()untuk pulang ke titik yang Basemerupakan akhir baris.
unutbu
26

Jika Anda akan memiliki banyak pewarisan (itulah yang terjadi di sini) saya sarankan Anda untuk meneruskan semua parameter menggunakan **kwargs, dan kemudian popmereka segera setelah Anda menggunakannya (kecuali Anda membutuhkannya di kelas atas).

class First(object):
    def __init__(self, *args, **kwargs):
        self.first_arg = kwargs.pop('first_arg')
        super(First, self).__init__(*args, **kwargs)

class Second(First):
    def __init__(self, *args, **kwargs):
        self.second_arg = kwargs.pop('second_arg')
        super(Second, self).__init__(*args, **kwargs)

class Third(Second):
    def __init__(self, *args, **kwargs):
        self.third_arg = kwargs.pop('third_arg')
        super(Third, self).__init__(*args, **kwargs)

Ini adalah cara termudah untuk memecahkan masalah semacam itu.

third = Third(first_arg=1, second_arg=2, third_arg=3)
juliomalegria.dll
sumber
7

Seperti yang dijelaskan dalam Python super () dianggap super , salah satu caranya adalah dengan membuat kelas memakan argumen yang diperlukan, dan meneruskan sisanya. Jadi, ketika rantai panggilan mencapai object, semua argumen telah dimakan, dan object.__init__akan dipanggil tanpa argumen (seperti yang diharapkan). Jadi kode Anda akan terlihat seperti ini:

class A(object):
    def __init__(self, *args, **kwargs):
        print "A"
        super(A, self).__init__(*args, **kwargs)

class B(object):
    def __init__(self, *args, **kwargs):
        print "B"
        super(B, self).__init__(*args, **kwargs)

class C(A):
    def __init__(self, arg, *args, **kwargs):
        print "C","arg=",arg
        super(C, self).__init__(*args, **kwargs)

class D(B):
    def __init__(self, arg, *args, **kwargs):
        print "D", "arg=",arg
        super(D, self).__init__(*args, **kwargs)

class E(C,D):
    def __init__(self, arg, *args, **kwargs):
        print "E", "arg=",arg
        super(E, self).__init__(*args, **kwargs)

print "MRO:", [x.__name__ for x in E.__mro__]
E(10, 20, 30)
Björn Pollex
sumber
Jadi bagaimana dengan E(arg = 10)? Saya tidak suka metode ini, saya perlu mengetahui MRO terlebih dahulu untuk menggunakannya. Metode lain di mana saya menulis "kelas root" yang diwarisi dari objecttampaknya jauh lebih bersih.
cha0site
Metode ini berguna jika fungsi yang dipanggil jangan berbagi nama argumen. Saya akui bahwa saya tidak memiliki banyak pengalaman praktis dengan super, jadi pendekatan ini mungkin memang agak teoretis.
Björn Pollex