How to check if an object is a list or tuple (but not string)?
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.
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.