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