如何判断一个变量是可迭代的但不是字符串

111 投票
11 回答
45187 浏览
提问于 2025-04-15 12:32

我有一个函数,它可以接收一个参数,这个参数可以是一个单独的项目,也可以是两个项目:

def iterable(arg)
    if #arg is an iterable:
        print "yes"
    else:
        print "no"

所以:

>>> iterable( ("f","f") )
yes

>>> iterable( ["f","f"] )
yes

>>> iterable("ff")
no

问题是,字符串在技术上是可以被遍历的,所以当我尝试用 arg[1] 时,不能仅仅捕捉到值错误(ValueError)。我不想使用 isinstance(),因为听说那样做不是个好习惯。

11 个回答

17

自从Python 2.6开始,引入了抽象基类(abstract base classes),所以在抽象基类上使用isinstance(而不是具体的类)现在是完全可以接受的。具体来说:

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

这段代码实际上是_abcoll.py中定义的Iterable的一个完全复制(只改了类名)……之所以这样做能达到你想要的效果,而collections.Iterable却不行,是因为后者特别处理了字符串,使其被认为是可迭代的,方法是在这个class声明后,明确调用了Iterable.register(str)

当然,你可以很容易地通过在any调用之前返回False来扩展__subclasshook__,这样可以排除你想要特别排除的其他类。

无论如何,在你将这个新模块导入为myiter后,isinstance('ciao', myiter.NonStringIterable)会返回False,而isinstance([1,2,3], myiter.NonStringIterable)会返回True,正如你所要求的那样——在Python 2.6及以后的版本中,这被认为是进行这种检查的正确方式……定义一个抽象基类,然后在其上检查isinstance

38

截至2017年,这里有一个适用于所有版本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
55

使用 isinstance(我不明白为什么这被认为是不好的做法)

import types
if not isinstance(arg, types.StringTypes):

注意使用 StringTypes。这可以确保我们不会忘记一些不常见的字符串类型。

好的一面是,这也适用于派生的字符串类。

class MyString(str):
    pass

isinstance(MyString("  "), types.StringTypes) # true

另外,你可能想看看这个 之前的问题

祝好。


注意: 在 Python 3 中,行为发生了变化,因为 StringTypesbasestring 不再被定义。根据你的需要,你可以在 isinstance 中用 str 替换它们,或者用一个包含 (str, bytes, unicode) 的元组,比如对于 Cython 用户。 正如 @Theron Luhn 提到的,你也可以使用 six

撰写回答