Type of an object:

Steven D'Aprano steve+comp.lang.python at pearwood.info
Tue Dec 17 18:51:08 EST 2013


On Wed, 18 Dec 2013 11:15:03 +1300, Gregory Ewing wrote:

> Steven D'Aprano wrote:
>> Well, that is a surprise, but I don't think that is intended behaviour.
>> I think that's something which only works by accident. The intention is
>> that __class__ returns the instance's type, not arbitrary values.
> 
> Well, a proxy object would obviously return a suitable class-like
> object. I was just demonstrating that it's possible to override what
> __class__ returns.

You can certainly do it with a __getattribute__ method:

py> class K(object):
...     def __getattribute__(self, name):
...             if name == '__class__': return 42
...             return super().__getattribute__(name)
...
py> k = K()
py> k.__class__
42

but I think that counts as "shoot yourself in the foot" category.


> I don't think it's an accident, because the weakref module uses this for
> its proxy objects.

Just a minute, we seem to be talking about completely different things 
here. You demonstrated setting __class__ to a non-class object inside a 
class statement, emphasis on the *non-class* part. Here's a simpler 
version showing the same thing:

py> class Q(object):
...     __class__ = 42
...
py> q = Q()
py> q.__class__
42
py> type(q)
<class '__main__.Q'>


Here's an equivalent way:

py> Q = type("Q", (object,), {'__class__': 23})
py> Q().__class__
23

It's the *non-class* part I reckon is an accident, or a bug. Telling me 
that weakproxy sets __class__ to a *class* doesn't argue for or against 
me.

Ignoring virtual __getattribute__ attributes, I cannot see any other way 
to set the __class__ of an object to be a non-class. (I suppose you can 
do anything you like with a metaclass, but that falls firmly into 
"consenting adults" territory.) Unless you "break" access to the 
__class__ descriptor first, as in the Q class above, there doesn't seem 
to be any way to set it to a non-class or an incompatible class. You 
can't set it on the class:

py> class C(object):
...     pass
...
py> C.__class__ = 23
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __class__ must be set to a class, not 'int' object
py> C.__dict__['__class__'] = 23
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment


nor can you set it on the instance:

py> c = C()
py> c.__class__ = 23
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __class__ must be set to a class, not 'int' object


You can set it on the instance dict directly, but it doesn't do you any 
good because the descriptor overrides it:

py> c.__dict__['__class__'] = 23
py> c.__class__
<class '__main__.C'>


Somebody has gone to a *lot* of trouble to ensure that __class__ always 
returns an actual class, and I'm not sure the Q example above is a 
deliberate hole in that.


Your weakref example is not a counter-example:

>  >>> import weakref
>  >>> class C(object):
> ...  pass
> ...
>  >>> c = C()
>  >>> p = weakref.proxy(c)
>  >>> p.__class__
> <class '__main__.C'>
>  >>> type(p)
> <type 'weakproxy'>


since both weakproxy and C are classes. This is a good example though of 
when type(obj) and obj.__class__ can legitimately differ.

This leads to another question: we've now seen two examples where 
(presumably) the internal type field and __class__ differ. In the 
weakproxy case, type(obj) returns the internal type field. In the 
"regular" case, where you set obj.__class__ to a class, type(obj) returns 
the new (external) type. How the hell does it decide which one to return?



-- 
Steven



More information about the Python-list mailing list