Tidak dapat menyetel atribut pada instance kelas "objek"

88

Jadi, saya bermain-main dengan Python saat menjawab pertanyaan ini , dan saya menemukan bahwa ini tidak valid:

o = object()
o.attr = 'hello'

karena sebuah AttributeError: 'object' object has no attribute 'attr'. Namun, dengan kelas apa pun yang diwarisi dari objek, itu valid:

class Sub(object):
    pass

s = Sub()
s.attr = 'hello'

Pencetakan s.attrmenampilkan 'halo' seperti yang diharapkan. Mengapa demikian? Apa dalam spesifikasi bahasa Python yang menentukan bahwa Anda tidak dapat menetapkan atribut ke objek vanilla?

Smashery
sumber
Tebakan murni: Jenisnya objecttidak dapat diubah dan atribut baru tidak dapat ditambahkan? Sepertinya ini yang paling masuk akal.
Chris Lutz
2
@ S. Lott: Lihat baris pertama dari pertanyaan ini. Rasa ingin tahu murni.
Smashery
3
Judul Anda menyesatkan, Anda mencoba menyetel atribut pada objectinstance kelas, bukan pada objectkelas.
dhill

Jawaban:

130

Untuk mendukung penetapan atribut arbitrer, sebuah objek membutuhkan a __dict__: a dict terkait dengan objek tersebut, di mana atribut arbitrer dapat disimpan. Jika tidak, tidak ada tempat untuk meletakkan atribut baru.

Sebuah contoh dari objecttidak tidak membawa sekitar __dict__- jika itu terjadi, sebelum mengerikan masalah ketergantungan melingkar (karena dict, seperti kebanyakan segalanya, mewarisi dariobject ;-), ini akan pelana setiap objek di Python dengan dict, yang berarti overhead dari banyak byte per objek yang saat ini tidak memiliki atau memerlukan dict (pada dasarnya, semua objek yang tidak memiliki atribut yang dapat ditetapkan secara sewenang-wenang tidak memiliki atau memerlukan dict).

Misalnya, menggunakan excellent pympler proyek yang (Anda bisa mendapatkannya melalui svn dari sini ), kita dapat melakukan beberapa pengukuran ...:

>>> from pympler import asizeof
>>> asizeof.asizeof({})
144
>>> asizeof.asizeof(23)
16

Anda tidak ingin setiap orang intmengambil 144 byte, bukan hanya 16, kan? -)

Sekarang, ketika Anda membuat kelas (mewarisi dari apa pun), banyak hal berubah ...:

>>> class dint(int): pass
... 
>>> asizeof.asizeof(dint(23))
184

...itu __dict__ ini sekarang ditambahkan (ditambah, sedikit lebih overhead) - sehingga dintmisalnya dapat memiliki atribut sewenang-wenang, tetapi Anda membayar cukup biaya ruang untuk fleksibilitas itu.

Jadi bagaimana jika Anda menginginkan inthanya dengan satu atribut tambahan foobar...? Ini adalah kebutuhan yang langka, tetapi Python memang menawarkan mekanisme khusus untuk tujuan tersebut ...

>>> class fint(int):
...   __slots__ = 'foobar',
...   def __init__(self, x): self.foobar=x+100
... 
>>> asizeof.asizeof(fint(23))
80

... tidak cukup sebagai kecil sebagai int, pikiran Anda! (atau bahkan keduanya int, satu selfdan satu self.foobar- yang kedua dapat dipindahkan), tapi pasti jauh lebih baik daripada a dint.

Ketika kelas memiliki __slots__atribut khusus (urutan string), maka classpernyataan (lebih tepatnya, metaclass default, type) tidak melengkapi setiap instance kelas itu dengan __dict__(dan karena itu kemampuan untuk memiliki atribut arbitrer), hanya terbatas , kumpulan "slot" yang kaku (pada dasarnya tempat yang masing-masing dapat menampung satu referensi ke beberapa objek) dengan nama yang diberikan.

Dalam pertukaran untuk fleksibilitas yang hilang, Anda mendapatkan banyak byte per contoh (mungkin bermakna hanya jika Anda memiliki zillions kasus berkeliaran di sekitar, tapi, ada yang menggunakan kasus untuk itu).

Alex Martelli
sumber
5
Ini menjelaskan bagaimana mekanisme tersebut diterapkan tetapi tidak menjelaskan mengapa diterapkan dengan cara ini. Saya dapat memikirkan setidaknya dua atau tiga cara untuk menerapkan menambahkan dict dengan cepat yang tidak akan memiliki kerugian overhead tetapi akan menambahkan beberapa kesederhanaan.
Георги Кременлиев
Perhatikan bahwa non-kosong __slots__tidak bekerja dengan jenis variabel-panjang seperti str, tuple, dan Python 3 juga int.
arekolek
Ini adalah penjelasan yang bagus, tetapi masih tidak menjawab mengapa (atau bagaimana) Submemiliki __dict__atribut dan objek tidak, karena itu Submewarisi object, bagaimana atribut itu (dan sejenisnya __module__) ditambahkan ke dalam warisan? Mungkin ini bisa menjadi pertanyaan baru
Rodrigo E. Principe
2
Sebuah objek __dict__hanya dibuat saat pertama kali dibutuhkan, jadi situasi biaya memori tidak sesederhana asizeofkeluaran membuatnya terlihat. ( asizeoftidak tahu bagaimana menghindari __dict__materialisasi.) Anda dapat melihat dict tidak terwujud hingga dibutuhkan dalam contoh ini , dan Anda dapat melihat salah satu jalur kode yang bertanggung jawab untuk __dict__materialisasi di sini .
user2357112 mendukung Monica
17

Seperti yang dikatakan oleh penjawab lain, an objecttidak memiliki a __dict__. objectadalah kelas dasar dari semua jenis, termasuk intatau str. Dengan demikian apapun yang diberikan objectakan menjadi beban bagi mereka juga. Bahkan sesuatu yang sederhana seperti opsional __dict__ akan membutuhkan penunjuk tambahan untuk setiap nilai; ini akan membuang 4-8 byte memori tambahan untuk setiap objek dalam sistem, untuk utilitas yang sangat terbatas.


Alih-alih melakukan instance kelas dummy, dengan Python 3.3+, Anda dapat (dan harus) menggunakannya types.SimpleNamespace.

Antti Haapala
sumber
4

Ini hanya karena pengoptimalan.

Dicts relatif besar.

>>> import sys
>>> sys.getsizeof((lambda:1).__dict__)
140

Kebanyakan (mungkin semua) kelas yang didefinisikan dalam C tidak memiliki perintah untuk pengoptimalan.

Jika Anda melihat kode sumber, Anda akan melihat bahwa ada banyak pemeriksaan untuk melihat apakah objek memiliki dict atau tidak.

Tidak diketahui
sumber
1

Jadi, menyelidiki pertanyaan saya sendiri, saya menemukan ini tentang bahasa Python: Anda dapat mewarisi dari hal-hal seperti int, dan Anda melihat perilaku yang sama:

>>> class MyInt(int):
       pass

>>> x = MyInt()
>>> print x
0
>>> x.hello = 4
>>> print x.hello
4
>>> x = x + 1
>>> print x
1
>>> print x.hello
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
AttributeError: 'int' object has no attribute 'hello'

Saya berasumsi kesalahan pada akhirnya adalah karena fungsi add mengembalikan int, jadi saya harus mengganti fungsi seperti __add__dan semacamnya untuk mempertahankan atribut khusus saya. Tapi ini semua sekarang masuk akal bagi saya (menurut saya), ketika saya memikirkan "objek" seperti "int".

Smashery
sumber
0

Itu karena objek adalah "tipe", bukan kelas. Secara umum, semua kelas yang didefinisikan dalam ekstensi C (seperti semua tipe data bawaan, dan hal-hal seperti array numpy) tidak mengizinkan penambahan atribut arbitrer.

Ryan
sumber
Tapi objek () adalah objek, sama seperti Sub () adalah objek. Pemahaman saya adalah bahwa s dan o adalah objek. Jadi apa perbedaan mendasar antara s dan o? Apakah yang satu adalah tipe yang dipakai dan yang lainnya adalah kelas yang dipakai?
Smashery
Bingo. Itulah masalahnya.
Ryan
1
Di Python 3, perbedaan antara tipe dan kelas tidak benar-benar ada. Jadi, "tipe" & "kelas" sekarang cukup identik. Tetapi Anda masih tidak dapat menambahkan atribut ke kelas-kelas yang tidak memiliki __dict__, karena alasan yang diberikan oleh Alex Martelli.
PM 2Ring
-2

Ini adalah (IMO) salah satu batasan mendasar dengan Python - Anda tidak dapat membuka kembali kelas. Saya percaya masalah sebenarnya, disebabkan oleh fakta bahwa kelas yang diimplementasikan dalam C tidak dapat diubah pada saat runtime ... subclass bisa, tetapi tidak kelas dasar.

Peter
sumber