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

How to check if an object is a list or tuple (but not string)?

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

1
assert isinstance(lst, (list, tuple))

我的问题是:有没有更好的方法来实现这个目标?


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

1
assert not isinstance(lst, basestring)

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


记住,在python中,我们要使用"duck-typing"。所以,任何类似于列表的东西都可以被视为列表。所以,不要检查列表的类型,只要看看它是否像一个列表。

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

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

1
2
3
4
5
6
7
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()方法,它是一个字符串,所以不要认为它是一个序列;否则,如果它是可索引或不可索引的,它是一个序列:

1
2
3
4
5
6
7
8
9
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__()更为基本,因此我改变了上述内容。


1
2
3
4
5
6
H ="Hello"

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


对于Python 2:

1
2
3
4
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"

对于Python 3:

1
2
3
4
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 unicode")

Changed in version 3.3: Moved Collections Abstract Base Classes to the collections.abc module. For backwards compatibility, they will continue to be visible in this module as well until version 3.8 where it will stop working.


具有PHP风格的python:

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


一般来说,迭代对象的函数可以处理字符串、元组和列表,这一事实比bug更具特点。你当然可以用isinstance或duck输入来检查一个论点,但是为什么要这样做呢?

这听起来像是一个反问问题,但事实并非如此。"为什么我要检查论点的类型?"可能会建议解决实际问题,而不是感知到的问题。当一个字符串传递给函数时,为什么它是一个bug?另外:如果一个字符串传递给这个函数时它是一个bug,那么如果传递给它的是其他非列表/元组iterable,它也是一个bug吗?为什么?为什么?

我认为对这个问题最常见的答案可能是编写f("abc")的开发人员希望函数的行为就像他们编写f(["abc"])一样。在某些情况下,保护开发人员不受其影响可能比支持在字符串中遍历字符的用例更有意义。但我会先考虑很久再考虑。


请尝试此操作以获得可读性和最佳实践:

Python 2

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

Python 3

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

希望它有帮助。


str对象没有__iter__属性

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

所以你可以查一下

1
assert hasattr(x, '__iter__')

这也将为任何其他不可辩驳的对象带来一个好的AssertionError

编辑:正如蒂姆在评论中提到的,这只在python 2.x中有效,而不是3.x。


这不是为了直接回答操作,但我想分享一些相关的想法。

我对上面的@steveha答案很感兴趣,它似乎给出了一个鸭子打字似乎中断的例子。然而,在第二个想法中,他的例子表明duck类型很难符合,但它并不意味着str值得任何特殊处理。

毕竟,非str类型(例如,维护一些复杂递归结构的用户定义类型)可能会导致@steveha srepr函数产生无限递归。诚然,这是不太可能的,但我们不能忽视这种可能性。因此,在srepr中,我们应该澄清当无限递归结果时,我们希望srepr做什么,而不是特殊的外壳str

一个合理的方法似乎是简单地打破srepr中的递归,即由list(arg) == [arg]开始。实际上,这将完全解决str的问题,而不需要任何isinstance

然而,一个非常复杂的递归结构可能会导致一个无限循环,其中list(arg) == [arg]从未发生过。因此,虽然上面的检查很有用,但还不够。我们需要像递归深度的硬限制这样的东西。

我的观点是,如果您计划处理任意参数类型,那么通过duck类型处理str要比处理可能(理论上)遇到的更一般的类型容易得多。因此,如果您觉得需要排除str实例,您应该要求该参数是您显式指定的少数类型之一的实例。


我在TensorFlow中发现这样一个名为is_序列的函数。

1
2
3
4
5
6
7
8
9
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))

我已经证实它能满足你的需要。


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

1
2
3
4
5
6
7
8
9
10
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))

我认为,如果通过一台发电机,你将在下一个"收益率"中留下,这可能会把下游的事情搞砸。但同样,这是一个"单元测试"


最简单的方法…使用anyisinstance

1
2
3
4
5
6
7
8
9
10
>>> 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

用"鸭子打字"的方式,怎么样

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

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

分别。这样就避免了isinstancehasattr的反省。

您也可以检查反之亦然:

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

所有变量实际上并不改变变量的内容,而是意味着重新分配。我不确定在某些情况下这是否不可取。

有趣的是,如果lst是一个列表(不是一个元组),那么在任何情况下,"就地"分配+=都不会引发TypeError。这就是为什么任务是这样完成的。也许有人能解释为什么会这样。


就这样做

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


1
assert (type(lst) == list) | (type(lst) == tuple),"Not a valid lst type, cannot be string"


python 3有:

1
2
3
4
5
6
7
8
9
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

因此,要同时检查列表和元组,应该是:

1
2
3
4
from typing import List, Tuple

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


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

1
2
3
4
5
6
7
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