Apa atribut __dict __.__ dict__ dari kelas Python?

92
>>> class A(object): pass
... 
>>> A.__dict__
<dictproxy object at 0x173ef30>
>>> A.__dict__.__dict__
Traceback (most recent call last):
  File "<string>", line 1, in <fragment>
AttributeError: 'dictproxy' object has no attribute '__dict__'
>>> A.__dict__.copy()
{'__dict__': <attribute '__dict__' of 'A' objects> ... }
>>> A.__dict__['__dict__']
<attribute '__dict__' of 'A' objects> # What is this object?

Jika saya lakukan A.something = 10, ini masuk ke A.__dict__. Apa yang ini <attribute '__dict__' of 'A' objects>berhasil ditemukan di A.__dict__.__dict__, dan ketika tidak mengandung sesuatu?

porgarmingduod
sumber
11
Variabel contoh yang lebih cocok adalah ive. Setidaknya itu akan membuat ini menjadi A.__dict__['ive']pertanyaan lagi ;) Saya akan melihat diri saya keluar
Joakim

Jawaban:

110

Pertama-tama A.__dict__.__dict__berbeda dari A.__dict__['__dict__'], dan yang pertama tidak ada. Yang terakhir adalah __dict__atribut yang dimiliki oleh instance kelas. Ini adalah objek deskriptor yang mengembalikan kamus internal atribut untuk instance tertentu. Singkatnya, __dict__atribut suatu objek tidak dapat disimpan dalam objek __dict__, jadi itu diakses melalui deskriptor yang ditentukan di kelas.

Untuk memahami ini, Anda harus membaca dokumentasi protokol deskriptor .

Versi singkatnya:

  1. Untuk instance kelas A, akses ke instance.__dict__disediakan oleh A.__dict__['__dict__']yang sama dengan vars(A)['__dict__'].
  2. Untuk kelas A, akses ke A.__dict__disediakan oleh type.__dict__['__dict__'](secara teori) yang sama dengan vars(type)['__dict__'].

Versi panjang:

Baik kelas maupun objek menyediakan akses ke atribut baik melalui operator atribut (diimplementasikan melalui kelas atau metaclass __getattribute__), dan __dict__atribut / protokol yang digunakan oleh vars(ob).

Untuk objek normal, __dict__objek membuat objek terpisah dict, yang menyimpan atribut, dan __getattribute__pertama kali mencoba mengaksesnya dan mendapatkan atribut dari sana (sebelum mencoba mencari atribut di kelas dengan memanfaatkan protokol deskriptor, dan sebelum memanggil __getattr__). The __dict__descriptor pada kelas mengimplementasikan akses ke kamus ini.

  • x.namesetara dengan mencoba mereka dalam rangka: x.__dict__['name'], type(x).name.__get__(x, type(x)),type(x).name
  • x.__dict__ melakukan hal yang sama tetapi melewatkan yang pertama karena alasan yang jelas

Seperti tidak mungkin untuk __dict__dari instanceuntuk disimpan dalam __dict__instance, itu diakses melalui protokol deskriptor langsung sebaliknya, dan disimpan dalam bidang khusus dalam hal ini.

Skenario serupa berlaku untuk kelas, meskipun mereka __dict__adalah objek proxy khusus yang berpura-pura menjadi kamus (tetapi mungkin tidak secara internal), dan tidak memungkinkan Anda untuk mengubahnya atau menggantinya dengan yang lain. Proksi ini memungkinkan Anda, di antara yang lainnya, untuk mengakses atribut kelas yang khusus untuk itu, dan tidak ditentukan di salah satu basisnya.

Secara default, vars(cls)kelas kosong membawa tiga deskriptor - __dict__untuk menyimpan atribut instance, __weakref__yang digunakan secara internal oleh weakref, dan docstring kelas. Dua yang pertama mungkin hilang jika Anda menentukan __slots__. Maka Anda tidak akan memiliki __dict__dan __weakref__atribut, tetapi Anda akan memiliki atribut kelas tunggal untuk setiap slot. Atribut instance tidak akan disimpan dalam kamus, dan akses ke sana akan disediakan oleh deskriptor masing-masing di kelas.


Dan terakhir, ketidakkonsistenan yang A.__dict__berbeda dari A.__dict__['__dict__']adalah karena atribut __dict__, dengan pengecualian, tidak pernah dicari vars(A), jadi apa yang benar karena itu tidak benar untuk hampir semua atribut lain yang Anda gunakan. Misalnya, A.__weakref__sama saja dengan A.__dict__['__weakref__']. Jika ketidakkonsistenan ini tidak ada, penggunaan A.__dict__tidak akan berhasil, dan Anda harus selalu menggunakannya vars(A).

Rosh Oxymoron
sumber
6
Terima kasih atas jawaban mendetailnya. Meskipun saya harus membacanya beberapa kali, saya rasa sudah lama saya tidak mempelajari begitu banyak detail baru tentang Python.
porgarmingduod
Mengapa sebenarnya __dict__atribut suatu objek tidak dapat disimpan di objek tersebut __dict__?
zumgruenenbaum
2
@zumgruenenbaum Karena __dict__dimaksudkan untuk menyimpan semua atribut instance, akses atribut dari formulir obj.xakhirnya dicari pada objek __dict__, yaitu obj.__dict__['x']. Sekarang jika __dict__tidak diimplementasikan sebagai deskriptor, ini akan mengarah pada rekursi tak terbatas, karena untuk mengakses obj.__dict__Anda harus mencarinya sebagai obj.__dict__['__dict__']. Deskriptor menghindari masalah ini.
Tamu
11

Karena A.__dict__kamus menyimpan Aatribut, A.__dict__['__dict__']adalah referensi langsung ke A.__dict__atribut yang sama .

A.__dict__berisi referensi (jenis) untuk dirinya sendiri. Bagian "kind-of" adalah alasan ekspresi A.__dict__mengembalikan a, dictproxybukan normal dict.

>>> class B(object):
...     "Documentation of B class"
...     pass
...
>>> B.__doc__
'Documentation of B class'
>>> B.__dict__
<dictproxy object at 0x00B83590>
>>> B.__dict__['__doc__']
'Documentation of B class'
vz0
sumber
9
A.__dict__['__dict__']bukan referensi ke A.__dict__. Ini mengimplementasikan __dict__atribut instance. Untuk mencobanya sendiri, A.__dict__['__dict__'].__get__(A(), A)kembalikan atribut A(), sementara A.__dict__['__dict__'].__get__(A, type)gagal.
Rosh Oxymoron
10

Mari kita menjelajah!

>>> A.__dict__['__dict__']
<attribute '__dict__' of 'A' objects>

Aku penasaran apa itu?

>>> type(A.__dict__['__dict__'])
<type 'getset_descriptor'>

Atribut apa yang getset_descriptordimiliki suatu benda?

>>> type(A.__dict__["__dict__"]).__dict__
<dictproxy object at 0xb7efc4ac>

Dengan membuat salinannya dictproxykita dapat menemukan beberapa atribut yang menarik, khususnya __objclass__dan __name__.

>>> A.__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__
(<class '__main__.A'>, '__dict__')

Jadi, __objclass__apakah referensi ke Adan __name__hanya string '__dict__', nama atribut mungkin?

>>> getattr(A.__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__) == A.__dict__
True

Itu dia! A.__dict__['__dict__']adalah objek yang bisa dirujuk kembali A.__dict__.

Andrew Clark
sumber
PEP 252 mengatakan itu __objclass__adalah kelas yang mendefinisikan atribut ini, bukan itu adalah atribut kelas itu. Ini membuat getattrcontoh Anda salah. Yang lebih benar adalahgetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
Rosh Oxymoron
1
@RoshOxymoron Ekspresi Anda muncul KeyError: '__dict__', bertentangan dengan @ AndrewClark.
Maggyero
9

Anda dapat mencoba contoh sederhana berikut untuk lebih memahami ini:

>>> class A(object): pass
... 
>>> a = A()
>>> type(A)
<type 'type'>
>>> type(a)
<class '__main__.A'>
>>> type(a.__dict__)
<type 'dict'>
>>> type(A.__dict__)
<type 'dictproxy'>
>>> type(type.__dict__)
<type 'dictproxy'>
>>> type(A.__dict__['__dict__'])
<type 'getset_descriptor'>
>>> type(type.__dict__['__dict__'])
<type 'getset_descriptor'>
>>> a.__dict__ == A.__dict__['__dict__'].__get__(a)
True
>>> A.__dict__ == type.__dict__['__dict__'].__get__(A)
True
>>> a.__dict__ == type.__dict__['__dict__'].__get__(A)['__dict__'].__get__(a)
True

Dari contoh di atas, terlihat bahwa atribut objek kelas disimpan oleh kelasnya, atribut kelas disimpan oleh kelasnya, yaitu metaclass. Ini juga divalidasi oleh:

>>> a.__dict__ == A.__getattribute__(a, '__dict__')
True
>>> A.__dict__ == type.__getattribute__(A, '__dict__')
True
damaZhang
sumber
1
Anehnya, jika isdiganti ==dalam perbandingan kedua, yaitu A.__dict__ is type.__dict__['__dict__'].__get__(A), hasilnya ada Falsedi python 2.7.15+ dan 3.6.8.
Arne Vogel