Mengapa sihir super () Python 3.x?

159

Dalam Python 3.x, super()dapat dipanggil tanpa argumen:

class A(object):
    def x(self):
         print("Hey now")

class B(A):
    def x(self):
        super().x()
>>> B().x()
Hey now

Untuk membuat ini bekerja, beberapa sihir waktu kompilasi dilakukan, salah satu konsekuensinya adalah bahwa kode berikut (yang rebind superke super_) gagal:

super_ = super

class A(object):
    def x(self):
        print("No flipping")

class B(A):
    def x(self):
        super_().x()
>>> B().x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in x
RuntimeError: super(): __class__ cell not found

Mengapa super()tidak dapat menyelesaikan superclass saat runtime tanpa bantuan dari kompiler? Apakah ada situasi praktis di mana perilaku ini, atau alasan yang mendasarinya, dapat menggigit programmer yang tidak waspada?

... dan, sebagai pertanyaan sampingan: apakah ada contoh lain dalam Python fungsi, metode, dll. yang dapat dipecah dengan memutarkannya ke nama yang berbeda?

Zero Piraeus
sumber
6
Aku akan membiarkan Armin melakukan menjelaskan hal ini satu . Ini juga pos yang
Games Brainiac

Jawaban:

217

super()Perilaku sulap baru ditambahkan untuk menghindari melanggar prinsip KERING (Jangan Ulangi Diri Sendiri), lihat PEP 3135 . Harus secara eksplisit memberi nama kelas dengan merujuknya sebagai global juga rentan terhadap masalah rebinding yang Anda temukan dengan super()dirinya sendiri:

class Foo(Bar):
    def baz(self):
        return super(Foo, self).baz() + 42

Spam = Foo
Foo = something_else()

Spam().baz()  # liable to blow up

Hal yang sama berlaku untuk menggunakan dekorator kelas di mana dekorator mengembalikan objek baru, yang mengubah nama kelas:

@class_decorator_returning_new_class
class Foo(Bar):
    def baz(self):
        # Now `Foo` is a *different class*
        return super(Foo, self).baz() + 42

super() __class__Sel ajaib menghindari masalah ini dengan baik dengan memberi Anda akses ke objek kelas asli.

PEP diluncurkan oleh Guido, yang awalnya membayangkan supermenjadi kata kunci , dan gagasan menggunakan sel untuk mencari kelas saat ini juga miliknya . Tentu saja, gagasan untuk menjadikannya kata kunci adalah bagian dari konsep pertama PEP .

Namun, sebenarnya Guido sendiri yang kemudian menjauh dari gagasan kata kunci sebagai 'terlalu ajaib' , malah mengusulkan penerapan saat ini. Dia mengantisipasi bahwa menggunakan nama yang berbeda untuk super()bisa menjadi masalah :

Tambalan saya menggunakan solusi perantara: diasumsikan Anda butuhkan __class__ setiap kali Anda menggunakan variabel bernama 'super'. Jadi, jika Anda (secara global) mengganti nama supermenjadi supperdan menggunakan suppertetapi tidak super, itu tidak akan berfungsi tanpa argumen (tapi itu masih akan berfungsi jika Anda meneruskannya baik __class__atau objek kelas yang sebenarnya); jika Anda memiliki nama variabel yang tidak terkait super, semuanya akan berfungsi tetapi metode ini akan menggunakan jalur panggilan yang sedikit lebih lambat yang digunakan untuk variabel sel.

Jadi, pada akhirnya, Guido sendiri yang menyatakan bahwa menggunakan superkata kunci tidak terasa benar, dan bahwa menyediakan __class__sel ajaib adalah kompromi yang dapat diterima.

Saya setuju bahwa keajaiban, perilaku implisit implementasi agak mengejutkan, tetapi super()merupakan salah satu fungsi yang paling salah diterapkan dalam bahasa. Lihat saja semua kesalahan penggunaan super(type(self), self)atau super(self.__class__, self) doa yang ditemukan di Internet; jika ada kode yang dipanggil dari kelas turunan, Anda akan berakhir dengan pengecualian rekursi tak terbatas . Di sangat paling tidak disederhanakan super()panggilan, tanpa argumen, menghindari itu masalah.

Adapun yang diganti namanya super_; referensi saja __class__dalam metode Anda juga dan itu akan bekerja lagi. Sel dibuat jika Anda merujuk salah satu super atau __class__ nama - nama dalam metode Anda:

>>> super_ = super
>>> class A(object):
...     def x(self):
...         print("No flipping")
... 
>>> class B(A):
...     def x(self):
...         __class__  # just referencing it is enough
...         super_().x()
... 
>>> B().x()
No flipping
Martijn Pieters
sumber
1
Tulisan yang bagus. Ini masih sejelas lumpur. Anda mengatakan bahwa super () setara dengan fungsi instantiated otomatis seperti def super(of_class=magic __class__)jenis seperti self.super(); def super(self): return self.__class__?
Charles Merriam
17
@CharlesMerriam: Posting ini bukan tentang bagaimana super()tanpa argumen berfungsi; sebagian besar berkaitan dengan mengapa itu ada. super(), dalam metode kelas, sama dengan super(ReferenceToClassMethodIsBeingDefinedIn, self), di mana ReferenceToClassMethodIsBeingDefinedInditentukan pada waktu kompilasi, dilampirkan pada metode sebagai penutup bernama __class__, dan super()akan mencari keduanya dari bingkai panggilan saat runtime. Tapi Anda sebenarnya tidak perlu tahu semua ini.
Martijn Pieters
1
@CharlesMerriam: tetapi super()tidak ada yang dekat dengan fungsi yang dipakai secara otomatis , no.
Martijn Pieters
1
@ chris.leonard: kalimat kuncinya adalah Sel dibuat jika Anda menggunakan super () atau digunakan __class__dalam metode Anda . Anda menggunakan nama superdalam fungsi Anda. Kompilator melihat itu dan menambahkan __class__penutup.
Martijn Pieters
4
@ Alexey: itu tidak cukup. type(self)memberikan tipe saat ini , yang tidak sama dengan tipe metode yang didefinisikan. Jadi kelas Foodengan metode bazkebutuhan super(Foo, self).baz(), karena bisa subclassed sebagai class Ham(Foo):, di mana titik type(self)adalah Hamdan super(type(self), self).baz()akan memberikan loop tak terbatas. Lihat posting yang saya tautkan ke dalam jawaban saya: Saat memanggil super () di kelas turunan, dapatkah saya lulus sendiri .__ class__?
Martijn Pieters