[issue32683] isinstance is calling ob.__getattribute__ as a fallback instead of object.__class__.__get__

Steven D'Aprano report at bugs.python.org
Fri Dec 10 06:41:23 EST 2021


Steven D'Aprano <steve+python at pearwood.info> added the comment:

The plot thickens.

I was wrong to say that type() always and only looks at the "real" underlying type of the instance. type() does look at __class__ as well. But only sometimes.

>>> class A:
...     __class__ = int
... 
>>> type(A())
<class '__main__.A'>
>>> a = A()
>>> a.__class__ = int
>>> type(a)
<class '__main__.A'>

So from A, we might think that type() ignores __class__

>>> class B:
...     pass
... 
>>> type(B())  # no __class__ to inspect
<class '__main__.B'>
>>> b = B()
>>> b.__class__ = int
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __class__ assignment only supported for mutable types or ModuleType subclasses

How very odd. But okay, let's try something else:

>>> b.__class__ = A  # lie that this is an A not a B
>>> type(b)
<class '__main__.A'>

So now type() *does* inspect __class__, and believes it.

If we generate the __class__ attribute dynamically with __getattr__, the method doesn't even get called. But if we use __getattribute__:

>>> class C:
...     def __getattribute__(self, name):
...             print('C getattribute:', name)
...             if name == '__class__':
...                     return int
...             raise AttributeError
... 
>>> C().__class__
C getattribute: __class__
<class 'int'>
>>> type(C())
<class '__main__.C'>

type() ignores the dynamically generated __class__. But isinstance does not:

>>> isinstance(C(), int)
C getattribute: __class__
True


The same applies if we use property:

>>> class D:
...     @property
...     def __class__(self):
...             return int
... 
>>> type(D())
<class '__main__.D'>
>>> isinstance(D(), int)
True


So the rules appear to be:

- you can set __class__, but only sometimes;
- type() will believe __class__, but only sometimes;
- you can generate __class__ dynamically, but not with __getattr__;
- isinstance() will believe __class__ (always?);
- and there is no indication of how much of this is deliberate language feature and how much is an accident of implementation.

----------

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue32683>
_______________________________________


More information about the Python-bugs-list mailing list