31

A question of semantics, really.

Up until recently, if I had to do any typechecking on a structure, I would use type(obj) is list et. al. However since joining SO I've noticed everyone (and I mean EVERYONE) uses isinstance(obj,list) instead. It seems they are synonymous, and timeit reveals almost IDENTICAL speed between them.

def a(): return type(list()) is list
def b(): return isinstance(list(),list)

from timeit import timeit
timeit(a)
# 0.5239454597495582
timeit(b)
# 0.5021292075273176

Indeed even dis agrees they're synonymous, with the exception of type is's COMPARE_OP

from dis import dis

dis(a)
# 2           0 LOAD_GLOBAL              0 (type) 
#             3 LOAD_GLOBAL              1 (list) 
#             6 CALL_FUNCTION            0 (0 positional, 0 keyword pair) 
#             9 CALL_FUNCTION            1 (1 positional, 0 keyword pair) 
#            12 LOAD_GLOBAL              1 (list) 
#            15 COMPARE_OP               8 (is) 
#            18 RETURN_VALUE

dis(b)
# 2           0 LOAD_GLOBAL              0 (isinstance)
#             3 LOAD_GLOBAL              1 (list) 
#             6 CALL_FUNCTION            0 (0 positional, 0 keyword pair) 
#             9 LOAD_GLOBAL              1 (list) 
#            12 CALL_FUNCTION            2 (2 positional, 0 keyword pair) 
#            15 RETURN_VALUE 

I frankly find it more readable to say if type(foo) is list: than if isinstance(foo,list):, the first is basically just pseudo-code and the second calls some function (which I have to look up every time to be isinstance or instanceof) with some arguments. It doesn't look like a type cast, and there's no explicit way of knowing whether isinstance(a,b) is checking if b is an instance of a or vice-versa.

I understand from this question that we use isinstance because it's nicer about inheritance. type(ClassDerivedFromList) is list will fail while isinstance(ClassDerivedFromList,list) will succeed. But if I'm checking what should ALWAYS BE A BASE OBJECT, what do I really lose from doing type is?

Community
  • 1
  • 1
Adam Smith
  • 52,157
  • 12
  • 73
  • 112

5 Answers5

20

if I'm checking what should ALWAYS BE A BASE OBJECT, what do I really lose from doing type is?

well, it's nice you give the full documented answer in your question, so your answer is you lose nothing! The only times where isinstance() is necessary is when checking inheritance of a given class compared to another, as you well said and referenced. type() shall be only used to check whether an instance is exactly of a given base type.

Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
zmo
  • 24,463
  • 4
  • 54
  • 90
  • I'm specifically thinking about refactoring a function I wrote about 6 months ago that accepts either a string or a list of strings. The only input to that function will EVER be a `str` or a `list`, so is doing `if type(foo) is str: _handle_str(foo) else: _handle_list(foo)` really bad practice? – Adam Smith Feb 19 '14 at 23:25
  • 3
    @adsmith: do you use Python 3? If not, your code just broke if you wind up with a unicode string. – DSM Feb 20 '14 at 00:00
  • @DSM I do, but I do appreciate the pitfalls of `unicode` vs `str`. – Adam Smith Feb 20 '14 at 00:05
  • or `isinstance(foo, (str,unicode))` for that matter! – Adam Smith Feb 20 '14 at 00:15
  • 3
    or you could simply do `if type(foo) is list: _handle_list(foo) else: _handle_str(foo)`. @DSM is totally right, to handle strings correctly, you need to check `isinstance(foo, basestring)`. And if I were you I'd actually check `if type(foo) is list: _handle_list(foo) elif isinstance(foo, basestring): _handle_str(foo) else raise Exception("Type Not Handled")`. – zmo Feb 20 '14 at 10:53
  • 1
    Your lib will be more easily adopted by others, or migrated to new features, if you are more tolerant of other sequence types than just list, most notably tuple and set. Accepting generator expressions in place of your list is also forward-thinking. – PaulMcG May 01 '18 at 14:07
  • indeed, instead of `type(list)`, checking if it's an instance of iterable is always a better idea ☺ – zmo May 01 '18 at 19:50
5

Other than the inheritance issue, you also lose the ability to test multiple types when using isinstance. For example:

def chk(typ):
    if not isinstance(typ, (str, int)):
        raise ValueError('typ must be string or int')
    ...
Matteo Italia
  • 123,740
  • 17
  • 206
  • 299
user590028
  • 11,364
  • 3
  • 40
  • 57
5

The Python Docs for the built-in type function provide clear guidance on the difference between type (with one arg) and isinstance.

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. The isinstance() built-in function is recommended for testing the type of an object, because it takes subclasses into account.

To illustrate, take a look at the inheritance hierarchy below in module diamond:

enter image description here

Then take a look at the python console output below based on module diamond:

enter image description here

As per the documentation, it is more versatile and can cover a wider range of scenarios. With respect to the OP's original question - the performance characteristics were not stated as a valid reason to favour one over the other. Neither was readability.

For a more in-depth discussion, which includes an explanation on (in the words of the answerer) "why checking type equality is an even worse practice in recent Python versions than it already used to be", please see this highly voted answer

Community
  • 1
  • 1
arcseldon
  • 35,523
  • 17
  • 121
  • 125
  • I appreciate the answer, but the only part of the question you talk about here is one that I answer in my question. "I understand...that we use `isinstance` because it's nicer about inheritance. `type(ClassDerivedFromList) is list` will fail while `isinstance(ClassDerivedFromList, list)` will succeed" – Adam Smith Nov 27 '15 at 17:57
  • @AdamSmith - as pointed out in my answer, the docs clearly explain the difference. " The isinstance() built-in function is recommended for testing the type of an object, because it takes subclasses into account." I am clarifying your uncertainty - and why you had noticed it was overwhelmingly the popular choice. It is more versatile and can cover a wider range of scenarios - type() would suffice where you know the exact type. Also wished to illustrate this answer with some code examples to clarify for others. – arcseldon Nov 28 '15 at 06:00
  • If you down-vote, I would be grateful if you could offer an explanation so I can amend / improve the answer if deemed necessary. Thanks! – arcseldon Dec 06 '15 at 13:43
  • I didn't downvote, but this answer is specifically about the part the question explicitly did not ask about. Seems pretty weird to me. The question asks specifically about the situation when one wants to test an exact type, and the answer talks about how `isinstance` is better when one doesn't want to test an exact type. – ShreevatsaR Dec 05 '16 at 20:59
  • My point exactly, thanks for the helpful comment - isInstance is more versatile hence the popularity. performance characteristics were not stated as a reason to favour one over the other. Hence most people opt for the more versatile option unless they "need" to do otherwise. – arcseldon Dec 05 '16 at 21:23
1

type(x) doesn't work with mypy

At least for me, the main reason for preferring isinstance over type(x) is because mypy can infer types from isinstance checks but it can't with type(x). From the docs:

Mypy can usually infer the types correctly when using isinstance type tests, but for other kinds of checks you may need to add an explicit type cast:

def f(o: object) -> None:
    if type(o) is int:
        o = cast(int, o)
        g(o + 1)    # This would be an error without the cast
        ...
    else:
        ...

Source: https://mypy.readthedocs.io/en/stable/common_issues.html?highlight=isinstance#complex-type-tests

Cesar Canassa
  • 18,659
  • 11
  • 66
  • 69
0

The answer to your question is:
No, given that you're checking for a definite base class, as you loose the ability to test for inherited classes.

And IMO isinstance is nicer to read and in Python: Readability counts.

PS: I'm getting a significant difference in timings (on Python 3.3)

      type: 0.5241982917936874  
isinstance: 0.46066255811928847
pradyunsg
  • 18,287
  • 11
  • 43
  • 96