83

What is the difference between type(obj) and obj.__class__? Is there ever a possibility of type(obj) is not obj.__class__?

I want to write a function that works generically on the supplied objects, using a default value of 1 in the same type as another parameter. Which variation, #1 or #2 below, is going to do the right thing?

def f(a, b=None):
  if b is None:
    b = type(a)(1) # #1
    b = a.__class__(1) # #2
Charles Merriam
  • 19,908
  • 6
  • 73
  • 83

5 Answers5

77

This is an old question, but none of the answers seems to mention that. in the general case, it IS possible for a new-style class to have different values for type(instance) and instance.__class__:

class ClassA(object):
    def display(self):
        print("ClassA")

class ClassB(object):
    __class__ = ClassA

    def display(self):
        print("ClassB")

instance = ClassB()

print(type(instance))
print(instance.__class__)
instance.display()

Output:

<class '__main__.ClassB'>
<class '__main__.ClassA'>
ClassB

The reason is that ClassB is overriding the __class__ descriptor, however the internal type field in the object is not changed. type(instance) reads directly from that type field, so it returns the correct value, whereas instance.__class__ refers to the new descriptor replacing the original descriptor provided by Python, which reads the internal type field. Instead of reading that internal type field, it returns a hardcoded value.

Flavien
  • 7,497
  • 10
  • 45
  • 52
  • 24
    _Caveat lector_: this should be taken as an example of why you should avoid overriding `__class__`! You may cause code down the line that uses `__class__` to break. – Benjamin Hodgson Aug 17 '12 at 10:32
  • 7
    Also affected by `__getattribute__`, which intercepts the request for `OBJ.__class__` but not for `type(OBJ)`. – kdb Oct 07 '17 at 20:33
  • 15
    Some code does this deliberately to lie about the type of objects, such as `weakref.proxy`. Some people think `obj.__class__` should be preferred, because it believes the lie, while some people think `type(obj)` should be preferred because it ignores the lie. `isinstance` will return true if an object's real class or its lie class is an instance of the second argument. – user2357112 Feb 16 '19 at 19:51
  • @BenjaminHodgson This mainly shows why magic `__` properties should NOT be override-able in the first place ... – jave.web Mar 23 '21 at 19:10
  • 4
    Important to notice here is that: assigning the `__class__` attribute in an _instance_ **will** effectively change its type, and class "for real" (including changing "type" and "isinstance" responses after the fact) – jsbueno Dec 16 '21 at 17:54
35

Old-style classes are the problem, sigh:

>>> class old: pass
... 
>>> x=old()
>>> type(x)
<type 'instance'>
>>> x.__class__
<class __main__.old at 0x6a150>
>>> 

Not a problem in Python 3 since all classes are new-style now;-).

In Python 2, a class is new-style only if it inherits from another new-style class (including object and the various built-in types such as dict, list, set, ...) or implicitly or explicitly sets __metaclass__ to type.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 5
    It's Python 3 time, which *should* we use? – Nick T Aug 22 '14 at 05:52
  • 5
    If your code is following best practice as described by Alex, `type()` would be preferable. In Python 3, it is always following best practice, so use type() in Python 3. – Russia Must Remove Putin Nov 06 '14 at 15:58
  • @AaronHall Would I be correct for me to assume that using `type()` is also preferred over `__class__` if I am writing Python 2 code where I know that it will only be called with instances of new-style classes? – kasperd Apr 10 '15 at 17:56
  • @kasperd Yes, if your code always inherits from object, you're safe using `type()`. – Russia Must Remove Putin Apr 10 '15 at 18:46
  • 4
    @Alex Martelli @AaronHall The built-in function `type(instance)` and the attribute `instance.__class__` can be different, even with [new-style classes](https://docs.python.org/2/reference/datamodel.html#new-style-and-classic-classes), as Guido mentioned in [PEP 3119](https://www.python.org/dev/peps/pep-3119/#the-abc-module-an-abc-support-framework): "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.)" and as @Flavien's answer illustrated. – Géry Ogam Dec 13 '18 at 10:10
23

type(obj) and type.__class__ do not behave the same for old style classes in Python 2:

>>> class a(object):
...     pass
... 
>>> class b(a):
...     pass
... 
>>> class c:
...     pass
... 
>>> ai = a()
>>> bi = b()
>>> ci = c()
>>> type(ai) is ai.__class__
True
>>> type(bi) is bi.__class__
True
>>> type(ci) is ci.__class__
False
>>> type(ci)
<type 'instance'>
>>> ci.__class__
<class __main__.c at 0x7f437cee8600>

This is explained in the documentation:

For an old-style class, the statement x.__class__ provides the class of x, but type(x) is always <type 'instance'>. This reflects the fact that all old-style instances, independent of their class, are implemented with a single built-in type, called instance.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
Mark Roddy
  • 27,122
  • 19
  • 67
  • 71
5

There's an interesting edge case with proxy objects (that use weak references):

>>> import weakref
>>> class MyClass:
...     x = 42
... 
>>> obj = MyClass()
>>> obj_proxy = weakref.proxy(obj)
>>> obj_proxy.x  # proxies attribute lookup to the referenced object
42
>>> type(obj_proxy)  # returns type of the proxy 
weakproxy
>>> obj_proxy.__class__  # returns type of the referenced object
__main__.MyClass
>>> del obj  # breaks the proxy's weak reference
>>> type(obj_proxy)  # still works
weakproxy
>>> obj_proxy.__class__  # fails
ReferenceError: weakly-referenced object no longer exists
leogama
  • 898
  • 9
  • 13
1

FYI - Django does this.

>>> from django.core.files.storage import default_storage
>>> type(default_storage)
django.core.files.storage.DefaultStorage
>>> default_storage.__class__
django.core.files.storage.FileSystemStorage

As someone with finite cognitive capacity who's just trying to figure out what's going in order to get work done... it's frustrating.

odigity
  • 7,568
  • 4
  • 37
  • 51