Thuộc tính __dict __.__ dict__ của một lớp Python là gì?


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?

Nếu tôi làm vậy A.something = 10, điều này sẽ đi vào A.__dict__. Có gì này <attribute '__dict__' of 'A' objects>được tìm thấy trong A.__dict__.__dict__, và khi nào nó chứa một cái gì đó?


11
Một biến ví dụ phù hợp hơn sẽ là ive. Ít nhất nó sẽ đã thực hiện điều này một nhiều A.__dict__['ive']câu hỏi;) Tôi sẽ thấy bản thân mình ra
Joakim

Câu trả lời:


110

Trước hết A.__dict__.__dict__là khác với A.__dict__['__dict__'], và trước đây không tồn tại. __dict__Thuộc tính sau là thuộc tính mà các thể hiện của lớp sẽ có. Đó là một đối tượng mô tả trả về từ điển nội bộ của các thuộc tính cho phiên bản cụ thể. Tóm lại, __dict__thuộc tính của một đối tượng không thể được lưu trữ trong đối tượng __dict__, vì vậy nó được truy cập thông qua một bộ mô tả được xác định trong lớp.

Để hiểu điều này, bạn phải đọc tài liệu về giao thức bộ mô tả .

Phiên bản ngắn:

  1. Đối với một thể hiện của lớp A, quyền truy cập vào instance.__dict__được cung cấp bởi A.__dict__['__dict__']nó giống như vars(A)['__dict__'].
  2. Đối với lớp A, quyền truy cập A.__dict__được cung cấp bởi type.__dict__['__dict__'](trên lý thuyết) giống như vars(type)['__dict__'].

Phiên bản dài:

Cả hai lớp và đối tượng đều cung cấp quyền truy cập vào các thuộc tính thông qua toán tử thuộc tính (được triển khai thông qua lớp hoặc siêu kính __getattribute__) và __dict__thuộc tính / giao thức được sử dụng bởi vars(ob).

Đối với các đối tượng bình thường, __dict__đối tượng tạo một dictđối tượng riêng biệt , lưu trữ các thuộc tính và __getattribute__trước tiên cố gắng truy cập nó và lấy các thuộc tính từ đó (trước khi cố gắng tìm kiếm thuộc tính trong lớp bằng cách sử dụng giao thức bộ mô tả và trước khi gọi __getattr__). Bộ __dict__mô tả trên lớp thực hiện quyền truy cập vào từ điển này.

  • x.nametương đương với cố gắng những theo thứ tự: x.__dict__['name'], type(x).name.__get__(x, type(x)),type(x).name
  • x.__dict__ làm tương tự nhưng bỏ qua cái đầu tiên vì những lý do rõ ràng

Vì nó không thể cho __dict__các instancephải được lưu trữ trong __dict__các ví dụ, nó được truy cập thông qua giao thức mô tả trực tiếp thay vào đó, và được lưu trữ trong một lĩnh vực đặc biệt trong trường hợp.

Một kịch bản tương tự cũng đúng đối với các lớp, mặc dù chúng __dict__là một đối tượng proxy đặc biệt giả vờ là một từ điển (nhưng có thể không nằm trong nội bộ) và không cho phép bạn thay đổi hoặc thay thế nó bằng một từ điển khác. Proxy này cho phép bạn, trong số tất cả những thứ khác, truy cập vào các thuộc tính của một lớp dành riêng cho nó, và không được xác định trong một trong các cơ sở của nó.

Theo mặc định, một vars(cls)lớp trống mang ba bộ mô tả - __dict__để lưu trữ các thuộc tính của các cá thể, __weakref__được sử dụng nội bộ bởi weakrefvà chuỗi tài liệu của lớp. Hai phần đầu tiên có thể biến mất nếu bạn xác định __slots__. Sau đó, bạn sẽ không có __dict____weakref__thuộc tính, nhưng thay vào đó bạn sẽ có một thuộc tính lớp duy nhất cho mỗi vị trí. Các thuộc tính của cá thể sau đó sẽ không được lưu trữ trong từ điển và quyền truy cập vào chúng sẽ được cung cấp bởi các bộ mô tả tương ứng trong lớp.


Và cuối cùng, sự mâu thuẫn A.__dict__khác với A.__dict__['__dict__']là do thuộc tính __dict__, ngoại lệ, không bao giờ được tra cứu vars(A), vì vậy những gì đúng với nó không đúng với bất kỳ thuộc tính nào khác mà bạn sử dụng. Ví dụ, A.__weakref__là điều tương tự như A.__dict__['__weakref__']. Nếu sự mâu thuẫn này không tồn tại, việc sử dụng A.__dict__sẽ không hoạt động và bạn phải luôn sử dụng vars(A)thay thế.


6
Cảm ơn các câu trả lời chi tiết. Mặc dù tôi đã phải đọc nó một vài lần, nhưng tôi nghĩ đã lâu rồi tôi mới học được nhiều chi tiết mới của Python.
porgarmingduod

Tại sao chính xác __dict__thuộc tính của một đối tượng không thể được lưu trữ trong đối tượng __dict__?
zumgruenenbaum

2
@zumgruenenbaum Vì __dict__có nghĩa là để lưu trữ tất cả các thuộc tính của cá thể, một truy cập thuộc tính của biểu mẫu obj.xcuối cùng được tra cứu trên đối tượng __dict__, cụ thể là obj.__dict__['x']. Bây giờ nếu __dict__không được triển khai như một bộ mô tả, điều này sẽ dẫn đến một đệ quy vô hạn, vì để truy cập, obj.__dict__bạn cần phải tra cứu nó dưới dạng obj.__dict__['__dict__']. Bộ mô tả đã giải quyết vấn đề này.
a_guest

11

A.__dict__là một từ điển lưu trữ Acác thuộc tính, A.__dict__['__dict__']là tham chiếu trực tiếp đến cùng một A.__dict__thuộc tính.

A.__dict__chứa một (loại) tham chiếu đến chính nó. Phần "loại" là lý do tại sao biểu thức A.__dict__trả về a dictproxythay vì bình thường 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'

9
A.__dict__['__dict__']không phải là một tham chiếu đến A.__dict__. Nó thực hiện __dict__thuộc tính của các cá thể. Để thử điều này cho chính bạn, A.__dict__['__dict__'].__get__(A(), A)trả về các thuộc tính của A(), trong khi A.__dict__['__dict__'].__get__(A, type)không thành công.
Rosh Oxymoron

10

Hãy khám phá!

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

Tôi tự hỏi đó là gì?

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

getset_descriptorĐối tượng có những thuộc tính nào ?

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

Bằng cách tạo một bản sao của nó, dictproxychúng tôi có thể tìm thấy một số thuộc tính thú vị, cụ thể __objclass____name__.

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

Vì vậy, __objclass__có lẽ là một tham chiếu đến A__name__chỉ là chuỗi '__dict__', tên của một thuộc tính?

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

Chúng tôi có nó! A.__dict__['__dict__']là một đối tượng có thể tham chiếu trở lại A.__dict__.


PEP 252 nói rằng đó __objclass__là lớp đã xác định thuộc tính này, không phải là thuộc tính của lớp đó. Điều này làm cho getattrví dụ của bạn không chính xác. Một câu trả lời đúng hơn sẽ làgetattr(A().__dict__['__dict__'].__objclass__, A.__dict__['__dict__'].__name__)
Rosh Oxymoron

1
@RoshOxymoron Biểu hiện của bạn tăng lên KeyError: '__dict__', trái ngược với @ AndrewClark.
Maggyero

9

Bạn có thể thử ví dụ đơn giản sau để hiểu thêm về điều này:

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

Từ ví dụ trên, có vẻ như các thuộc tính của đối tượng lớp được lưu trữ bởi lớp của chúng, các thuộc tính của lớp được lưu trữ bởi lớp của chúng, đó là các siêu kính. Điều này cũng được xác thực bởi:

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

1
Thật kỳ lạ, nếu isđược thay thế cho ==trong so sánh thứ hai, tức là A.__dict__ is type.__dict__['__dict__'].__get__(A), kết quả là Falsetrong cả python 2.7.15+ và 3.6.8.
Arne Vogel
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.