views:

536

answers:

6

In python, what's the best way to test if a variable contains a list or a tuple? (ie. a collection)

Is isinstance as evil as suggested here? http://www.canonical.org/~kragen/isinstance/

+14  A: 

Document the argument as needing to be a sequence, and use it as a sequence. Don't check the type.

Ignacio Vazquez-Abrams
A: 

In principle, I agree with Ignacio, above, but you can also use type to check if something is a tuple or a list.

>>> a = (1,)
>>> type(a)
(type 'tuple')
>>> a = [1]
>>> type(a)
(type 'list')
Adam Crossland
+3  A: 

There's nothing wrong with using isinstance as long as it's not redundant. If a variable should only be a list/tuple then document the interface and just use it as such. Otherwise a check is perfectly reasonable:

if isinstance(a, collections.Iterable):
    # use as a container
else:
    # not a container!

This type of check does have some good use-cases, such as with the standard string startswith / endswith methods (although to be accurate these are implemented in C in CPython using an explicit check to see if it's a tuple - there's more than one way to solve this problem, as mentioned in the article you link to).

An explicit check is often better than trying to use the object as a container and handling the exception - that can cause all sorts of problems with code being run partially or unnecessarily.

Scott Griffiths
+3  A: 

Python uses "Duck typing", i.e. if a variable kwaks like a duck, it must be a duck. In your case, you probably want it to be iterable, or you want to access the item at a certain index. You should just do this: i.e. use the object in for var: or var[idx] inside a try block, and if you get an exception it wasn't a duck...

Wim
+2  A: 

Go ahead and use isinstance if you need it. It is somewhat evil, as it excludes custom sequences, iterators, and other things that you might actually need. However, sometimes you need to behave differently if someone, for instance, passes a string. My preference there would be to explicitly check for str or unicode like so:

import types
isinstance(var, types.StringTypes)

N.B. Don't mistake types.StringType for types.StringTypes. The latter incorporates str and unicode objects.

The types module is considered by many to be obsolete in favor of just checking directly against the object's type, so if you'd rather not use the above, you can alternatively check explicitly against str and unicode, like this:

isinstance(var, (str, unicode)):

After either of these, you can fall back to behaving as if you're getting a normal sequence, letting non-sequences raise appropriate exceptions.

See the thing that's "evil" about type checking is not that you might want to behave differently for a certain type of object, it's that you artificially restrict your function from doing the right thing with unexpected object types that would otherwise do the right thing. If you have a final fallback that is not type-checked, you remove this restriction. It should be noted that too much type checking is a code smell that indicates that you might want to do some refactoring, but that doesn't necessarily mean you should avoid it from the getgo.

jcdyer
The types module is a bit of a historical artifact. As mentioned on http://docs.python.org/dev/library/types.html#module-types if you really must check for the `str` type you should just use that directly, instead of using `types.StringType` which is just an alias for it.But I do not think this answer answers the question as asked, because that was about "a collection". Unless you're using a python new enough to have the `abc` module that's not something you can use `isinstance` to check for, and even then I would recommend avoiding the check if at all possible.
mzz
`assert isinstance(u'abc', str) == False`. I agree that it is better to check against the type directly, rather than using the `types` module, but `types.StringTypes` does something that `str` doesn't: it returns True for `str` and `unicode` objects. I'll edit my response to offer a double check as an alternative.
jcdyer
I realize I didn't answer the question of checking for collections directly, but the actual question asked was "Is `isinstance` evil?" And I gave a counter example which (1) is a non-evil use of `isinstance`, because having a fallback means it doesn't break ducktyping, and (2) is a good solution for a very common motivation people have for wanting to check if something is a `list` or `tuple` (i.e. to disambiguate them from strings).
jcdyer
I agree, with the caveat that often it's useful for custom types to behave like strings, too. But Python's OO only goes so far...
Kragen Javier Sitaker
Does `class Foo(str): pass` do what you want?
jcdyer
A: 

If you just need to know if you can use the foo[123] notation with the variable, you can check for the existence of a __getitem__ attribute (which is what python calls when you access by index) with hasattr(foo, '__getitem__')

Geoff Reedy