如何检查对象是列表还是元组(但不是字符串)?

Posted

技术标签:

【中文标题】如何检查对象是列表还是元组(但不是字符串)?【英文标题】:How to check if an object is a list or tuple (but not string)? 【发布时间】:2010-12-22 13:03:20 【问题描述】:

这是我通常所做的,以确定输入是list/tuple - 但不是str。因为很多时候我偶然发现一个函数错误地传递了str 对象的错误,而目标函数执行for x in lst 假设lst 实际上是listtuple

assert isinstance(lst, (list, tuple))

我的问题是:有没有更好的方法来实现这一点?

【问题讨论】:

【参考方案1】:

仅在 python 2 中(不是 python 3):

assert not isinstance(lst, basestring)

实际上是你想要的,否则你会错过很多类似于列表但不是listtuple 的子类的东西。

【讨论】:

是的,这是正确的答案。在 Python 3 中,basestring 消失了,您只需检查 isinstance(lst, str) 有很多东西可以迭代,比如列表,例如set、生成器表达式、迭代器。有像 mmap 这样的异国情调的东西,像 array 这样不那么异国情调的东西很像列表,而且我可能忘记了更多。 值得注意的是,这并不能保证 lst 是可迭代的,而原来的却是可迭代的(例如 int 将通过此检查) @PeterGibson - 两者的组合将提供有效的、更具限制性的检查并确保 1) lst 是可迭代的,2) lst 不是字符串。 assert isinstance(lst, (list, tuple)) and assert not isinstance(lst, basestring) 好吧,这个解决方案只检查字符串派生类型,但是整数、双精度数或任何其他不可迭代的类型呢?【参考方案2】:

请记住,在 Python 中我们要使用“鸭子类型”。因此,任何像列表一样的东西都可以被视为列表。所以,不要检查列表的类型,只看它是否像列表一样。

但是字符串也像一个列表,而这通常不是我们想要的。有时甚至是一个问题!所以,明确地检查一个字符串,然后使用鸭子类型。

这是我为了好玩而写的一个函数。它是repr() 的特殊版本,可以打印尖括号('')中的任何序列。

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

总体而言,这是干净而优雅的。但是isinstance() 支票在那里做什么?这是一种黑客行为。但这是必不可少的。

这个函数递归地调用任何像列表一样的东西。如果我们没有特别处理这个字符串,那么它就会被当作一个列表来处理,并且一次拆分一个字符。但随后递归调用会尝试将每个字符视为一个列表——它会起作用!即使是一个字符的字符串也可以作为一个列表!该函数将继续递归调用自身,直到堆栈溢出。

像这样的函数,依赖于分解要完成的工作的每个递归调用,必须是特殊情况的字符串——因为你不能分解低于单字符字符串级别的字符串,并且即使是一个字符的字符串也像一个列表。

注意:try/except 是表达我们意图的最简洁方式。但是如果这段代码在某种程度上是时间紧迫的,我们可能想用某种测试来替换它,看看arg 是否是一个序列。我们可能应该测试行为,而不是测试类型。如果它有.strip() 方法,它是一个字符串,所以不要认为它是一个序列;否则,如果它是可索引或可迭代的,则它是一个序列:

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)

编辑:我最初写以上内容时检查了__getslice__(),但我注意到在collections 模块文档中,有趣的方法是__getitem__();这是有道理的,这就是您索引对象的方式。这似乎比__getslice__() 更基本,所以我更改了上述内容。

【讨论】:

@stantonk,谢谢你这么说,但我认为在我写这篇文章时已经有一个接受的答案,我真的不希望改变接受的答案。 @steveha: srepr 是一个非常有趣的想法。但我对是否需要特例 str 持有不同的意见。是的,str 是迄今为止最明显和最常见的可迭代对象,它会导致 srepr 中的无限递归。但是我可以很容易地想象用户定义的迭代以相同的方式运行(无论有没有充分的理由)。而不是特例str,我们应该承认这种方法可能会遇到无限递归,并同意某种处理方式。我会在答案中发表我的建议。 我认为这绝对是正确的道路。但是,为了处理特殊情况(在这种情况下是字符串),我认为我们最好问一个问题“人类如何区分?”例如,考虑一个函数参数,它可以是电子邮件地址列表或单个电子邮件地址(请记住,字符串只是字符列表)。将此变量提供给人类。怎么知道是哪一个?我能想到的最简单的方法是查看列表的每个项目中有多少个字符。如果大于 1,则参数肯定不能是字符列表。 我已经考虑过这个问题,并与其他几个人讨论过,我认为srepr() 可以。我们需要一个递归函数来处理诸如嵌套在另一个列表中的列表之类的事情;但是对于字符串,我们宁愿将它们打印为"foo" 而不是&lt;'f', 'o', 'o'&gt;。因此,在这里对字符串进行显式检查很有意义。此外,实际上没有任何其他数据类型的示例,其中迭代 always 返回可迭代且递归将始终导致堆栈溢出,因此我们不需要特殊属性来测试这一点(“实用性胜过纯洁”)。 这在 Python 3 中不起作用,因为字符串在 Python 3 中有一个 __iter__() 方法,但在 Python 2 中没有。您在 is_sequence() 中缺少括号,它应该是:return (not hasattr(arg, "strip") and (hasattr(arg, "__getitem__") or hasattr(arg, "__iter__"))) 【参考方案3】:
H = "Hello"

if type(H) is list or type(H) is tuple:
    ## Do Something.
else
    ## Do Something.

【讨论】:

除了它没有像其他评论者指出的那样使用鸭子类型的 Python 习语(尽管它确实直接而干净地回答了这个问题)。 这个答案比其他答案更不被接受,因为它不允许鸭子打字,而且在简单的子类化情况下也失败了(一个典型的例子是 namedtuple 类)。 “不允许鸭子打字”并没有降低答案的可接受性,特别是考虑到这个答案实际上回答了这个问题。 我赞成这个答案,但if isinstance( H, (list, tuple) ): ... 更短更清晰。 替代语法:if type(H) in [list, tuple]:【参考方案4】:

Python 3:

import collections.abc

if isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str):
    print("`obj` is a sequence (list, tuple, etc) but not a string or a dictionary.")

在 3.3 版中更改:将“集合抽象基类”的全局命名空间从 abc 移动到 collections.abc 模块。为了向后兼容,它们将继续在此模块中可见,直到版本 3.8 停止工作。

Python 2:

import collections

if isinstance(obj, collections.Sequence) and not isinstance(obj, basestring):
    print "`obj` is a sequence (list, tuple, etc) but not a string or unicode or dictionary."

【讨论】:

哇!这非常有效,并且比任何其他正确答案都简洁得多。我不知道内置类型继承自collections.Sequence,但我对其进行了测试,发现它们确实如此。 xrange 也是如此。更好的是,这个测试排除了dict,它同时具有__getitem____iter__ @SteveJorgensen 方法解析顺序定义了 Python 使用的类搜索路径,以搜索要在类中使用的正确方法。 Sequence 是一个抽象类。 在 Python3 中,您可以将 isinstance(obj, basestring) 替换为 isinstance(obj, str),这应该可以。 在 Python 3 中你需要而不是 isinstance(obj, bytes) ...如果你想要一个事物列表,而不仅仅是枚举字节... 在 Python 3 中你实际上想要and not isinstance(obj, (str, collections.abc.ByteString))【参考方案5】:

带有 php 风格的 Python:

def is_array(var):
    return isinstance(var, (list, tuple))

【讨论】:

Python 是一种鸭式语言,所以你真的应该检查 var 是否有属性__getitem__。名称也具有误导性,因为还有数组模块。 var 也可以是 numpy.ndarray 或任何其他类型,它具有__getitem__。正确答案见***.com/a/1835259/470560。 @peterhil str 也有 __getitem__ 因此您的支票不排除 str 字典也是如此。在这里检查__getitem__ 是个坏建议。【参考方案6】:

一般来说,迭代对象的函数适用于字符串以及元组和列表这一事实比错误更具功能。您当然可以使用isinstance 或duck typing 来检查参数,但是为什么要这样做呢?

这听起来像是一个反问,但事实并非如此。 “我为什么要检查参数的类型”的答案?可能会建议解决实际问题,而不是感知问题。为什么将字符串传递给函数时会出现错误?另外:如果将字符串传递给此函数时出现错误,那么如果将其他一些非列表/元组可迭代对象传递给它,这也是错误吗?为什么,或者为什么不?

我认为这个问题最常见的答案可能是编写f("abc") 的开发人员期望该函数的行为就像他们编写了f(["abc"]) 一样。在某些情况下,保护开发人员免受自己的侵害可能比支持遍历字符串中的字符的用例更有意义。但我会先仔细考虑一下。

【讨论】:

“但我会先考虑很久。”我不会。如果该函数应该是一个 list-y 函数,那么是的,它应该同样对待它们(即,给定一个列表,将其向后吐出,诸如此类)。但是,如果它是一个函数,其中一个参数可以是字符串或字符串列表(这是很常见的需求),那么强制使用该函数的开发人员总是在其中输入参数一个数组似乎有点多。另外,想想你将如何处理 JSON 输入。您肯定希望处理不同于字符串的对象列表。【参考方案7】:

试试这个以获得可读性和最佳实践:

Python2 - isinstance()

import types
if isinstance(lst, types.ListType) or isinstance(lst, types.TupleType):
    # Do something

Python3 - isinstance()

import typing
if isinstance(lst, typing.List) or isinstance(lst, typing.Tuple):
    # Do something

希望对你有帮助。

【讨论】:

Python 3.6.5:AttributeError: module 'types' has no attribute 'ListType' 在 Python 3 中为:from typing import List --> isinstance([1, 2, 3], List = True, 和 isinstance("asd", List) = False【参考方案8】:

str 对象没有__iter__ 属性

>>> hasattr('', '__iter__')
False 

所以你可以检查一下

assert hasattr(x, '__iter__')

这也会为任何其他不可迭代的对象提出一个很好的AssertionError

编辑: 正如 Tim 在 cmets 中提到的,这仅适用于 python 2.x,而不适用于 3.x

【讨论】:

小心:在 Python 3 中 hasattr('','__iter__') 返回 True。当然这是有道理的,因为你可以遍历一个字符串。 真的吗?我不知道。我一直认为这是解决问题的优雅方法,哦。 此测试不适用于 pyodbc.Row。它没有 iter__() 但它或多或少地表现得像一个列表(它甚至定义了“__setitem”)。您可以很好地迭代它的元素。 len() 函数有效,您可以索引它的元素。我正在努力寻找能够捕获所有列表类型但不包括字符串的正确组合。我想我会在明确排除 basestring 的同时对“getitem”和“len”进行检查。【参考方案9】:

这里不是直接回答OP,只是想分享一些相关的想法。

我对上面的@steveha 答案非常感兴趣,这似乎给出了一个鸭子打字似乎中断的例子。然而,再想一想,他的示例表明鸭子类型很难符合,但它确实表明str 值得任何特殊处理。

毕竟,非str 类型(例如,维护一些复杂递归结构的用户定义类型)可能会导致@steveha srepr 函数导致无限递归。虽然这显然不太可能,但我们不能忽视这种可能性。因此,与其在srepr 中特殊化str,不如明确我们希望srepr 在产生无限递归时做什么。

似乎一种合理的方法是在list(arg) == [arg] 的时刻简单地中断srepr 中的递归。实际上,这将完全解决 str 的问题,而无需任何 isinstance

但是,一个非常复杂的递归结构可能会导致list(arg) == [arg] 永远不会发生的无限循环。因此,虽然上述检查很有用,但还不够。我们需要对递归深度进行硬性限制。

我的观点是,如果您打算处理任意参数类型,通过鸭子类型处理str 远比处理您可能(理论上)遇到的更一般的类型要容易得多。因此,如果您觉得需要排除 str 实例,则应改为要求参数是您明确指定的少数类型之一的实例。

【讨论】:

嗯,我喜欢你的想法。我认为你不能说我的代码是实用的:只有一种常见的情况,str,特殊情况的代码可以处理。但也许应该有一个新的标准属性,代码可以检查,.__atomic__ 比方说,这表明某些东西不能被进一步分解。在 Python 中添加另一个内置函数 atomic() 可能为时已晚,但也许我们可以添加 from collections import atomic 或其他东西。【参考方案10】:

我找到了一个名为is_sequence in tensorflow 的函数。

def is_sequence(seq):
  """Returns a true if its input is a collections.Sequence (except strings).
  Args:
    seq: an input sequence.
  Returns:
    True if the sequence is a not a string and is a collections.Sequence.
  """
  return (isinstance(seq, collections.Sequence)
and not isinstance(seq, six.string_types))

我已经验证它满足您的需求。

【讨论】:

【参考方案11】:

我在我的测试用例中这样做。

def assertIsIterable(self, item):
    #add types here you don't want to mistake as iterables
    if isinstance(item, basestring): 
        raise AssertionError("type %s is not iterable" % type(item))

    #Fake an iteration.
    try:
        for x in item:
            break;
    except TypeError:
        raise AssertionError("type %s is not iterable" % type(item))

在生成器上未经测试,我认为如果通过生成器,您将处于下一个“产量”,这可能会在下游搞砸。但话又说回来,这是一个“单元测试”

【讨论】:

【参考方案12】:

以“鸭式打字”的方式,怎么样

try:
    lst = lst + []
except TypeError:
    #it's not a list

try:
    lst = lst + ()
except TypeError:
    #it's not a tuple

分别。这避免了 isinstance / hasattr 内省的东西。

你也可以反过来检查:

try:
    lst = lst + ''
except TypeError:
    #it's not (base)string

所有变体实际上并不改变变量的内容,而是暗示重新赋值。我不确定这在某些情况下是否不受欢迎。

有趣的是,如果 lstlist(不是 tuple )。这就是为什么以这种方式完成任务的原因。也许有人可以解释为什么会这样。

【讨论】:

【参考方案13】:

另一个版本的鸭子类型有助于区分字符串对象和其他序列对象。

类字符串对象的字符串表示就是字符串本身,所以你可以检查你是否从str构造函数返回一个相等的对象:

# If a string was passed, convert it to a single-element sequence
if var == str(var):
    my_list = [var]

# All other iterables
else: 
    my_list = list(var)

这应该适用于与str 兼容的所有对象以及所有类型的可迭代对象。

【讨论】:

【参考方案14】:

最简单的方法...使用anyisinstance

>>> console_routers = 'x'
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
False
>>>
>>> console_routers = ('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
True
>>> console_routers = list('x',)
>>> any([isinstance(console_routers, list), isinstance(console_routers, tuple)])
True

【讨论】:

【参考方案15】:

Python 3 有这个:

from typing import List

def isit(value):
    return isinstance(value, List)

isit([1, 2, 3])  # True
isit("test")  # False
isit("Hello": "Mars")  # False
isit((1, 2))  # False

所以要同时检查列表和元组,应该是:

from typing import List, Tuple

def isit(value):
    return isinstance(value, List) or isinstance(value, Tuple)

【讨论】:

【参考方案16】:
assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string"

【讨论】:

这样可以吗? 欢迎来到 SO。解释为什么这段代码回答了这个问题会很有帮助。 是的,当然,我使用与此类似的方法,因为管道被视为一个或因此您断言该类型必须是一个列表或类型元组输出自定义消息错误以进行错误处理。我相信它回答了这个问题,但我很好奇它是否是一种有效的方法,因为我仍在努力掌握编写最优化代码的窍门。但是,我不确定这段代码是否遗漏了可能像列表/元组但不是它们的子类的东西,因为接受的答案如何解决这种可能性。谢谢!【参考方案17】:

就这样做

if type(lst) in (list, tuple):
    # Do stuff

【讨论】:

isinstance(lst, (list, tuple)) @DaviLima 好的,那是另一种方式。但是 type() 推荐用于内置类型和 isinstance 用于类。【参考方案18】:

在 python >3.6 中

import collections
isinstance(set(),collections.abc.Container)
True
isinstance([],collections.abc.Container)
True
isinstance(,collections.abc.Container)
True
isinstance((),collections.abc.Container)
True
isinstance(str,collections.abc.Container)
False

【讨论】:

在最后一次检查中,您使用类型 str,而不是字符串。试试isinstance('my_string', collections.abc.Container),你会看到它会返回True。这是因为abc.Container 提供了__contains__ 方法,当然字符串也有它。【参考方案19】:

我倾向于这样做(如果我真的,真的不得不这样做的话):

for i in some_var:
   if type(i) == type(list()):
       #do something with a list
   elif type(i) == type(tuple()):
       #do something with a tuple
   elif type(i) == type(str()):
       #here's your string

【讨论】:

你几乎不应该这样做。如果我some_varlist() 的子类的一个实例,会发生什么?您的代码不知道如何处理它,即使它可以在“使用列表执行某些操作”代码下完美运行。而且你很少需要关心列表和元组之间的区别。抱歉,-1。 无需写type(tuple()) -- tuple 即可。列表也一样。此外,strunicode 都扩展了 basestring,这是真正的字符串类型,因此您需要检查它。 @DrBloodmoney:意外投反对票。请(简单地)编辑您的答案,以使我能够删除反对票。 对我来说,平等似乎不是一个有意义的类型比较。我会测试身份:type(i) is list。此外,type(list()) 只是 list 本身......最后,这不能与子类正常工作。如果i 实际上是 OrderedDict 或某种命名元组,则此代码会将 is 视为字符串。

以上是关于如何检查对象是列表还是元组(但不是字符串)?的主要内容,如果未能解决你的问题,请参考以下文章

#9 Python列表和元组

PYTHON 写函数,检查用户传入的对象(字符串列表元组)的每一个元素是否含有空内容。

python3检查列表、元组、字符串中的重复元素

如何检查字符串是不是包含字符列表?

如何将字符串的所有排列作为字符串列表(而不是元组列表)?

如何判断一个值是 Django 模板中的字符串还是列表?