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

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

assert isinstance(lst, (list, tuple))

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

569169 次浏览

仅在python2中(不是python3):

assert not isinstance(lst, basestring)

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

str对象没有__iter__属性

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

你可以检查一下

assert hasattr(x, '__iter__')

并且这也会为任何其他不可迭代对象抛出一个漂亮的AssertionError

< em >编辑: 正如Tim在评论中提到的,这只适用于python2。X,不是3.x

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

但是字符串也像一个列表,这通常不是我们想要的。有时候这甚至是个问题!因此,显式检查字符串,然后使用duck typing。

这是我写的一个函数,只是为了好玩。它是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()检查在这里做什么呢?这是一种hack。但这是必要的。

这个函数在任何类似于列表的东西上递归地调用自己。如果我们不专门处理字符串,那么它将被视为一个列表,并一次分割一个字符。但随后递归调用将尝试将每个字符视为列表—这将工作!即使只有一个字符的字符串也可以作为列表!函数将继续递归地调用自己,直到堆栈溢出。

像这样的函数,依赖于每个递归调用分解要完成的工作,必须使用特殊情况的字符串——因为您不能分解一个字符串级别以下的字符串,甚至一个字符串也像一个列表。

注意: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__()更基本,所以我改变了上面的内容。

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

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

一般来说,迭代对象的函数不仅适用于元组和列表,也适用于字符串,这是一个特性而不是缺陷。你当然可以使用isinstance或duck typing来检查参数,但为什么要这样做呢?

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

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

带有PHP风格的Python:

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

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

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

毕竟,非-str类型(例如,维护一些复杂递归结构的用户定义类型)可能会导致@steveha srepr函数导致无限递归。虽然承认这不大可能,但我们不能忽视这种可能性。因此,我们不应该在srepr中使用特殊的str,而是应该阐明当出现无限递归时,我们希望srepr做什么。

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

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

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

我在测试用例中这样做。

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))

没有在发电机上进行测试,我认为如果通过发电机,你会在下一次“屈服”时留下,这可能会把下游的事情搞砸。不过,这是一个unittest

H = "Hello"


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

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版更改:移动了“集合抽象基类”的全局命名空间;从abccollections.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."

我找到了这样一个名为Is_sequence在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))

我已经核实了,它符合你的需求。

就这么做

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

简单的方法…使用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

为了提高可读性和最佳实践,请尝试以下方法:

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

希望能有所帮助。

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

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

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

有趣的是,对于“in place”赋值+=,如果lst列表(而不是元组),则在任何情况下都不会引发TypeError。这就是为什么作业是这样做的。也许有人能解释一下原因。

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

因此,要检查list和tuple,它将是:

from typing import List, Tuple


def isit(value):
return isinstance(value, List) or isinstance(value, Tuple)
assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string"

duck-typing的另一个版本,用于帮助区分类似字符串的对象和其他类似序列的对象。

类字符串对象的字符串表示形式就是字符串本身,所以你可以检查是否从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兼容的对象和所有类型的可迭代对象。

在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