แอตทริบิวต์ __dict __.__ dict__ ของคลาส 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?

ถ้าผมทำนี้จะเข้าสู่A.something = 10 A.__dict__อะไรคือนี้<attribute '__dict__' of 'A' objects>พบในA.__dict__.__dict__และเมื่อมันไม่ได้มีอะไร?


11
iveตัวแปรตัวอย่างเหมาะสมมากขึ้นจะได้รับ อย่างน้อยมันก็จะทำให้A.__dict__['ive']คำถามนี้มีมากขึ้น) ฉันจะเห็นตัวเอง
Joakim

คำตอบ:


110

ประการแรกA.__dict__.__dict__แตกต่างจากA.__dict__['__dict__']อดีตไม่มีอยู่จริง ส่วนหลังเป็น__dict__แอตทริบิวต์ที่อินสแตนซ์ของคลาสจะมี เป็นวัตถุอธิบายที่ส่งคืนพจนานุกรมภายในของแอตทริบิวต์สำหรับอินสแตนซ์เฉพาะ ในระยะสั้น__dict__แอตทริบิวต์ของวัตถุไม่สามารถเก็บไว้ในวัตถุได้__dict__ดังนั้นจึงเข้าถึงได้โดยใช้ descriptor ที่กำหนดไว้ในคลาส

เพื่อทำความเข้าใจนี้คุณจะต้องอ่านเอกสารของโปรโตคอลอธิบาย

เวอร์ชันสั้น:

  1. สำหรับตัวอย่างของคลาสAการเข้าถึงinstance.__dict__ให้บริการโดยซึ่งเป็นเช่นเดียวกับA.__dict__['__dict__']vars(A)['__dict__']
  2. สำหรับคลาส A, การเข้าถึงการA.__dict__ให้บริการโดยtype.__dict__['__dict__'](ในทางทฤษฎี) vars(type)['__dict__']ซึ่งเป็นเช่นเดียวกับ

เวอร์ชันยาว:

ทั้งการเรียนและวัตถุให้เข้าถึงคุณลักษณะทั้งสองผ่านผู้ประกอบการแอตทริบิวต์ (ดำเนินการผ่านชั้นเรียนหรือ metaclass ฯ__getattribute__) และ__dict__แอตทริบิวต์ / vars(ob)โปรโตคอลซึ่งถูกใช้โดย

สำหรับอ็อบเจ็กต์ปกติ__dict__อ็อบเจ็กต์จะสร้างอ็อบเจ็กต์แยกต่างหากdictซึ่งเก็บแอ็ตทริบิวต์และ__getattribute__ก่อนอื่นจะพยายามเข้าถึงและรับแอ็ตทริบิวต์จากที่นั่น (ก่อนที่จะพยายามค้นหาแอ็ตทริบิวต์ในคลาสโดยใช้โปรโตคอล descriptor และก่อนที่จะเรียก__getattr__) ตัว__dict__อธิบายในชั้นเรียนใช้การเข้าถึงพจนานุกรมนี้

  • x.nameเทียบเท่ากับการพยายามที่ผู้ที่อยู่ในการสั่งซื้อ: x.__dict__['name'], type(x).name.__get__(x, type(x)),type(x).name
  • x.__dict__ ทำเช่นเดียวกัน แต่ข้ามอันแรกด้วยเหตุผลที่ชัดเจน

เนื่องจากเป็นไปไม่ได้ที่__dict__ของinstanceจะถูกเก็บไว้ใน__dict__อินสแตนซ์จึงถูกเข้าถึงผ่านโปรโตคอล descriptor โดยตรงแทนและจะถูกเก็บไว้ในฟิลด์พิเศษในอินสแตนซ์

สถานการณ์ที่คล้ายกันนี้เป็นจริงสำหรับคลาสแม้ว่า__dict__จะเป็นวัตถุพร็อกซีพิเศษที่แสร้งทำเป็นพจนานุกรม (แต่อาจไม่ใช่ภายใน) และไม่อนุญาตให้คุณเปลี่ยนหรือแทนที่ด้วยอันอื่น พร็อกซีนี้อนุญาตให้คุณเข้าถึงแอตทริบิวต์ของคลาสที่เฉพาะเจาะจงและไม่ได้กำหนดไว้ในฐานใดฐานหนึ่ง

ตามค่าเริ่มต้นvars(cls)คลาสว่างจะมีตัวบ่งชี้สามตัว - __dict__สำหรับการจัดเก็บแอ็ตทริบิวต์ของอินสแตนซ์__weakref__ซึ่งใช้โดยภายในweakrefและ docstring ของคลาส __slots__สองคนแรกอาจจะหายไปถ้าคุณกำหนด จากนั้นคุณจะไม่มี__dict__และ__weakref__แอตทริบิวต์ แต่คุณจะมีแอตทริบิวต์คลาสเดียวสำหรับแต่ละช่องแทน จากนั้นแอตทริบิวต์ของอินสแตนซ์จะไม่ถูกเก็บไว้ในพจนานุกรมและการเข้าถึงจะถูกจัดเตรียมโดยตัวอธิบายตามลำดับในคลาส


และประการสุดท้ายความไม่ลงรอยกันที่A.__dict__แตกต่างจากA.__dict__['__dict__']แอตทริบิวต์__dict__คือโดยข้อยกเว้นไม่เคยมองหาvars(A)ดังนั้นสิ่งที่เป็นจริงจึงไม่เป็นความจริงสำหรับแอตทริบิวต์อื่น ๆ ที่คุณใช้ ยกตัวอย่างเช่นเป็นสิ่งเดียวกับA.__weakref__ A.__dict__['__weakref__']หากไม่มีความไม่สอดคล้องกันนี้การใช้A.__dict__จะไม่ได้ผลและคุณต้องใช้vars(A)แทนเสมอ


6
ขอบคุณสำหรับคำตอบโดยละเอียด แม้ว่าฉันจะต้องอ่านมันสองสามครั้ง แต่ฉันคิดว่ามันก็ผ่านมาสักพักแล้วที่ฉันได้เรียนรู้รายละเอียดใหม่ ๆ ของ Python
porgarmingduod

เหตุใดจึงไม่สามารถ__dict__จัดเก็บแอตทริบิวต์ของวัตถุในวัตถุได้__dict__?
zumgruenenbaum

2
@zumgruenenbaum เนื่องจาก__dict__จะหมายถึงการเก็บตัวอย่างคุณลักษณะทั้งหมดเข้าถึงแอตทริบิวต์ของแบบฟอร์มobj.xจะมองในที่สุดก็ขึ้นไปบนวัตถุของคือ__dict__ obj.__dict__['x']ตอนนี้ถ้า__dict__ไม่ได้ถูกนำมาใช้เป็นคำอธิบายถึงเรื่องนี้จะนำไปสู่การเรียกซ้ำอนันต์ตั้งแต่ในเพื่อที่จะเข้าถึงคุณจะต้องมองมันได้เป็นobj.__dict__ obj.__dict__['__dict__']ตัวบ่งชี้หลีกเลี่ยงปัญหานี้
a_guest

11

เนื่องจากA.__dict__เป็นพจนานุกรมที่จัดเก็บAแอตทริบิวต์A.__dict__['__dict__']จึงเป็นการอ้างอิงโดยตรงไปยังA.__dict__แอตทริบิวต์เดียวกันนั้น

A.__dict__มีการอ้างอิง (ชนิดของ) สำหรับตัวมันเอง "การชนิดของ" ส่วนหนึ่งเป็นเหตุผลที่แสดงออกA.__dict__ส่งกลับแทนที่จะเป็นปกติdictproxydict

>>> 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'

9
A.__dict__['__dict__']A.__dict__ไม่ได้มีการอ้างอิงถึง มันใช้__dict__แอตทริบิวต์ของอินสแตนซ์ หากต้องการลองด้วยตัวคุณเองให้A.__dict__['__dict__'].__get__(A(), A)ส่งคืนแอตทริบิวต์ของA()ขณะที่A.__dict__['__dict__'].__get__(A, type)ล้มเหลว
Rosh Oxymoron

10

มาสำรวจกัน!

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

ฉันสงสัยว่ามันคืออะไร?

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

getset_descriptorวัตถุมีคุณลักษณะอะไรบ้าง?

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

ด้วยการทำสำเนาของที่dictproxyเราสามารถหาคุณลักษณะที่น่าสนใจบางอย่างโดยเฉพาะและ__objclass____name__

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

ดังนั้น__objclass__การอ้างอิงถึงAและ__name__เป็นเพียงสตริง'__dict__'อาจเป็นชื่อของแอตทริบิวต์?

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

เรามีแล้ว! A.__dict__['__dict__']เป็นวัตถุที่สามารถอ้างอิงกลับไปA.__dict__ได้


PEP 252 กล่าวว่า__objclass__เป็นคลาสที่กำหนดแอตทริบิวต์นี้ไม่ใช่แอตทริบิวต์ของคลาสนั้น สิ่งนี้ทำให้getattrตัวอย่างของคุณไม่ถูกต้อง สิ่งที่ถูกต้องกว่านั้นคือgetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
Rosh Oxymoron

1
@RoshOxymoron การแสดงออกของคุณเพิ่มขึ้นKeyError: '__dict__'ตรงกันข้ามกับ @ AndrewClark's
Maggyero

9

คุณสามารถลองใช้ตัวอย่างง่ายๆต่อไปนี้เพื่อทำความเข้าใจสิ่งนี้ให้มากขึ้น:

>>> 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

จากตัวอย่างข้างต้นดูเหมือนว่าคลาสอ็อบเจ็กต์แอ็ตทริบิวต์จะถูกจัดเก็บโดยคลาสของพวกเขาแอ็ตทริบิวต์ของคลาสจะถูกจัดเก็บโดยคลาสของพวกเขาซึ่งก็คือเมตาคลาส นอกจากนี้ยังตรวจสอบความถูกต้องโดย:

>>> a.__dict__ == A.__getattribute__(a, '__dict__')
True
>>> A.__dict__ == type.__getattribute__(A, '__dict__')
True

1
น่าแปลกที่ถ้าisถูกแทนที่==ในการเปรียบเทียบครั้งที่สองนั่นคือA.__dict__ is type.__dict__['__dict__'].__get__(A)ผลลัพธ์อยู่Falseใน python 2.7.15+ และ 3.6.8
Arne Vogel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.