18

To explain better, consider this simple type checker function:

from collections import Iterable
def typecheck(obj):
    return not isinstance(obj, str) and isinstance(obj, Iterable)

If obj is an iterable type other than str, it returns True. However, if obj is a str or a non-iterable type, it returns False.

Is there any way to perform the type check more efficiently? I mean, it seems kinda redundant to check the type of obj once to see if it is not a str and then check it again to see if it is iterable.

I thought about listing every other iterable type besides str like this:

return isinstance(obj, (list, tuple, dict,...))

But the problem is that that approach will miss any other iterable types that are not explicitly listed.

So...is there anything better or is the approach I gave in the function the most efficient?

aIKid
  • 26,968
  • 4
  • 39
  • 65
  • Just curious. Why do you consider string as a non-iterable? – thefourtheye Nov 13 '13 at 01:45
  • 5
    It's not inefficient. You likely have more important things to worry about. – arshajii Nov 13 '13 at 01:45
  • 5
    @thefourtheye - Because sometimes you need to check if something is iterable but isn't a string. An example could be a function that flattens a nested list. You don't want the strings in it flattened as well. –  Nov 13 '13 at 01:47
  • 1
    @iCodez: But you want the dicts flattened? Or the files? In this specific case, it seems more reasonable to specifically check for lists. That said, there are legitimate use cases where you want to accept either a string or a sequence of strings, and you want to distinguish between the two. – user2357112 Nov 13 '13 at 01:49
  • Something like `return isinstance(obj, (list, tuple, dict,...))` will be far more inefficient since it has to check all the types, isn't it? – aIKid Nov 13 '13 at 02:03
  • 1
    If you exclude str, usually you should exclude unicode and buffer too – Leonardo.Z Nov 13 '13 at 02:05
  • TBH, I think you're already on the right track. – aIKid Nov 13 '13 at 02:06
  • 3
    @aIKid - Yea, I didn't like the `isinstance(obj, (list, tuple, dict,...))` solution too much either. I was just curious if there was any way to do this without calling `isinstance` twice. But now I'm beginning to think that that _is_ actually the best way. –  Nov 13 '13 at 02:17
  • It *is*. It has to check it only twice, for each types. No solution can do it better. – aIKid Nov 13 '13 at 02:32

1 Answers1

15

In python 2.x, Checking for the __iter__ attribute was helpful (though not always wise), because iterables should have this attribute, but strings did not.

def typecheck(obj): return hasattr(myObj, '__iter__')

The down side was that __iter__ was not a truely Pythonic way to do it: Some objects might implement __getitem__ but not __iter__ for instance.

In Python 3.x, strings got the __iter__ attribute, breaking this method.

The method you listed is the most efficient truely Pythonic way I know in Python 3.x:

def typecheck(obj): return not isinstance(obj, str) and isinstance(obj, Iterable)

There is a much faster (more efficient) way, which is to check __iter__ like in Python 2.x, and then subsequently check str.

def typecheck(obj): return hasattr(obj, '__iter__') and not isinstance(obj, str)

This has the same caveat as in Python 2.x, but is much faster.

Pi Marillion
  • 4,465
  • 1
  • 19
  • 20
  • There is still the distinct problem that `isinstance(u'foo', str)` returns `False` and so you end up with the same conundrum as explicitly listing all the types which are *not* string types. As a quick hack, I came up with checking `hasattr(thing, 'lower')` but it's obviously too obscure. – tripleee Apr 19 '16 at 07:52
  • 1
    @tripleee - `isinstance(u'foo', basestring)` does the trick in Python 2.x (both `unicode` and `str` subclass `basestring`) – ukrutt Nov 11 '16 at 14:10