Python: check if an object is a sequence

47,533

Solution 1

iter(x) will raise a TypeError if x cannot be iterated on -- but that check "accepts" sets and dictionaries, though it "rejects" other non-sequences such as None and numbers.

On the other hands, strings (which most applications want to consider "single items" rather than sequences) are in fact sequences (so, any test, unless specialcased for strings, is going to confirm that they are). So, such simple checks are often not sufficient.

In Python 2.6 and better, abstract base classes were introduced, and among other powerful features they offer more good, systematic support for such "category checking".

>>> import collections
>>> isinstance([], collections.Sequence)
True
>>> isinstance((), collections.Sequence)
True
>>> isinstance(23, collections.Sequence)
False
>>> isinstance('foo', collections.Sequence)
True
>>> isinstance({}, collections.Sequence)
False
>>> isinstance(set(), collections.Sequence)
False

You'll note strings are still considered "a sequence" (since they are), but at least you get dicts and sets out of the way. If you want to exclude strings from your concept of "being sequences", you could use collections.MutableSequence (but that also excludes tuples, which, like strings, are sequences, but are not mutable), or do it explicitly:

import collections

def issequenceforme(obj):
    if isinstance(obj, basestring):
        return False
    return isinstance(obj, collections.Sequence)

Season to taste, and serve hot!-)

PS: For Python 3, use str instead of basestring, and for Python 3.3+: Abstract Base Classes like Sequence have moved to collections.abc.

Solution 2

For Python 3 and 2.6+, you can check if it's a subclass of collections.Sequence:

>>> import collections
>>> isinstance(myObject, collections.Sequence)
True

In Python 3.7 you must use collections.abc.Sequence (collections.Sequence will be removed in Python 3.8):

>>> import collections.abc
>>> isinstance(myObject, collections.abc.Sequence)
True

However, this won't work for duck-typed sequences which implement __len__() and __getitem__() but do not (as they should) subclass collections.Sequence. But it will work for all the built-in Python sequence types: lists, tuples, strings, etc.

While all sequences are iterables, not all iterables are sequences (for example, sets and dictionaries are iterable but not sequences). Checking hasattr(type(obj), '__iter__') will return True for dictionaries and sets.

Solution 3

Since Python "adheres" duck typing, one of the approach is to check if an object has some member (method).

A sequence has length, has sequence of items, and support slicing [doc]. So, it would be like this:

def is_sequence(obj):
    t = type(obj)
    return hasattr(t, '__len__') and hasattr(t, '__getitem__')
    # additionally: and hasattr(t, '__setitem__') and hasattr(t, '__delitem__')

They are all special methods, __len__() should return number of items, __getitem__(i) should return an item (in sequence it is i-th item, but not with mapping), __getitem__(slice(start, stop, step)) should return subsequence, and __setitem__ and __delitem__ like you expect. This is such a contract, but whether the object really do these or not depends on whether the object adheres the contract or not.

Note that, the function above will also return True for mapping, e.g. dict, since mapping also has these methods. To overcome this, you can do a heavier work:

def is_sequence(obj):
    try:
        len(obj)
        obj[0:0]
        return True
    except TypeError:
        return False

But most of the time you don't need this, just do what you want as if the object is a sequence and catch an exception if you wish. This is more pythonic.

Solution 4

For the sake of completeness. There is a utility is_sequence in numpy library ("The fundamental package for scientific computing with Python").

>>> from numpy.distutils.misc_util import is_sequence
>>> is_sequence((2,3,4))
True
>>> is_sequence(45.9)
False

But it accepts sets as sequences and rejects strings

>>> is_sequence(set((1,2)))
True
>>> is_sequence("abc")
False

The code looks a bit like @adrian 's (See numpy git code), which is kind of shaky.

def is_sequence(seq):
    if is_string(seq):
        return False
    try:
        len(seq)
    except Exception:
        return False
    return True

Solution 5

The Python 2.6.5 documentation describes the following sequence types: string, Unicode string, list, tuple, buffer, and xrange.

def isSequence(obj):
    return type(obj) in [str, unicode, list, tuple, buffer, xrange]
Share:
47,533

Related videos on Youtube

nicotine
Author by

nicotine

Updated on July 05, 2022

Comments

  • nicotine
    nicotine almost 2 years

    In python is there an easy way to tell if something is not a sequence? I tried to just do: if x is not sequence but python did not like that

    • miku
      miku almost 14 years
      Related: In python, how do I determine if a variable is Iterable? stackoverflow.com/questions/1952464/…
    • Alex Martelli
      Alex Martelli almost 14 years
      Yep, but while all sequences are iterables not all iterables are sequences (sets and dicts are built-in iterable containers that are not sequences, for example).
  • Mike Graham
    Mike Graham almost 14 years
    Note that this code example will return the wrong result for objects that implement the sequence protocol but do not involve the collections.Sequence ABC.
  • nicotine
    nicotine almost 14 years
    It's an assignment constraint. If the argument we are passed isn't an int, long or sequence we need to raise a TypeError
  • Alex Martelli
    Alex Martelli almost 14 years
    Yep: differently from simpler ABCs, Sequence doesn't implement a __subclasshook__ class method, so it will never automatically recognize a class that chose not to register with it (or inherit from it) -- it would be essentially impossible to tell by introspection whether a class's __getitem__ accepts integers and slices, raises IndexError on wrong indices, etc -- all you need to rule out dict and set, essentially (that do seem to "implement the sequence protocol" if you just do introspection... but then turn out not to!-).
  • Mike Graham
    Mike Graham almost 14 years
    @nicotine, recognize that this assignment indicates a design that is usually nonidiomatic and fragile. Consider the normal case, where an object should be exactly one type of thing. If a parameter is supposed to be a sequence but you get an int, when you index or iterate over it you will already get a TypeError. Similarly, if you tried to do integer operations with a sequence you would.
  • Mike Graham
    Mike Graham almost 14 years
    Providing a consistent API (when at all possible) that expects just one type of thing lets you write simpler code which still raises errors when something goes wrong but is more robust in that it supports types you didn't think about but satisfy the true goal (for example, some custom class that implements __len__ and __getitem__ would work as a sequence.)
  • intuited
    intuited almost 14 years
    The problem with this answer is that it won't detect sequences that aren't builtin types. A Python "sequence" is any object that implements the methods necessary to respond to sequence operations.
  • user2357112
    user2357112 almost 9 years
    Sets are pretty easy to rule out, since they don't have __getitem__, but mappings are much harder. The best check I've seen is probably to look for keys, like dict.update does, but that still leaves a lot to be desired.
  • antred
    antred over 8 years
    @MikeGraham To provide an example for where such a thing might be useful, I'm trying to write a function that can be given either a single object of one type or a list (or tuple) of such objects, and I need to be able to detect whether the caller gave me a sequence or just a single object. I know it's debatable whether or not this is good design, but at the moment I can't think of anything that speaks against it.
  • Mike Graham
    Mike Graham over 8 years
    @antred, The better solution by far is to have a function do one thing, not multiple things. Always require a sequence, for instance. (You can make a second function with a different API.) The problem is that there's no Good way to detect the difference in Python and that it's harder to understand functions that do multiple things instead of one thing.
  • bfontaine
    bfontaine over 7 years
    A set has a length but isn’t a sequence.
  • bfontaine
    bfontaine over 7 years
    Also, use isinstance instead of type to support subclasses.
  • Christopher Barber
    Christopher Barber over 7 years
    The important point about dict is that if you treat it like a sequence you will just get the keys, not the values, and information will be lost.
  • asciiphil
    asciiphil over 7 years
    Sometimes it's unavoidable. I'm dealing with a situation where I'm processing JSON data where a particular value might be either a string, an integer, or a list of integers. I can't change the JSON generator, so I have to handle the data as it comes to me.
  • augurar
    augurar about 7 years
    If using hasattr(), you need to check the type of the object for the magic methods, not the object itself. See the Python 2 and Python 3 documentation on how special methods are looked up.
  • flutefreak7
    flutefreak7 almost 7 years
    So for my custom type to be detected as a Sequence I have to subclass Sequence?
  • Visionscaper
    Visionscaper over 6 years
    @MikeGraham Without checking you will indeed get a TypeError exception at some point, however this throws me out of my normal flow of my code. By checking I can mitigate this issue with much more control of what to do in this invalid situation. This is actually a discussion about wether or not to use exceptions as a error handling mechanism. I have always been against it, there are also multiple modern languages that don't support exceptions (any)more. Unfortunately, using exceptions is the Pythonic way, so idiomatically you are (unfortunately) right.
  • tricasse
    tricasse over 6 years
    __setitem__ and __delitem__ would only apply to mutable sequences (e.g. not tuple or string)
  • spinkus
    spinkus almost 6 years
    Good example of the caveat mentioned is numpy arrays, which have all the required properties of a Sequence but fail to recognized as such by isinstance. If this doesn't even work for numpy arrays, seems pretty hopeless.
  • ivan_pozdeev
    ivan_pozdeev almost 6 years
    In most practical cases, str should not be considered a sequence, that's why hasattr(str,'__iter__') returns false.
  • John Strood
    John Strood over 5 years
    @AlexMartelli reversed needs a sequence. OrderedDict implements reversed, and yet isinstance(OrderedDict(), Sequence) is False. I wonder why.
  • devaerial
    devaerial almost 4 years
    you can also use typing.Sequence
  • loved.by.Jesus
    loved.by.Jesus over 3 years
    @bfontaine is right, but in some cases this code might be useful.
  • MestreLion
    MestreLion over 2 years
    To rule out all strings, I'd use (str, collections.abc.ByteString), or bytes will pass issequenceforme
  • Jason S
    Jason S almost 2 years
    ugh, and it would return True for dicts as well, since len() works on them