tags:

views:

318

answers:

3

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).

+6  A: 

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.

scvalex
basestring might be better, because of Unicode strings.
RichieHindle
Don't use arg.__class__ because if you havea subclass of the class you are testing for, it's wrong! For example, if you do want to test for basestring as RichieHindle suggests, arg.__class__ != basestring would fail for strings.
Tom
Meh. Added unicode support, removing class check.
scvalex
Nice, scvalex. I'm removing my -1 now and making it a +1 :-).
Tom
+2  A: 

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
+4  A: 

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.

Alex Martelli