Search code examples
pythoncpython

type's behavior with weakref - can't understand


I always believed that in Python interpreter values of x.__class__ and type(x) are equivalent. But if we do the following (in Python 2.7, 3.3 and also PyPy 2.0b1):

>>> import weakref
>>> x = set()
>>> y = weakref.proxy(x)
>>> x.__class__, isinstance(x, set), type(x)
(<type 'set'>, True, <type 'set'>)
>>> y.__class__, isinstance(y, set), type(y)
(<type 'set'>, True, <type 'weakproxy'>)

we'll see that y.__class__ corresponds to the wrapped type for weakref.proxy (I suppose that weakref.proxy just replaces the attribute for disguise). Even isinstance identifies y as set.

But type shows the "true" type -- weakproxy. So, type doesn't use __class__ attribute to identify an argument's type, does it? Does it use some "more reliable" source for this purpose? If so, can we access it directly?


Solution

  • x.__class__ and type(x) are not equivalent. type(x) is rooted in typeobject.c, and will return the true type ob_type.

    /* Special case: type(x) should return x->ob_type */

    While x.__class__ is just an attribute lookup. It is equivalent to object.__getattribute__(x, '__class__'), unless the attribute lookup has been redefined.
    object's '__class__' is a data descriptor, which is also defined in typeobject.c. Its getter returns ob_type as well. Thus, in most cases, x.__class__ and type(x) return the same thing.

    But weakproxy, namely _PyWeakref_ProxyType, deliberately defined its own proxy_getattr. That's why y.__class__ is not the same as type(y) in your case.

    In the following experiment, we can achieve the same effect.

    class A(object):
        pass
    
    class C(object):
        def __getattribute__(self, name):
            if name == '__class__':
                return A
            return object.__getattribute__(self, name)
    
    
    >>> c = C()
    >>> c.__class__
    <class '__main__.A'>
    >>> type(c)
    <class '__main__.C'>
    

    Moreover, isinstance(c, A) and isinstance(c, C) are both true in this example. Since isinstance would check the equality of ob_type first.