485

This is what I normally do in order to ascertain that the input is a list/tuple - but not a str. Because many times I stumbled upon bugs where a function passes a str object by mistake, and the target function does for x in lst assuming that lst is actually a list or tuple.

assert isinstance(lst, (list, tuple))

My question is: is there a better way of achieving this?

kenorb
  • 155,785
  • 88
  • 678
  • 743
Sridhar Ratnakumar
  • 81,433
  • 63
  • 146
  • 187

20 Answers20

329

In python 2 only (not python 3):

assert not isinstance(lst, basestring)

Is actually what you want, otherwise you'll miss out on a lot of things which act like lists, but aren't subclasses of list or tuple.

sorin
  • 161,544
  • 178
  • 535
  • 806
Nick Craig-Wood
  • 52,955
  • 12
  • 126
  • 132
  • 97
    Yes, this is the correct answer. In Python 3, `basestring` is gone, and you just check for `isinstance(lst, str)`. – steveha Dec 02 '09 at 19:11
  • What other types act like lists other than `list` and `tuple`? – Sridhar Ratnakumar Dec 04 '09 at 01:34
  • 5
    There are lots of things you can iterate like lists, eg `set`, generator expressions, iterators. There are exotic things like `mmap`, less exotic things like `array` which act pretty much like lists, and probably lots more I've forgotten. – Nick Craig-Wood Dec 04 '09 at 06:57
  • 1
    My only objection to this answer is that: what if - in future - there is yet another sequence type that would pass the "for x in seq" syntax? – Sridhar Ratnakumar Dec 21 '09 at 02:30
  • 56
    It's worth noting that this doesn't guarantee that `lst` is iterable, whilst the original did (eg an int would pass this check) – Peter Gibson Feb 28 '12 at 04:25
  • 11
    @PeterGibson - A combination of the two will provide a valid, more restrictive check and ensure 1) lst is iterable, 2) lst is not a string. `assert isinstance(lst, (list, tuple)) and assert not isinstance(lst, basestring)` – strongMA Jun 19 '13 at 21:34
  • 6
    Well, this solution only checks for string derived types, but what about integers, doubles or any other non-iterable type? – Eneko Alonso Aug 04 '13 at 21:58
  • 1
    This doesn't check for iterable - not sure why so many upvotes – L__ Aug 18 '16 at 20:09
  • @PeterGibson I believe asserts can be ignored for "releases" by doing something with _ _ debug _ _. That can insert sneaky bugs for releases. – DevPlayer Dec 09 '16 at 16:42
  • 2
    Funny that this has been accepted and upvoted so much, given that it does not actually answer the question that well at all. Anyone coming here should check the answer by @suzanshakya below for a better one. – Petri Feb 15 '17 at 13:22
  • 2
    with ``assert not isinstance(lst, basestring)``, anything not a string will pass this test, like dicts, floats, ints. – thet Sep 30 '17 at 21:52
  • Assert throws an exception. But this answer does not mention exception handling at all. – Duane Oct 05 '18 at 18:58
175

Remember that in Python we want to use "duck typing". So, anything that acts like a list can be treated as a list. So, don't check for the type of a list, just see if it acts like a list.

But strings act like a list too, and often that is not what we want. There are times when it is even a problem! So, check explicitly for a string, but then use duck typing.

Here is a function I wrote for fun. It is a special version of repr() that prints any sequence in angle brackets ('<', '>').

def srepr(arg):
    if isinstance(arg, basestring): # Python 3: isinstance(arg, str)
        return repr(arg)
    try:
        return '<' + ", ".join(srepr(x) for x in arg) + '>'
    except TypeError: # catch when for loop fails
        return repr(arg) # not a sequence so just return repr

This is clean and elegant, overall. But what's that isinstance() check doing there? That's kind of a hack. But it is essential.

This function calls itself recursively on anything that acts like a list. If we didn't handle the string specially, then it would be treated like a list, and split up one character at a time. But then the recursive call would try to treat each character as a list -- and it would work! Even a one-character string works as a list! The function would keep on calling itself recursively until stack overflow.

Functions like this one, that depend on each recursive call breaking down the work to be done, have to special-case strings--because you can't break down a string below the level of a one-character string, and even a one-character string acts like a list.

Note: the try/except is the cleanest way to express our intentions. But if this code were somehow time-critical, we might want to replace it with some sort of test to see if arg is a sequence. Rather than testing the type, we should probably test behaviors. If it has a .strip() method, it's a string, so don't consider it a sequence; otherwise, if it is indexable or iterable, it's a sequence:

def is_sequence(arg):
    return (not hasattr(arg, "strip") and
            hasattr(arg, "__getitem__") or
            hasattr(arg, "__iter__"))

def srepr(arg):
    if is_sequence(arg):
        return '<' + ", ".join(srepr(x) for x in arg) + '>'
    return repr(arg)

EDIT: I originally wrote the above with a check for __getslice__() but I noticed that in the collections module documentation, the interesting method is __getitem__(); this makes sense, that's how you index an object. That seems more fundamental than __getslice__() so I changed the above.

steveha
  • 74,789
  • 21
  • 92
  • 117
  • 2
    @stantonk, thank you for saying so, but I think that there was already an accepted answer when I wrote this and I don't really expect the accepted answer to be changed. – steveha May 25 '12 at 04:29
  • @steveha: `srepr` is a very interesting idea. But I hold a different opinion than you on whether it needs to special-case `str`. Yes, `str` is by far the most obvious and common iterable that would cause an infinite recursion in `srepr`. But I can easily imagine user-defined iterables that behave in the same way (with or without good reason). Rather than special-case `str`, we should admit that this approach may run into an infinite recursion, and agree to some way of dealing with it. I'll post my suggestion in an answer. – max Oct 29 '12 at 07:19
  • 1
    I think this is definitely the right path. However, to handle the special case (of string in this scenario), I think we're better off asking the question "how would a human tell the difference?" For example, consider a function argument that can be a list of email addresses or a single email address (keeping in mind that a string is simply a list of characters). Give this variable to a human. How could the tell which it is? The easiest way I can think of is to see how many characters are in each item of the list. If it's greater than 1, the argument certainly can't be a list of characters. – Josh Dec 13 '12 at 16:56
  • 1
    I've thought about this a bit, and discussed it with a few other people, and I think `srepr()` is fine as-is. We need a recursive function to handle things like a list nested inside another list; but for strings we would rather have them printed as `"foo"` than as `<'f', 'o', 'o'>`. So an explicit check for a string makes good sense here. Also, there really aren't any other examples of data types where iterating *always* returns an iterable and recursion will always cause stack overflow, so we don't need a special property to test for this ("Practicality beats purity"). – steveha Mar 21 '13 at 18:43
  • @steveha: While is_sequence is a pretty nice function, it includes dict, but OP asked only for detection of list/tuple args... (checking for __getslice__ ensured this constrain) – Vajk Hermecz Dec 03 '13 at 16:26
  • It's worth noting that it's probably better to use getattr instead of hasattr. http://grokbase.com/t/python/python-3000/081pag24aq/exception-swallowing-behaviour-of-hasattr – Dustin Wyatt Jan 08 '14 at 18:08
  • this doesn't work for xrange. I added a 'not isinstance(arg, type(xrange))' to my `is_sequence` function. – wdkrnls Apr 04 '14 at 20:51
  • srepr scares me for exceptionally large iterables or generators – FlipMcF Aug 14 '14 at 22:58
  • Having an `__iter__` attribute insufficiently restrictive. Since the OP specifically asked about lists and tuples, `X = {4:3, 2:1}` doesn't pass the quack test since `list(X)` throws away half the information in X. In order for X to act like a list, it must be possible to losslessly convert X to a list and vice versa. Dicts don't quack. Nor do sets, since X = [3, 1, 2] does not equal `list(set(X))` – Dave Jun 01 '16 at 16:16
  • 1
    This does not work in Python 3, because strings have an `__iter__()` method in Python 3, but not in Python 2. You are missing parentheses in `is_sequence()`, it should read: `return (not hasattr(arg, "strip") and (hasattr(arg, "__getitem__") or hasattr(arg, "__iter__")))` – MiniQuark Apr 06 '17 at 07:46
125
H = "Hello"

if type(H) is list or type(H) is tuple:
    ## Do Something.
else
    ## Do Something.
llrs
  • 3,308
  • 35
  • 68
  • @tr33hous - Probably because it doesn't add anything that the other answers don't already. – ArtOfWarfare Oct 30 '14 at 17:00
  • 12
    Except it doesn't use the Python idiom of duck typing as other commenters have pointed out (though it does answer the question directly and cleanly). – Soren Bjornstad Sep 18 '15 at 23:54
  • 8
    This answer less acceptable than others because it doesn't allow for duck typing and also it fails in the simple case of subclassing (a typical example is the namedtuple class). –  Feb 18 '16 at 19:07
  • 13
    "Not allowing for duck typing" does not make the answer any less acceptable, especially given that this answer actually answers the question. – Petri Feb 15 '17 at 13:24
  • 7
    I've upvoted this answer, but `if isinstance( H, (list, tuple) ): ...` is shorter and clearer. – shahar_m Jul 06 '17 at 13:53
  • 1
    It is not ideomatic. It works and it looks quite readable actually, but it is different from how all other code that I've read so far solves this common problem. And this generally should be avoided. – Torsten Bronger Nov 15 '17 at 11:17
  • 2
    Will not treat a **subclass** of a list as if it were a list. – Bob Stein Feb 07 '18 at 01:21
  • 6
    Alternative syntax: `if type(H) in [list, tuple]:` – Štefan Schindler Aug 08 '18 at 18:51
  • This answer is clearly better, because it handles the case without throwing an exception! – Duane Oct 05 '18 at 18:59
  • This answer **is wrong**. As already noted it doesn't respect subclasses of list and tuple. But any sub-type instance **IS** super-type instance. Code that acts otherwise violates Liskov Substitution Principle (even though the violation is happening outside of class). Python developer have full right to expect LSP being respected everywhere as common pattern. – Mikhail Gerasimov Jan 08 '19 at 11:45
109

Python 3:

import collections.abc

if isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str):
    print("`obj` is a sequence (list, tuple, etc) but not a string or a dictionary.")

Changed in version 3.3: Moved global namespace of "Collections Abstract Base Classes" from abc to collections.abc module. For backwards compatibility, they will continue to be visible in this module as well until version 3.8 where it will stop working.

Python 2:

import collections

if isinstance(obj, collections.Sequence) and not isinstance(obj, basestring):
    print "`obj` is a sequence (list, tuple, etc) but not a string or unicode or dictionary."
suzanshakya
  • 3,524
  • 1
  • 26
  • 22
  • 5
    Wow! This works really nicely, and is much much succinct than any of the other correct answers. I had no idea that the built-in types inherit from `collections.Sequence` but I tested it and I see that they do. So does `xrange`. Even better, this test excludes `dict`, which has both `__getitem__` and `__iter__`. – Neil Mayhew Jun 28 '16 at 15:25
  • Any idea why the result of `inspect.getmro(list)` does not include `Sequence` though? How are we supposed to figure out what we can do with `isinstance` when `getmro` doesn't show everything? – Steve Jorgensen Nov 02 '17 at 00:56
  • 1
    @SteveJorgensen Method Resolution Order defines the class search path used by Python to search for the right method to use in classes. `Sequence` is an abstract class. – suzanshakya Feb 27 '18 at 12:36
  • 3
    In Python3, you can replace isinstance(obj, basestring) with isinstance(obj, str), and that should work. – Adrian Keister Mar 05 '18 at 21:36
  • 3
    in Python 3 you need and not isinstance(obj, bytes) ... if you want a list of things, and not just to enumerate bytes... – Erik Aronesty Mar 08 '19 at 14:26
  • 2
    In Python 3 you actually want `and not isinstance(obj, (str, collections.abc.ByteString))` – sparkyb Sep 09 '20 at 16:48
  • 1
    @pdp `tuple` is not mutable and hence not a `MutableSequence`. – suzanshakya Apr 08 '21 at 07:40
40

Python with PHP flavor:

def is_array(var):
    return isinstance(var, (list, tuple))
Cesar
  • 4,418
  • 2
  • 31
  • 37
  • 6
    Python is a duck-typed language, so you really should check if var has attribute `__getitem__`. Also the name is misleading, as there is also array module. And the var could also be a numpy.ndarray or any other type, which has `__getitem__`. See http://stackoverflow.com/a/1835259/470560 for the correct answer. – peterhil Aug 26 '12 at 12:03
  • 11
    @peterhil `str` also has `__getitem__` therefore your check doesn't exclude `str` – erikbstack Jan 23 '15 at 13:50
  • 11
    So does a dict. Checking for `__getitem__` is bad advice here. – Petri Feb 15 '17 at 13:18
11

Generally speaking, the fact that a function which iterates over an object works on strings as well as tuples and lists is more feature than bug. You certainly can use isinstance or duck typing to check an argument, but why should you?

That sounds like a rhetorical question, but it isn't. The answer to "why should I check the argument's type?" is probably going to suggest a solution to the real problem, not the perceived problem. Why is it a bug when a string is passed to the function? Also: if it's a bug when a string is passed to this function, is it also a bug if some other non-list/tuple iterable is passed to it? Why, or why not?

I think that the most common answer to the question is likely to be that developers who write f("abc") are expecting the function to behave as though they'd written f(["abc"]). There are probably circumstances where it makes more sense to protect developers from themselves than it does to support the use case of iterating across the characters in a string. But I'd think long and hard about it first.

Robert Rossney
  • 94,622
  • 24
  • 146
  • 218
  • 20
    "But I'd think long and hard about it first." I wouldn't. If the function is *supposed to be* a list-y function, then yes, it should treat them the same (i.e., given a list, spit it out backwards, things like that). However, if it is a function where one of the arguments can be either a string or a list of strings (which is a pretty common need) then forcing the developer using that function to *always* enter their parameter inside of an array seems a bit much. Also, think about how you'd handle, say, JSON input. You'd definitely want to treat a lists of objects different from a string. – Jordan Reiter Nov 29 '11 at 00:55
10

Try this for readability and best practices:

Python2 - isinstance()

import types
if isinstance(lst, types.ListType) or isinstance(lst, types.TupleType):
    # Do something

Python3 - isinstance()

import typing
if isinstance(lst, typing.List) or isinstance(lst, typing.Tuple):
    # Do something

Hope it helps.

Om Prakash
  • 2,675
  • 4
  • 29
  • 50
6

This is not intended to directly answer the OP, but I wanted to share some related ideas.

I was very interested in @steveha answer above, which seemed to give an example where duck typing seems to break. On second thought, however, his example suggests that duck typing is hard to conform to, but it does not suggest that str deserves any special handling.

After all, a non-str type (e.g., a user-defined type that maintains some complicated recursive structures) may cause @steveha srepr function to cause an infinite recursion. While this is admittedly rather unlikely, we can't ignore this possibility. Therefore, rather than special-casing str in srepr, we should clarify what we want srepr to do when an infinite recursion results.

It may seem that one reasonable approach is to simply break the recursion in srepr the moment list(arg) == [arg]. This would, in fact, completely solve the problem with str, without any isinstance.

However, a really complicated recursive structure may cause an infinite loop where list(arg) == [arg] never happens. Therefore, while the above check is useful, it's not sufficient. We need something like a hard limit on the recursion depth.

My point is that if you plan to handle arbitrary argument types, handling str via duck typing is far, far easier than handling the more general types you may (theoretically) encounter. So if you feel the need to exclude str instances, you should instead demand that the argument is an instance of one of the few types that you explicitly specify.

max
  • 49,282
  • 56
  • 208
  • 355
  • 1
    Hmm, I like the way you think. I think you cannot argue that my code is practical: there is exactly one common case, `str`, that the special-case code handles. But maybe there should be a new standard property that code can inspect, `.__atomic__` let's say, that signals that something cannot be broken down any further. It's probably too late to add another builtin function `atomic()` to Python, but maybe we can add `from collections import atomic` or something. – steveha Oct 29 '12 at 19:21
6

I find such a function named is_sequence in tensorflow.

def is_sequence(seq):
  """Returns a true if its input is a collections.Sequence (except strings).
  Args:
    seq: an input sequence.
  Returns:
    True if the sequence is a not a string and is a collections.Sequence.
  """
  return (isinstance(seq, collections.Sequence)
and not isinstance(seq, six.string_types))

And I have verified that it meets your needs.

Lerner Zhang
  • 6,184
  • 2
  • 49
  • 66
5

The str object doesn't have an __iter__ attribute

>>> hasattr('', '__iter__')
False 

so you can do a check

assert hasattr(x, '__iter__')

and this will also raise a nice AssertionError for any other non-iterable object too.

Edit: As Tim mentions in the comments, this will only work in python 2.x, not 3.x

Moe
  • 28,607
  • 10
  • 51
  • 67
  • 11
    Careful: In Python 3 `hasattr('','__iter__')` returns `True`. And of course that makes sense since you can iterate over a string. – Tim Pietzcker Dec 02 '09 at 20:23
  • 1
    Really? I didn't know that. I always thought this was an elegant solution to the problem, oh well. – Moe Dec 02 '09 at 21:08
  • 1
    This test didn't work on pyodbc.Row. It has no __iter__() but it more or less behaves like a list (it even defines "__setitem__"). You can iterate its elements just fine. The len() function works and you can index its elements. I am struggling to find the right combination that catches all list types, but excludes strings. I think I will settle for a check on "__getitem__" and "__len__" while explicitly excluding basestring. – haridsv Jan 15 '10 at 06:13
2

I do this in my testcases.

def assertIsIterable(self, item):
    #add types here you don't want to mistake as iterables
    if isinstance(item, basestring): 
        raise AssertionError("type %s is not iterable" % type(item))

    #Fake an iteration.
    try:
        for x in item:
            break;
    except TypeError:
        raise AssertionError("type %s is not iterable" % type(item))

Untested on generators, I think you are left at the next 'yield' if passed in a generator, which may screw things up downstream. But then again, this is a 'unittest'

FlipMcF
  • 12,636
  • 2
  • 35
  • 44
2

In "duck typing" manner, how about

try:
    lst = lst + []
except TypeError:
    #it's not a list

or

try:
    lst = lst + ()
except TypeError:
    #it's not a tuple

respectively. This avoids the isinstance / hasattr introspection stuff.

You could also check vice versa:

try:
    lst = lst + ''
except TypeError:
    #it's not (base)string

All variants do not actually change the content of the variable, but imply a reassignment. I'm unsure whether this might be undesirable under some circumstances.

Interestingly, with the "in place" assignment += no TypeError would be raised in any case if lst is a list (not a tuple). That's why the assignment is done this way. Maybe someone can shed light on why that is.

utobi
  • 21
  • 2
2

Another version of duck-typing to help distinguish string-like objects from other sequence-like objects.

The string representation of string-like objects is the string itself, so you can check if you get an equal object back from the str constructor:

# If a string was passed, convert it to a single-element sequence
if var == str(var):
    my_list = [var]

# All other iterables
else: 
    my_list = list(var)

This should work for all objects compatible with str and for all kinds of iterable objects.

stevepastelan
  • 1,264
  • 7
  • 11
1

simplest way... using any and isinstance

>>> console_routers = 'x'
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
False
>>>
>>> console_routers = ('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
True
>>> console_routers = list('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
True
amulllb
  • 3,036
  • 7
  • 50
  • 87
0

Python 3 has this:

from typing import List

def isit(value):
    return isinstance(value, List)

isit([1, 2, 3])  # True
isit("test")  # False
isit({"Hello": "Mars"})  # False
isit((1, 2))  # False

So to check for both Lists and Tuples, it would be:

from typing import List, Tuple

def isit(value):
    return isinstance(value, List) or isinstance(value, Tuple)
Juha Untinen
  • 1,806
  • 1
  • 24
  • 40
0
assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string"
ersh
  • 339
  • 2
  • 8
  • 2
    is this an okay way to do this? – ersh Apr 17 '19 at 08:40
  • 1
    Welcome to SO. An explanation of why this code answers the question would be helpful. – Nick Apr 17 '19 at 08:55
  • Yeah of course, I use methods similar to this since the pipe is treated as an or so you are asserting that the type must be a list or type tuple outputting a custom message error for error handling. I believe it answers the question, but I was curious as if it is an effective way of doing this as I am still trying to get the hang of writing the most optimized code. I am unsure however if this code misses out of things that may act like lists/tuples but aren't subclasses of either, as how the accepted answer addresses that possibility. Thanks! – ersh Apr 18 '19 at 05:38
0
assert type(lst).__name__ in ('tuple', 'list')

It is also easy to expand for more check, e.g. numpy array (ndarray) without importing numpy.

-1

Just do this

if type(lst) in (list, tuple):
    # Do stuff
ATOzTOA
  • 34,814
  • 22
  • 96
  • 117
-3

in python >3.6

import collections
isinstance(set(),collections.abc.Container)
True
isinstance([],collections.abc.Container)
True
isinstance({},collections.abc.Container)
True
isinstance((),collections.abc.Container)
True
isinstance(str,collections.abc.Container)
False
Jean Doux
  • 11
  • 1
  • 5
    In the last check you use a type `str`, not a string. Try `isinstance('my_string', collections.abc.Container)` and you will see that it will return `True`. This is because [`abc.Container`](https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes) supplies the `__contains__` method, and strings have it, of course. – Georgy Mar 11 '20 at 16:01
-6

I tend to do this (if I really, really had to):

for i in some_var:
   if type(i) == type(list()):
       #do something with a list
   elif type(i) == type(tuple()):
       #do something with a tuple
   elif type(i) == type(str()):
       #here's your string
DrBloodmoney
  • 2,786
  • 2
  • 18
  • 17
  • 5
    You almost never should do this. What happens if I `some_var` is an instance of a class that is a subclass of `list()`? Your code won't have any idea what to do with it, even though it will work perfectly under the "do something with a list" code. And you rarely need to care about the difference between a list and a tuple. Sorry, -1. – steveha Dec 02 '09 at 20:11
  • 1
    No need to write `type(tuple())` -- `tuple` will do. Same for list. Also, both `str` and `unicode` extend `basestring`, which is the real string type, so you want to check for that instead. – Tim has moved to Codidact Jan 09 '11 at 01:35
  • @DrBloodmoney: Accidental downvote. Please (trivially) edit your answer to enable me to remove the downvote. – SabreWolfy Apr 26 '12 at 09:27
  • Equality doesn't seem like a meaningful comparison for types, to me. I'd test for identity instead: `type(i) is list`. Also, `type(list())` is just `list` itself... Finally, this doesn't work gracefully with subclasses. If `i` is in fact and OrderedDict, or some kind of namedtuple, this code will treat is as a string. – bukzor Jun 07 '13 at 22:01