how to tell a variable is iterable but not a string

后端 未结 8 2149
小蘑菇
小蘑菇 2020-12-02 09:23

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         


        
相关标签:
8条回答
  • 2020-12-02 10:00

    To explicitly expand on Alex Martelli's excellent hack of collections.py and address some of the questions around it: The current working solution in python 3.6+ is

    import collections
    import _collections_abc as cabc
    import abc
    
    
    class NonStringIterable(metaclass=abc.ABCMeta):
    
        __slots__ = ()
    
        @abc.abstractmethod
        def __iter__(self):
            while False:
                yield None
    
        @classmethod
        def __subclasshook__(cls, c):
            if cls is NonStringIterable:
                if issubclass(c, str):
                    return False
                return cabc._check_methods(c, "__iter__")
            return NotImplemented
    

    and demonstrated

    >>> typs = ['string', iter(''), list(), dict(), tuple(), set()]
    >>> [isinstance(o, NonStringIterable) for o in typs]
    [False, True, True, True, True, True]
    

    If you want to add iter('') into the exclusions, for example, modify the line

                if issubclass(c, str):
                    return False
    

    to be

                # `str_iterator` is just a shortcut for `type(iter(''))`*
                if issubclass(c, (str, cabc.str_iterator)):
                    return False
    

    to get

    [False, False, True, True, True, True]
    
    0 讨论(0)
  • 2020-12-02 10:02

    I realise this is an old post but thought it was worth adding my approach for Internet posterity. The function below seems to work for me under most circumstances with both Python 2 and 3:

    def is_collection(obj):
        """ Returns true for any iterable which is not a string or byte sequence.
        """
        try:
            if isinstance(obj, unicode):
                return False
        except NameError:
            pass
        if isinstance(obj, bytes):
            return False
        try:
            iter(obj)
        except TypeError:
            return False
        try:
            hasattr(None, obj)
        except TypeError:
            return True
        return False
    

    This checks for a non-string iterable by (mis)using the built-in hasattr which will raise a TypeError when its second argument is not a string or unicode string.

    0 讨论(0)
  • 2020-12-02 10:03

    2.x

    I would have suggested:

    hasattr(x, '__iter__')
    

    or in view of David Charles' comment tweaking this for Python3, what about:

    hasattr(x, '__iter__') and not isinstance(x, (str, bytes))
    

    3.x

    the builtin basestring abstract type was removed. Use str instead. The str and bytes types don’t have functionality enough in common to warrant a shared base class.

    0 讨论(0)
  • 2020-12-02 10:11

    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.

    0 讨论(0)
  • 2020-12-02 10:22

    As of 2017, here is a portable solution that works with all versions of Python:

    #!/usr/bin/env python
    import collections
    import six
    
    
    def iterable(arg):
        return (
            isinstance(arg, collections.Iterable) 
            and not isinstance(arg, six.string_types)
        )
    
    
    # non-string iterables    
    assert iterable(("f", "f"))    # tuple
    assert iterable(["f", "f"])    # list
    assert iterable(iter("ff"))    # iterator
    assert iterable(range(44))     # generator
    assert iterable(b"ff")         # bytes (Python 2 calls this a string)
    
    # strings or non-iterables
    assert not iterable(u"ff")     # string
    assert not iterable(44)        # integer
    assert not iterable(iterable)  # function
    
    0 讨论(0)
  • 2020-12-02 10:24

    By combining previous replies, I'm using:

    import types
    import collections
    
    #[...]
    
    if isinstance(var, types.StringTypes ) \
        or not isinstance(var, collections.Iterable):
    
    #[Do stuff...]
    

    Not 100% fools proof, but if an object is not an iterable you still can let it pass and fall back to duck typing.


    Edit: Python3

    types.StringTypes == (str, unicode). The Phython3 equivalent is:

    if isinstance(var, str ) \
        or not isinstance(var, collections.Iterable):
    
    0 讨论(0)
提交回复
热议问题