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


88

我有一个带有参数的函数,该参数可以是单项或双项:

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

以便:

>>> iterable((“ f”,“ f”))
是

>>> iterable([“ f”,“ f”])
是

>>> iterable(“ ff”)
没有

问题是字符串在技术上是可迭代的,因此在尝试时我不能只捕捉ValueError arg[1]。我不想使用isinstance(),因为这不是一个好习惯(或者有人告诉我)。


1
哪个版本的Python?我相信答案在2. *和3之间有所不同
凯西·范·斯通·史东

4
有人不正确地告诉您,instance并不是一个坏习惯。
Lennart Regebro 09年

3
哦,等等,也许他指的是检查对象类型不好的原则,并且这表明程序已损坏吗?原则上是这样(但在实践中并非总是如此)。这种情况可能是,也可能不是。但是问题不是函数实例,而是检查类型的习惯。
Lennart Regebro 09年

@Lennart:canonical.org/~kragen/isinstance 它可能已经过时了
priestc

@up虽然没有提到基于类型的函数重载,但是isinstance它是在动态类型的语言中实现此功能的方法。每天不使用的东西,但在合理的情况下可以使用。
科斯2012年

Answers:


50

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

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

请注意StringTypes的使用。它确保我们不会忘记一些晦涩的字符串类型。

从好的方面来说,这也适用于派生的字符串类。

class MyString(str):
    pass

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

另外,您可能想看看上一个问题

干杯。


注意:行为在Python 3中更改为StringTypesbasestring并且不再定义。根据您的需要,您可以将它们替换isinstancestr,或替换为的子集元组(str, bytes, unicode),例如,对于Cython用户。正如@Theron Luhn提到的那样,您也可以使用six


很好,scvalex。我现在要删除-1并将其设为+1 :-)。
汤姆(Tom)” 2009年

2
我认为坏习惯的想法是因为鸭子的打字原则。成为特定类的成员并不意味着它是唯一可以使用的对象,也不意味着可以使用预期的方法。但是我认为有时即使存在该方法,您也无法推断该方法的作用,所以这isinstance可能是唯一的方法。
estani 2012年

2
注意:types.StringTypes在Python 3中不可用。由于py3k中只有一种字符串类型,因此我认为这是安全的do isinstance(arg, str)。对于向后兼容的版本,请考虑使用pythonhosted.org/six/#six.string_types
Theron Luhn

我严格使用Python3,并且注意到types.StringTypes在Python3中不可用。Python2的价值是什么?
kevinarpe 2015年

2
2017年:此答案不再有效,请参阅stackoverflow.com/a/44328500/99834,了解适用于所有版本Python的答案。
sorin

26

截至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

在2/3与字节字符串之间存在一些细微的不一致,但是如果您使用本地“字符串”,则它们都是错误的
Nick

16

从Python 2.6开始,引入了抽象基类isinstance(用于ABC,而不是具体类),现在被认为是完全可以接受的。特别:

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

这是(的实现细节)中Iterable定义的的精确副本(仅更改类名)...之所以能按您希望的那样工作而并非如此,是因为后者会加倍努力以确保字符串通过在此语句之后显式调用,将其视为可迭代的。_abcoll.pycollections.pycollections.IterableIterable.register(str)class

当然,很容易__subclasshook__通过Falseany调用您要专门从定义中排除的其他类之前返回来进行扩充。

无论如何,按照您的要求将新模块导入为myiterisinstance('ciao', myiter.NonStringIterable)将是Falseisinstance([1,2,3], myiter.NonStringIterable)将是之后True-在Python 2.6及更高版本中,这被视为体现此类检查的正确方法...定义抽象基类并检查isinstance一下。


在Python 3中,isinstance('spam', NonStringIterable)return True
尼克T

1
(...),在python 2.6和更高版本中,这被认为是体现这种检查的正确方法(...)以这种方式滥用众所周知的抽象类概念是如何被认为是正确的方法,这超出了我的理解。正确的方法是引入一些看起来像的运算符。
Piotr Dobrogost,2014年

Alex,您能解决Nick所说的在Python 3中不起作用的断言吗?我喜欢这个答案,但想确保自己正在编写面向未来的代码。
Merlyn Morgan-Graham

@ MerlynMorgan -格雷厄姆,这是正确的,因为__iter__ 现在的字符串在Python 3,所以我的“易扩充”段落变得适用,例如实施if issublass(cls, str): return False需要在开始添加__subclasshook__(以及它定义的任何其他类__iter__,但在你的心态一定不能被接受为“非字符串可迭代”。
Alex Martelli

@AlexMartelli对于Python 3,您不是说if issublass(C, str): return False应该添加吗?
罗布·史密斯

4

我意识到这是一篇过时的文章,但认为值得添加我的后代方法。以下功能似乎在大多数情况下都适用于Python 2和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

这通过使用内置函数(错误)来检查非字符串可迭代的,当其第二个参数不是字符串或Unicode字符串时hasattr,它将引发a TypeError


3

通过合并以前的答复,我正在使用:

import types
import collections

#[...]

if isinstance(var, types.StringTypes ) \
    or not isinstance(var, collections.Iterable):

#[Do stuff...]

不是100%的傻瓜证明,但是如果对象不是可迭代的,您仍然可以让它通过并退回给鸭子输入。


编辑:Python3

types.StringTypes == (str, unicode)。Phython3等效项是:

if isinstance(var, str ) \
    or not isinstance(var, collections.Iterable):

您的导入声明应为“类型”而非“类型”
PaulR

3

2.x

我会建议:

hasattr(x, '__iter__')

或鉴于David Charles的评论针对Python3进行了调整,那么:

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

3.x

内置的basestring抽象类型被移除。使用str代替。该strbytes类型没有足够的功能共同保证一个共享的基类。


3
也许是因为__iter__Python 3中有字符串?
davidrmcharles

@DavidCharles哦,真的吗?我的错。我是一个Jython用户和Jython目前还没有版本3
麦克灭鼠

这实际上不是答案,更多是评论/问题,并且对于3.x是错误的。你能收拾一下吗?您是否可以说出理由“ str和字节类型没有足够的功能来保证共享基类”?3.x的关键点之一是使Unicode字节成为头等公民。
smci

我不知道为什么我写了以上任何内容。我建议删除“ 3.x”下的所有文本...尽管您已经编辑了我的答案。如果愿意,可以对其进行更多编辑。
麦克啮齿动物

0

如您正确指出的那样,单个字符串是一个字符序列。

因此,您真正想做的arg就是通过使用isinstance或type(a)== str找出什么样的序列。

如果要实现一个需要可变数量参数的函数,则应这样进行:

def function(*args):
    # args is a tuple
    for arg in args:
        do_something(arg)

function(“ ff”)和function(“ ff”,“ ff”)将起作用。

我看不到需要像您一样的isiterable()函数的情况。不是isinstance()是不好的样式,而是需要使用isinstance()的情况。


4
type(a) == str应避免使用。这是一种不好的做法,因为它没有考虑相似的类型或从派生的类型strtype不会爬上类型层次结构,而爬上类型层次结构isinstance,因此最好使用isinstance
AkiRoss 2015年

0

为了明确扩展Alex Martelli的出色技巧collections.py并解决围绕它的一些问题:python 3.6+中当前有效的解决方案是

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

并展示

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

iter('')例如,如果要添加到排除项中,请修改该行

            if issubclass(c, str):
                return False

成为

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

要得到

[False, False, True, True, True, True]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.