Mengapa saya tidak bisa mengubah atribut __class__ dari instance objek?

10
class A(object):
    pass

class B(A):
    pass

o = object()
a = A()
b = B()

Meskipun saya bisa berubah a.__class__, saya tidak bisa melakukan hal yang sama o.__class__(itu melempar TypeErrorkesalahan). Mengapa?

Sebagai contoh:

isinstance(a, A) # True
isinstance(a, B) # False
a.__class__ = B
isinstance(a, A) # True
isinstance(a, B) # True

isinstance(o, object) # True
isinstance(o, A) # False
o.__class__ = A # This fails and throws a TypeError
# isinstance(o, object)
# isinstance(o, A)

Saya tahu ini umumnya bukan ide yang baik, karena dapat menyebabkan perilaku yang sangat aneh jika ditangani dengan tidak benar. Hanya demi rasa penasaran.

Riccardo Bucco
sumber
3
Tipe bawaan mengorbankan dinamisme dari tipe yang ditentukan pengguna untuk alasan efisiensi. Catatan, optimasi opsional lain adalah slot, yang juga akan mencegah hal ini.
juanpa.arrivillaga

Jawaban:

6

CPython memiliki komentar di Objects / typeobject.c pada topik ini:

Dalam versi CPython sebelum 3.5, kode di compatible_for_assignmenttidak diatur untuk memeriksa dengan benar untuk memori layout / slot / dll kompatibilitas untuk kelas non-HEAPTYPE, jadi kami hanya menolak __class__penugasan dalam hal apa pun yang bukan HEAPTYPE -> HEAPTYPE.

Selama siklus pengembangan 3,5, kami memperbaiki kode compatible_for_assignmentuntuk mengecek kompatibilitas antara tipe yang sewenang-wenang, dan mulai memungkinkan __class__penugasan dalam semua kasus di mana tipe lama dan baru sebenarnya memiliki slot dan tata letak memori yang kompatibel (terlepas dari apakah mereka diimplementasikan sebagai HEAPTYPEs atau tidak).

Namun, sebelum 3.5 dirilis, kami menemukan bahwa ini menyebabkan masalah dengan tipe yang tidak dapat diubah seperti int, di mana penerjemah menganggap mereka tidak dapat diubah dan menginternir beberapa nilai. Sebelumnya ini bukan masalah, karena mereka benar-benar tidak dapat diubah - khususnya, semua jenis di mana penerjemah menerapkan trik magang ini juga dialokasikan secara statis, sehingga aturan HEAPTYPE lama "secara tidak sengaja" menghentikan mereka dari mengizinkan __class__penugasan. Tetapi dengan perubahan pada __class__tugas, kami mulai mengizinkan kode seperti

class MyInt(int):
#   ...
# Modifies the type of *all* instances of 1 in the whole program,
# including future instances (!), because the 1 object is interned.
 (1).__class__ = MyInt

(lihat https://bugs.python.org/issue24912 ).

Secara teori, perbaikan yang tepat adalah mengidentifikasi kelas mana yang bergantung pada invarian ini dan entah bagaimana melarang __class__tugas hanya untuk mereka, mungkin melalui beberapa mekanisme seperti bendera Py_TPFLAGS_IMMUTABLE baru (pendekatan "daftar hitam"). Namun dalam praktiknya, karena masalah ini tidak diketahui di akhir siklus 3.5 RC, kami mengambil pendekatan konservatif dan mengembalikan pemeriksaan HEAPTYPE-> HEAPTYPE yang sama dengan yang dulu kami miliki, plus "daftar putih". Untuk saat ini, daftar putih hanya terdiri dari subtipe ModuleType, karena ini adalah kasus yang memotivasi patch pada awalnya - lihat https://bugs.python.org/issue22986 - dan karena objek modul dapat diubah, kita dapat memastikan bahwa mereka pasti tidak diinternir. Jadi sekarang kita mengizinkan HEAPTYPE-> HEAPTYPE atau ModuleType subtype -> ModuleType subtype.

Sejauh yang kita ketahui, semua kode di luar pernyataan 'jika' berikut ini akan menangani dengan benar kelas-kelas non-HEAPTYPE, dan pemeriksaan HEAPTYPE hanya diperlukan untuk melindungi subset kelas-kelas non-HEAPTYPE yang telah dibuat oleh juru bahasa dengan asumsi bahwa semua contoh benar-benar abadi.

Penjelasan:

CPython menyimpan objek dalam dua cara:

Objek adalah struktur yang dialokasikan pada heap. Aturan khusus berlaku untuk penggunaan objek untuk memastikan mereka dikumpulkan dengan benar. Objek tidak pernah dialokasikan secara statis atau di stack; mereka harus diakses melalui makro khusus dan fungsi saja. (Tipe objek adalah pengecualian dari aturan pertama; tipe standar diwakili oleh objek tipe yang diinisialisasi secara statis, meskipun bekerja pada tipe / kelas unifikasi untuk Python 2.2 memungkinkan untuk memiliki objek tipe heap-dialokasikan juga).

Informasi dari komentar di Sertakan / objek.h .

Ketika Anda mencoba untuk menetapkan nilai baru some_obj.__class__, object_set_classfungsinya dipanggil. Itu diwarisi dari PyBaseObject_Type , lihat /* tp_getset */bidang. Fungsi ini memeriksa : dapatkah tipe baru menggantikan tipe lama some_obj?

Ambil contoh Anda:

class A:
    pass

class B:
    pass

o = object()
a = A() 
b = B() 

Kasus pertama:

a.__class__ = B 

Jenis aobjek adalah A, tipe tumpukan, karena dialokasikan secara dinamis. Serta B. The atipe 's berubah tanpa masalah.

Kasus kedua:

o.__class__ = B

Tipe oadalah tipe bawaan object( PyBaseObject_Type). Ini bukan tipe heap, jadi TypeErrordinaikkan:

TypeError: __class__ assignment only supported for heap types or ModuleType subclasses.
MiniMax
sumber
4

Anda hanya dapat mengubah __class__ke tipe lain yang memiliki tata letak internal (C) yang sama . Runtime bahkan tidak tahu tata letak itu kecuali tipe itu sendiri secara dinamis dialokasikan ("tipe tumpukan"), jadi itu adalah kondisi yang diperlukan yang mengecualikan tipe bawaan sebagai sumber atau tujuan. Anda juga harus memiliki set yang sama __slots__dengan nama yang sama.

Davis Herring
sumber