110

I have a function that take an argument which can be either a single item or a double item:

def iterable(arg)
    if #arg is an iterable:
        print "yes"
    else:
        print "no"

so that:

>>> iterable( ("f","f") )
yes

>>> iterable( ["f","f"] )
yes

>>> iterable("ff")
no

The problem is that string is technically iterable, so I can't just catch the ValueError when trying arg[1]. I don't want to use isinstance(), because that's not good practice (or so I'm told).

priestc
  • 33,060
  • 24
  • 83
  • 117
  • 1
    Which version of Python? I believe the answer is different between 2.* and 3 – Kathy Van Stone Jun 28 '09 at 17:47
  • 5
    You were told incorrectly, isinstance is not bad practice. – Lennart Regebro Jun 28 '09 at 20:37
  • 3
    Oh, wait, maybe he refers to the principle that it's bad to check an objects type, and that this is an indication of the program being broken? This is true in principle (but not always in practice). This may or may not be such a case. But it's not the function isinstance that is the problem, it's the habit of checking for types. – Lennart Regebro Jun 28 '09 at 20:45
  • @Lennart: http://www.canonical.org/~kragen/isinstance/ it may be outdated though – priestc Jun 30 '09 at 03:47
  • @up This doesn't mention type-based function overloading though, and `isinstance` is the way to do it in dynamically typed languages. A thing not to be used everyday, but OK in justified cases. – Kos Nov 02 '12 at 19:00
  • Strings is not iterable. Or that depends on how you define iterable. But at least strings does not have the `__iter__` attribute. So use `hasattr(x, '__iter__')` (look at my more detailed answer) – elgehelge Jan 08 '14 at 14:12

11 Answers11

55

Use isinstance (I don't see why it's bad practice)

import types
if not isinstance(arg, types.StringTypes):

Note the use of StringTypes. It ensures that we don't forget about some obscure type of string.

On the upside, this also works for derived string classes.

class MyString(str):
    pass

isinstance(MyString("  "), types.StringTypes) # true

Also, you might want to have a look at this previous question.

Cheers.


NB: behavior changed in Python 3 as StringTypes and basestring are no longer defined. Depending on your needs, you can replace them in isinstance by str, or a subset tuple of (str, bytes, unicode), e.g. for Cython users. As @Theron Luhn mentionned, you can also use six.

Community
  • 1
  • 1
scvalex
  • 14,931
  • 2
  • 34
  • 43
  • Nice, scvalex. I'm removing my -1 now and making it a +1 :-). – Tom Jun 28 '09 at 18:19
  • 2
    I think the bad practice idea is because of the [duck typing](http://en.wikipedia.org/wiki/Duck_typing) principle. Being a member of a particular class does neither mean that it's the _only_ object that can be used nor that the expected methods are available. But I think sometimes you just can't infer what the method does even if it's present, so `isinstance` might be the only way. – estani Dec 05 '12 at 15:19
  • 3
    Note: types.StringTypes is not available in Python 3. Since there's only one string type in py3k, I think it's safe to `do isinstance(arg, str)`. For a backwards-compatible version, consider using https://pythonhosted.org/six/#six.string_types – Theron Luhn Jan 17 '15 at 00:54
  • I strictly use Python3 and noticed `types.StringTypes` is not available in Python3. What is the value in Python2? – kevinarpe Apr 19 '15 at 04:31
  • 2
    **2017**: This answer is not valid anymore, see https://stackoverflow.com/a/44328500/99834 for one that works with all versions of Python. – sorin Jun 21 '17 at 11:47
  • Doesn't actual check if the object is iterable and not something else e.g. an int. – Danny Varod Jul 05 '20 at 16:44
38

As of 2017, here is a portable solution that works with all versions of Python:

#!/usr/bin/env python
import collections
import six


def iterable(arg):
    return (
        isinstance(arg, collections.Iterable) 
        and not isinstance(arg, six.string_types)
    )


# non-string iterables    
assert iterable(("f", "f"))    # tuple
assert iterable(["f", "f"])    # list
assert iterable(iter("ff"))    # iterator
assert iterable(range(44))     # generator
assert iterable(b"ff")         # bytes (Python 2 calls this a string)

# strings or non-iterables
assert not iterable(u"ff")     # string
assert not iterable(44)        # integer
assert not iterable(iterable)  # function
Nick T
  • 25,754
  • 12
  • 83
  • 121
sorin
  • 161,544
  • 178
  • 535
  • 806
  • There's some minor inconsistencies between 2/3 with bytestrings, but if you use the native "string" then they're both false – Nick T Aug 15 '18 at 16:28
  • 8
    Why not remove the dependency on six assuming everybody in 2021 is on python3. Use `isinstance(arg, str)` instead of `isinstance(arg, six.string_types)` – Charlie Feb 05 '21 at 15:23
  • 2
    Importing `Iterable` from `collections` has been removed in Python 3.10. Import it from `collections.abc` instead. – CoffeeBasedLifeform Mar 09 '23 at 10:19
17

Since Python 2.6, with the introduction of abstract base classes, isinstance (used on ABCs, not concrete classes) is now considered perfectly acceptable. Specifically:

from abc import ABCMeta, abstractmethod

class NonStringIterable:
    __metaclass__ = ABCMeta

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, C):
        if cls is NonStringIterable:
            if any("__iter__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

This is an exact copy (changing only the class name) of Iterable as defined in _abcoll.py (an implementation detail of collections.py)... the reason this works as you wish, while collections.Iterable doesn't, is that the latter goes the extra mile to ensure strings are considered iterable, by calling Iterable.register(str) explicitly just after this class statement.

Of course it's easy to augment __subclasshook__ by returning False before the any call for other classes you want to specifically exclude from your definition.

In any case, after you have imported this new module as myiter, isinstance('ciao', myiter.NonStringIterable) will be False, and isinstance([1,2,3], myiter.NonStringIterable)will be True, just as you request -- and in Python 2.6 and later this is considered the proper way to embody such checks... define an abstract base class and check isinstance on it.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • In Python 3 `isinstance('spam', NonStringIterable)` returns `True`. – Nick T Apr 22 '14 at 06:30
  • 2
    *(...) and in Python 2.6 and later this is considered the proper way to embody such checks (...)* How abusing well known concept of abstract class in such a way could ever be considered *the proper way* is beyond my comprehension. The proper way would be to introduce some *lookslike* operator instead. – Piotr Dobrogost Oct 23 '14 at 09:54
  • Alex, can you address Nick's assertion that this doesn't work in Python 3? I like the answer, but would like to make sure I'm writing future-proof code. – Merlyn Morgan-Graham Feb 02 '15 at 07:08
  • @MerlynMorgan-Graham, that's correct, because `__iter__` **is** now implemented in strings in Python 3. So my "easy to augment" paragraph becomes applicable and e.g `if issublass(cls, str): return False` needs to be added at the start of `__subclasshook__` (as well as any other classes which define `__iter__` but in your mindset must not be accepted as "non-string iterables"). – Alex Martelli Feb 02 '15 at 15:25
  • @AlexMartelli For Python 3, don't you mean that `if issublass(C, str): return False` should be added? – Rob Smallshire Aug 14 '18 at 09:27
  • 1
    @AlexMartelli Is this answer still up-to-date for Python 3[-only], assuming you add the `if issublass(C, str): return False` part at the start of `__subclasshook__` as discussed in the comments? I notice that the implementation of `Iterable`, now in `_collections_abc.py`, has changed so it also checks that the required `__iter__` method isn't just `None`. And am I right in thinking this is equivalent to using `isinstance(arg, collections.Iterable) and not isinstance(arg, str)`, except it just gives you a single ABC to check? – Tim Jan 03 '20 at 17:15
7

By combining previous replies, I'm using:

import types
import collections

#[...]

if isinstance(var, types.StringTypes ) \
    or not isinstance(var, collections.Iterable):

#[Do stuff...]

Not 100% fools proof, but if an object is not an iterable you still can let it pass and fall back to duck typing.


Edit: Python3

types.StringTypes == (str, unicode). The Phython3 equivalent is:

if isinstance(var, str ) \
    or not isinstance(var, collections.Iterable):

Edit: Python3.3

types.StringTypes == (str, unicode). The Phython3 equivalent is:

if isinstance(var, str ) \
    or not isinstance(var, collections.abc.Iterable):
xvan
  • 4,554
  • 1
  • 22
  • 37
  • 1
    Your import statement should be 'types' not 'type' – PaulR Feb 13 '15 at 14:56
  • 2
    I got "DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working" Change collections.Iterable to collections.abc.Iterable. It won't let me edit! – Ethan Bradford Apr 22 '21 at 17:31
4

I realise this is an old post but thought it was worth adding my approach for Internet posterity. The function below seems to work for me under most circumstances with both Python 2 and 3:

def is_collection(obj):
    """ Returns true for any iterable which is not a string or byte sequence.
    """
    try:
        if isinstance(obj, unicode):
            return False
    except NameError:
        pass
    if isinstance(obj, bytes):
        return False
    try:
        iter(obj)
    except TypeError:
        return False
    try:
        hasattr(None, obj)
    except TypeError:
        return True
    return False

This checks for a non-string iterable by (mis)using the built-in hasattr which will raise a TypeError when its second argument is not a string or unicode string.

Nigel Small
  • 4,475
  • 1
  • 17
  • 15
4

2.x

I would have suggested:

hasattr(x, '__iter__')

or in view of David Charles' comment tweaking this for Python3, what about:

hasattr(x, '__iter__') and not isinstance(x, (str, bytes))

3.x

the builtin basestring abstract type was removed. Use str instead. The str and bytes types don’t have functionality enough in common to warrant a shared base class.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
mike rodent
  • 14,126
  • 11
  • 103
  • 157
  • 3
    Perhaps because `__iter__` is there on strings in Python 3? – davidrmcharles Jan 21 '16 at 23:43
  • @DavidCharles Oh, really? My bad. I'm a Jython user and Jython currently has no version 3. – mike rodent Jan 23 '16 at 11:36
  • This isn't really an answer, more a comment/question, and it's wrong for 3.x. Could you clean it up? Can you add justification for claiming *"The 'str' and 'bytes' types don’t have functionality enough in common to warrant a shared base class."* One of the key points of 3.x was to make Unicode bytes a first-class citizen. – smci Jun 28 '20 at 06:07
  • I haven't a clue why I wrote any of the above. I propose to delete all the text under "3.x" ... although you've already edited my answer. Edit it more if you like. – mike rodent Jun 30 '20 at 13:56
2

To explicitly expand on Alex Martelli's excellent hack of collections.py and address some of the questions around it: The current working solution in python 3.6+ is

import collections
import _collections_abc as cabc
import abc


class NonStringIterable(metaclass=abc.ABCMeta):

    __slots__ = ()

    @abc.abstractmethod
    def __iter__(self):
        while False:
            yield None

    @classmethod
    def __subclasshook__(cls, c):
        if cls is NonStringIterable:
            if issubclass(c, str):
                return False
            return cabc._check_methods(c, "__iter__")
        return NotImplemented

and demonstrated

>>> typs = ['string', iter(''), list(), dict(), tuple(), set()]
>>> [isinstance(o, NonStringIterable) for o in typs]
[False, True, True, True, True, True]

If you want to add iter('') into the exclusions, for example, modify the line

            if issubclass(c, str):
                return False

to be

            # `str_iterator` is just a shortcut for `type(iter(''))`*
            if issubclass(c, (str, cabc.str_iterator)):
                return False

to get

[False, False, True, True, True, True]
Alexander McFarlane
  • 10,643
  • 9
  • 59
  • 100
0

As you point out correctly, a single string is a character sequence.

So the thing you really want to do is to find out what kind of sequence arg is by using isinstance or type(a)==str.

If you want to realize a function that takes a variable amount of parameters, you should do it like this:

def function(*args):
    # args is a tuple
    for arg in args:
        do_something(arg)

function("ff") and function("ff", "ff") will work.

I can't see a scenario where an isiterable() function like yours is needed. It isn't isinstance() that is bad style but situations where you need to use isinstance().

Otto Allmendinger
  • 27,448
  • 7
  • 68
  • 79
  • 4
    Using `type(a) == str` shall be avoided. It's a bad practice because it does not take into account similar types or types derived from `str`. `type` does not climb the type hierarchy, while `isinstance` does, therefore it is better to use `isinstance`. – AkiRoss Jun 20 '15 at 11:17
0

Adding another answer here that doesn't require extra imports and is maybe more "pythonic", relying on duck typing and the fact that str has had a unicode casefold method since Python 3.

def iterable_not_string(x):
    '''
    Check if input has an __iter__ method and then determine if it's a
    string by checking for a casefold method.
    '''
    try:
        assert x.__iter__

        try:
            assert x.casefold
            # could do the following instead for python 2.7 because
            # str and unicode types both had a splitlines method
            # assert x.splitlines
            return False

        except AttributeError:
            return True

    except AttributeError:
        return False
shortorian
  • 1,082
  • 1
  • 10
  • 19
0

Python 3.X

Notes:

  • You need implement "isListable" method.
  • In my case dict is not iterable because iter(obj_dict) returns an iterator of just the keys.
  • Sequences are iterables, but not all iterables are sequences (immutable, mutable).
  • set, dict are iterables but not sequence.
  • list is iterable and sequence.
  • str is an iterable and immutable sequence.

Sources:

See this example:

from typing import Iterable, Sequence, MutableSequence, Mapping, Text

class Custom():
    pass

def isListable(obj):
    if(isinstance(obj, type)): return isListable(obj.__new__(obj))
    return isinstance(obj, MutableSequence)

try:
    
    # Listable
    #o = [Custom()]
    #o = ["a","b"]
    #o = [{"a":"va"},{"b":"vb"}]
    #o = list # class type
    
    # Not listable
    #o = {"a" : "Value"}
    o = "Only string"
    #o = 1
    #o = False
    #o = 2.4
    #o = None
    #o = Custom()
    #o = {1, 2, 3} #type set
    #o = (n**2 for n in {1, 2, 3})
    #o = bytes("Only string", 'utf-8')
    #o = Custom # class type
    
    if isListable(o):
        print("Is Listable[%s]: %s" % (o.__class__, str(o)))
    else:
        print("Not Listable[%s]: %s" % (o.__class__, str(o)))
    
except Exception as exc:
    raise exc
nmdaz
  • 99
  • 1
  • 3
0

If you like to test if the variable is a iterable object and not a "string like" object (str, bytes, ...) you can use the fact that the __mod__() function exists in such "string like" objects for formatting proposes. So you can do a check like this:

>>> def is_not_iterable(item):
...    return hasattr(item, '__trunc__') or hasattr(item, '__mod__')
>>> is_not_iterable('')
True
>>> is_not_iterable(b'')
True
>>> is_not_iterable(())
False
>>> is_not_iterable([])
False
>>> is_not_iterable(1)
True
>>> is_not_iterable({})
False
>>> is_not_iterable(set())
False
>>> is_not_iterable(range(19)) #considers also Generators or Iterators
False
B.R.
  • 234
  • 2
  • 7