4

Following piece of code lists the attributes of a class named 'A' in a sorted order:-

>>> class A():
        def __init__(self, i):
            self.at = i

>>> sorted(vars(A))
['__dict__', '__doc__', '__init__', '__module__', '__weakref__']

Now, printing the value of key, '__dict__' results this:-

>>> vars(A)['__dict__']                 #Value of '__dict__'
<attribute '__dict__' of 'A' objects>

As per docs, vars([object])

Return the __dict__ attribute for a module, class, instance, or any other object with a __dict__ attribute.


What I am not understanding is that is the '__dict__' attribute in the list the same attribute used by vars() to return the attributes of A or is it a different attribute which has some another objective like implementing A's objects' namespace as suggested (according to me) by the value which '__dict__' holds.


Edit:-

The first part of the question is very much related to this other question (also, mentioned by @eliotness) but it's the second part (described below) for which I can't find any answers or related question and hence, changing title of the question.


Let's consider another code that produces list of attributes of ultimate base class in Python, object:-

>>> sorted(vars(object))
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', ...., '__str__', '__subclasshook__']
>>> hasattr(object, '__dict__')
True
>>> sorted(getattr(object, '__dict__')) == sorted(vars(object))
True

Another quotation from docs about object.__dict__

A dictionary or other mapping object used to store an object’s (writable) attributes.


This time, '__dict__' doesn't show up in the list of object. So, is it that the __dict__ attribute is a read-only attribute in case of object or any other reason?

Also, is it possible to get a list of read-only attributes in Python in any way?


Perspicacious
  • 562
  • 3
  • 12

2 Answers2

2

Firstly I want to say that I only partly know the answer to your question. The second part may not be entirely true on the behaviour of python. But first, I want to clear some things out: the docs you are quotings are concerning normal objects and may not apply on meta-classes as their internal behaviour is entirely coded in C.

vars(A)['dict']

As I understand how python works, I suspect the __dict__ you saw here:

>>> vars(A)['__dict__']                 #Value of '__dict__'
<attribute '__dict__' of 'A' objects>

I suspect it to be a sample for the __dict__ attribute of a future instance of the class. I don't have better answer for now but let's keep digging.

The __dict__ attribute on meta-classes

As object is a of class type is is pretty normal for it not to have a __dict__ as it is not possible to add attribute on basic types like int, float, or str. So why would these types have a __dict__ attribute if it is not used?

And finally, yes. There are restrictions on the __dict__ attribute of classes and meta-classes. These restrictions take form as the class MappingProxy like said in the vars documentation:

Objects such as modules and instances have an updateable __dict__ attribute; however, other objects may have write restrictions on their __dict__ attributes (for example, classes use a types.MappingProxyType to prevent direct dictionary updates).

But this behaviour is also explicitly said in the doc of the class object

Edit: I started to find good information about the wierd vars(A)['__dict__']. Take a look here

eliottness
  • 116
  • 3
  • @eliotness, after taking a look at the link you've given at the end it seems that the `'__dict__'` is a different attribute which will be used for the instances of the class. But upon reading about `object` base class in the docs, the note given at the end of it confuses me a bit. > Note: object does not have a `__dict__`, so you can’t assign arbitrary attributes to an instance of the object class. – Perspicacious Jul 04 '20 at 14:06
  • Continuing....I mean the code `hasattr(object, '__dict__')` returns `True` (I have written it in the question also). Also, `vars(object)` works fine. So how much is this statement "`object` doesn't have a `__dict__` attribute" accurate? – Perspicacious Jul 04 '20 at 14:06
  • @Perspicacious, one thing we are sure is that the doc of the `object` class is ambiguous and in this end we don't know if they talk about instances of `object` or the class itself. Concerning the `__dict__` attribute of the class `object`, It may have to do with the descriptors of the class. I'm checking if I find something in my books, but I am afraid that we will have to look for the base implementation of `__call__` and `__new__` of the class `object` in CPython. – eliottness Jul 04 '20 at 16:17
  • @eliotness, I think the doc about `object` is about class `object` itself and not about it's instances but even if it's about the instances of the `object` itself (which every object in Python is), it doesn't seem accurate to me. – Perspicacious Jul 05 '20 at 09:53
  • At this point of time, what I think is that one can look up to `__dict__` attr of `object` using every standard way which otherwise, is used to access `__dict__` of any other normal object. It's just that it's a **read-only** attribute (I mean like class objects, printing it's `__dict__` also gives a `mappingproxy` object) and until now, I couldn't find a way to get a *list of read-only attributes of an object*. – Perspicacious Jul 05 '20 at 10:00
  • I hope this thread gets a bit more views because, right now, I haven't got much answers. – Perspicacious Jul 05 '20 at 10:02
2

The first part of your question is already answered by the linked answer: the __dict__ of instances is stored as a descriptor on the class. This is the A.__dict__['__dict__']. A.__dict__ on the other hand stores all the attributes of the A class - which itself is an instance of type. So actually it's type.__dict__['__dict__'] that provides these variables:

>>> type.__dict__['__dict__']
<attribute '__dict__' of 'type' objects>
>>> A.__dict__ == type.__dict__['__dict__'].__get__(A)
True

The reason why you're not seeing a __dict__ attribute on object is because it doesn't have one. This means you can't set instance variables on object instances:

>>> o = object()
>>> o.x = 1

AttributeError: 'object' object has no attribute 'x'

Similar behavior can be achieved for custom classes by defining __slots__:

>>> class B:
...     __slots__ = ()
... 
>>> vars(B)
mappingproxy({'__module__': '__main__', '__slots__': (), '__doc__': None})
>>> 
>>> b = B()
>>> b.x = 1

AttributeError: 'B' object has no attribute 'x'
a_guest
  • 25,051
  • 7
  • 38
  • 80
  • I don't think I can agree on this that "The reason why you're not seeing a `__dict__` attribute on object is because it doesn't have one" because in that case, `hasattr(object, '__dict__')` would have returned `False` but instead, it returns `True` and thus, `vars(object)` returns a `mappingproxy` type dictionary "which prevents direct dictionary updates" (as mentioned in the docs). I strongly feel that the `__dict__` of `object` is a _read-only attribute_ and so, no instance vars can be set to `object` instances and doesn't appear in `vars(object)` as well. – Perspicacious Jul 14 '20 at 09:15
  • 1
    @Perspicacious What I meant is that `object` doesn't have a `__dict__` *descriptor* (similar to `A.__dict__['__dict__']`). Of course the object `object` has its own `__dict__` similar to `A.__dict__`. They both originate from `type` which provides the corresponding descriptor. `object.__dict__` is indeed read-only since it's a mappingproxy but this holds for any instance of `type`. The reason why you can't set instance variables on `object` instances however is really because they don't have a `__dict__`. Try the following: `o = object(); o.__dict__`. You'll get an AttributeError. – a_guest Jul 14 '20 at 10:33
  • (just to confirm) by saying this, "They both originate from `type` which provides the corresponding descriptor." were you referring to this `type.__dict__['__dict__']`?? Also, as I asked in my question, is there any way to see _the list of read-only attributes_ because `__dict__` just shows the writable attributes of an object. – Perspicacious Jul 14 '20 at 13:07
  • 1
    @Perspicacious I was referring to `A.__dict__` and `object.__dict__`. Both are instances of `type` and hence the `__dict__` attribute comes from the descriptor `type.__dict__['__dict__']`. Regarding `object.__dict__` the docs rather mean that this is the place that holds writable attributes - though not all writable attributes must appear there. You can define a property on the class and it won't show up in `__dict__`. Read-only attributes are always descriptors on the class so they never show up in `__dict__`. You can check `dir(obj)` and then see what's available there. – a_guest Jul 14 '20 at 13:17
  • `dir(obj)` though takes object's bases' attributes into account. – Perspicacious Jul 14 '20 at 13:23
  • @Perspicacious So that's a good thing, isn't it? So you get to see all the descriptors that this object is governed by. In the end `obj.attr` *is* influenced by the full MRO, i.e. all base classes of `obj`. – a_guest Jul 14 '20 at 14:21