Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is type(instance) different from instance.__class__?

Python has the built-in function type:

class type(object)

With one argument, return the type of an object. The return value is a type object and generally the same object as returned by object.__class__.

Python also has the special attribute __class__:

instance.__class__

The class to which a class instance belongs.

I used to believe that they both refer to the same object. However in the method abc.ABCMeta.__instancecheck__ there is a check if they are identical:

    def __instancecheck__(cls, instance):
        """Override for isinstance(instance, cls)."""
        # Inline the cache checking
        subclass = instance.__class__
        # […]
        subtype = type(instance)
        if subtype is subclass:
        # […]

When is type(instance) different from instance.__class__?

like image 722
MSeifert Avatar asked Feb 22 '17 07:02

MSeifert


2 Answers

type(instance) and instance.__class__ may be different, even with new-style classes, as Guido van Rossum mentioned in PEP 3119:

Also, isinstance(x, B) is equivalent to issubclass(x.__class__, B) or issubclass(type(x), B). (It is possible type(x) and x.__class__ are not the same object, e.g. when x is a proxy object.)

For example, the function weakref.proxy of the standard library creates proxy objects.

>>> import weakref
>>> class A: pass
... 
>>> a = A()
>>> type(weakref.proxy(a))
<class 'weakproxy'>
>>> weakref.proxy(a).__class__
<class '__main__.A'>
>>> repr(weakref.proxy(a))
'<weakproxy at 0x10065ab30 to A at 0x1006534c0>'

Note that the implementation of the __repr__ method of the proxy object uses type(instance), not instance.__class__, since the primary purpose the __repr__ method is to provide enough information to recreate the object when debugging.

type(instance)

The real class of an object instance is stored on the instance in a __class__ slot (i.e. at a fixed offset in the instance layout). It can only be accessed through the data descriptor vars(object)['__class__'] (whose method __get__ allows attribute retrieval, whose method __set__ allows attribute assignment, and whose method __delete__ forbids attribute deletion), or equivalently through the built-in function type (whose one-argument form allows attribute retrieval):

>>> class A: pass
... 
>>> a = A()
>>> type(a)
<class '__main__.A'>
>>> vars(object)['__class__'].__get__(a)
<class '__main__.A'>
>>> class B: pass
... 
>>> vars(object)['__class__'].__set__(a, B)
>>> type(a)
<class '__main__.B'>
>>> vars(object)['__class__'].__get__(a)
<class '__main__.B'>
>>> vars(object)['__class__'].__delete__(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't delete __class__ attribute

instance.__class__

If the data descriptor vars(object)['__class__'] is not overridden in an object subclass, instance.__class__ accesses the real class of instance through the data descriptor:

>>> class A: pass
... 
>>> a = A()
>>> type(a)
<class '__main__.A'>
>>> a.__class__
<class '__main__.A'>
>>> class B: pass
... 
>>> a.__class__ = B
>>> type(a)
<class '__main__.B'>
>>> a.__class__
<class '__main__.B'>
>>> del a.__class__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't delete __class__ attribute

But if the data descriptor vars(object)['__class__'] is overridden in an object subclass, instance.__class__ does not access the real class of instance. Moreover, if the override is not a data descriptor, it can itself be overridden in instance:

>>> class A: __class__ = int  # overrides vars(object)['__class__']
... 
>>> a = A()
>>> type(a)
<class '__main__.A'>
>>> a.__class__
<class 'int'>
>>> a.__class__ = str  # overrides vars(A)['__class__'] (not a data descriptor)
>>> type(a)
<class '__main__.A'>
>>> a.__class__
<class 'str'>
>>> del a.__class__
>>> type(a)
<class '__main__.A'>
>>> a.__class__
<class 'int'>
like image 68
Maggyero Avatar answered Sep 20 '22 12:09

Maggyero


This is the case for old style objects (inheriting from nothing). Such objects do not have the __class__ property. I think they do it this way to prevent errors. Example for Python 2.7:

class A:
    pass

class B(object):
    pass

a = A()
b = B()

print(dir(a)) # ['__doc__', '__module__']
print(dir(b)) # ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

print(b.__class__) # <class '__main__.B'>
print(type(b))     # <class '__main__.B'>

#####################
# The intersting part
print(a.__class__) # __main__.A
print(type(a))     # <type 'instance'>


print(B.__class__) # <type 'type'>
print(type(B))     # <type 'type'>

print(type(A))      # <type 'classobj'>
#print(A.__class__) # AttributeError: class A has no attribute '__class__'

See this for further information:

  • Python type() or __class__, == or is
  • NewClassVsClassicClass
  • Why does `type(myField)` return `<type 'instance'>` and not `<type 'Field'>`?

Note: The given lines from cpython were changed in 2008 last time (commit), so it really seems to be a compatibility thing or they just forgot about it.

like image 42
ppasler Avatar answered Sep 17 '22 12:09

ppasler