Data model and attribute resolution in subclasses

Terry Reedy tjreedy at udel.edu
Fri Feb 28 13:04:23 EST 2020


On 2/28/2020 2:21 AM, Adam Preble wrote:
> I have been making some progress on my custom interpreter project but I found I have totally blown implementing proper subclassing in the data model. What I have right now is PyClass defining what a PyObject is. When I make a PyObject from a PyClass, the PyObject sets up a __dict__ that is used for attribute lookup. When I realized I needed to worry about looking up parent namespace stuff, this fell apart because my PyClass had no real notion of a namespace.
> 
> I'm looking at the Python data model for inspiration. While I don't have to implement the full specifications, it helps me where I don't have an alternative. However, the data model is definitely a programmer document; it's one of those things where the prose is being very precise in what it's saying and that can foil a casual reading.
> 
> Here's what I think is supposed to exist:
> 1. PyObject is the base.
> 2. It has an "internal dictionary." This isn't exposed as __dict__

The internal mapping *is* visible.

 >>> object.__dict__
mappingproxy({'__repr__': <slot wrapper '__repr__' of 'object' objects>, 
'__hash__': <slot wrapper '__hash__' of 'object' objects>, '__str__': 
<slot wrapper '__str__' of 'object' objects>, '__getattribute__': <slot 
wrapper '__getattribute__' of 'object' objects>, '__setattr__': <slot 
wrapper '__setattr__' of 'object' objects>, '__delattr__': <slot wrapper 
'__delattr__' of 'object' objects>, '__lt__': <slot wrapper '__lt__' of 
'object' objects>, '__le__': <slot wrapper '__le__' of 'object' 
objects>, '__eq__': <slot wrapper '__eq__' of 'object' objects>, 
'__ne__': <slot wrapper '__ne__' of 'object' objects>, '__gt__': <slot 
wrapper '__gt__' of 'object' objects>, '__ge__': <slot wrapper '__ge__' 
of 'object' objects>, '__init__': <slot wrapper '__init__' of 'object' 
objects>, '__new__': <built-in method __new__ of type object at 
0x00007FFBEFE989A0>, '__reduce_ex__': <method '__reduce_ex__' of 
'object' objects>, '__reduce__': <method '__reduce__' of 'object' 
objects>, '__subclasshook__': <method '__subclasshook__' of 'object' 
objects>, '__init_subclass__': <method '__init_subclass__' of 'object' 
objects>, '__format__': <method '__format__' of 'object' objects>, 
'__sizeof__': <method '__sizeof__' of 'object' objects>, '__dir__': 
<method '__dir__' of 'object' objects>, '__class__': <attribute 
'__class__' of 'object' objects>, '__doc__': 'The base class of the 
class hierarchy.\n\nWhen called, it accepts no arguments and returns a 
new featureless\ninstance that has no instance attributes and cannot be 
given any.\n'})

The internal mapping is not an instance of dict, and need/should not be 
as it is frozen.

 >>> o = object()
 >>> o.a = 3
Traceback (most recent call last):
   File "<pyshell#2>", line 1, in <module>
     o.a = 3
AttributeError: 'object' object has no attribute 'a'

When classes and objects do have a dict __dict__, __dict__ is not in 
__dict__, to avoid recursion.  gettattribute or __getattribute__ must 
special case '__dict__'.  I am guessing this as what must be from the 
evidence, without seeing the actual code.

> 3. PyClass subclasses PyObject.
> 4. PyClass has a __dict__
> 
> Is there a term for PyObject's internal dictionary. It wasn't called __dict__ and I think that's for good reasons. I guess the idea is a PyObject doesn't have a namespace, but a PyClass does (?).
> 
> Now to look something up. I assume that __getattribute__ is supposed to do something like:
> 1. The PyClass __dict__ for the given PyObject is consulted.
> 2. The implementation for __getattribute__ for the PyObject will default to looking into the "internal dictionary."
> 3. Assuming the attribute is not found, the subclasses are then consulted using the subclass' __getattribute__ calls. We might recurse on this. There's probably some trivia here regarding multiple inheritance; I'm not entirely concerned (yet).

For non-reserved names Attribute lookup starts with the object dict, 
then object class, then superclasses.  Dunder names usually start with 
the object class.

> 4. Assuming it's never found, then the user sees an AttributeError
> 
> Would each of these failed lookups result in an AttributeError?

I presume so.  They are caught and only re-raised only if there is no 
where else to look.

-- 
Terry Jan Reedy



More information about the Python-list mailing list