【发布时间】:2010-12-22 13:03:20
【问题描述】:
这是我通常所做的,以确定输入是list/tuple - 但不是str。因为很多时候我偶然发现一个函数错误地传递了str 对象的错误,而目标函数执行for x in lst 假设lst 实际上是list 或tuple。
assert isinstance(lst, (list, tuple))
我的问题是:有没有更好的方法来实现这一点?
【问题讨论】:
这是我通常所做的,以确定输入是list/tuple - 但不是str。因为很多时候我偶然发现一个函数错误地传递了str 对象的错误,而目标函数执行for x in lst 假设lst 实际上是list 或tuple。
assert isinstance(lst, (list, tuple))
我的问题是:有没有更好的方法来实现这一点?
【问题讨论】:
仅在 python 2 中(不是 python 3):
assert not isinstance(lst, basestring)
实际上是你想要的,否则你会错过很多类似于列表但不是list 或tuple 的子类的东西。
【讨论】:
basestring 消失了,您只需检查 isinstance(lst, str)。
set、生成器表达式、迭代器。有像 mmap 这样的异国情调的东西,像 array 这样不那么异国情调的东西很像列表,而且我可能忘记了更多。
lst 是可迭代的,而原来的却是可迭代的(例如 int 将通过此检查)
assert isinstance(lst, (list, tuple)) and assert not isinstance(lst, basestring)
请记住,在 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__() 更基本,所以我更改了上述内容。
【讨论】:
srepr 是一个非常有趣的想法。但我对是否需要特例 str 持有不同的意见。是的,str 是迄今为止最明显和最常见的可迭代对象,它会导致 srepr 中的无限递归。但是我可以很容易地想象用户定义的迭代以相同的方式运行(无论有没有充分的理由)。而不是特例str,我们应该承认这种方法可能会遇到无限递归,并同意某种处理方式。我会在答案中发表我的建议。
srepr() 可以。我们需要一个递归函数来处理诸如嵌套在另一个列表中的列表之类的事情;但是对于字符串,我们宁愿将它们打印为"foo" 而不是<'f', 'o', 'o'>。因此,在这里对字符串进行显式检查很有意义。此外,实际上没有任何其他数据类型的示例,其中迭代 always 返回可迭代且递归将始终导致堆栈溢出,因此我们不需要特殊属性来测试这一点(“实用性胜过纯洁”)。
__iter__() 方法,但在 Python 2 中没有。您在 is_sequence() 中缺少括号,它应该是:return (not hasattr(arg, "strip") and (hasattr(arg, "__getitem__") or hasattr(arg, "__iter__")))
H = "Hello"
if type(H) is list or type(H) is tuple:
## Do Something.
else
## Do Something.
【讨论】:
if isinstance( H, (list, tuple) ): ... 更短更清晰。
if type(H) in [list, tuple]:
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__。
Sequence 是一个抽象类。
and not isinstance(obj, (str, collections.abc.ByteString))
带有 PHP 风格的 Python:
def is_array(var):
return isinstance(var, (list, tuple))
【讨论】:
__getitem__。名称也具有误导性,因为还有数组模块。 var 也可以是 numpy.ndarray 或任何其他类型,它具有__getitem__。正确答案见stackoverflow.com/a/1835259/470560。
str 也有 __getitem__ 因此您的支票不排除 str
__getitem__ 是个坏建议。
一般来说,迭代对象的函数适用于字符串以及元组和列表这一事实比错误更具功能。您当然可以使用isinstance 或duck typing 来检查参数,但是为什么要这样做呢?
这听起来像是一个反问,但事实并非如此。 “我为什么要检查参数的类型”的答案?可能会建议解决实际问题,而不是感知问题。为什么将字符串传递给函数时会出现错误?另外:如果将字符串传递给此函数时出现错误,那么如果将其他一些非列表/元组可迭代对象传递给它,这也是错误吗?为什么,或者为什么不?
我认为这个问题最常见的答案可能是编写f("abc") 的开发人员期望该函数的行为就像他们编写了f(["abc"]) 一样。在某些情况下,保护开发人员免受自己的侵害可能比支持遍历字符串中的字符的用例更有意义。但我会先仔细考虑一下。
【讨论】:
试试这个以获得可读性和最佳实践:
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
希望对你有帮助。
【讨论】:
AttributeError: module 'types' has no attribute 'ListType'
from typing import List --> isinstance([1, 2, 3], List = True, 和 isinstance("asd", List) = False
str 对象没有__iter__ 属性
>>> hasattr('', '__iter__')
False
所以你可以检查一下
assert hasattr(x, '__iter__')
这也会为任何其他不可迭代的对象提出一个很好的AssertionError。
编辑: 正如 Tim 在 cmets 中提到的,这仅适用于 python 2.x,而不适用于 3.x
【讨论】:
hasattr('','__iter__') 返回 True。当然这是有道理的,因为你可以遍历一个字符串。
这里不是直接回答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 或其他东西。
我找到了一个名为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))
我已经验证它满足您的需求。
【讨论】:
我在我的测试用例中这样做。
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))
在生成器上未经测试,我认为如果通过生成器,您将处于下一个“产量”,这可能会在下游搞砸。但话又说回来,这是一个“单元测试”
【讨论】:
以“鸭式打字”的方式,怎么样
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
所有变体实际上并不改变变量的内容,而是暗示重新赋值。我不确定这在某些情况下是否不受欢迎。
有趣的是,如果 lst 是 list(不是 tuple )。这就是为什么以这种方式完成任务的原因。也许有人可以解释为什么会这样。
【讨论】:
另一个版本的鸭子类型有助于区分字符串对象和其他序列对象。
类字符串对象的字符串表示就是字符串本身,所以你可以检查你是否从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 兼容的所有对象以及所有类型的可迭代对象。
【讨论】:
最简单的方法...使用any 和isinstance
>>> 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
【讨论】:
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)
【讨论】:
assert (type(lst) == list) | (type(lst) == tuple), "Not a valid lst type, cannot be string"
【讨论】:
就这样做
if type(lst) in (list, tuple):
# Do stuff
【讨论】:
在 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__ 方法,当然字符串也有它。
我倾向于这样做(如果我真的,真的不得不这样做的话):
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_var 是list() 的子类的一个实例,会发生什么?您的代码不知道如何处理它,即使它可以在“使用列表执行某些操作”代码下完美运行。而且你很少需要关心列表和元组之间的区别。抱歉,-1。
type(tuple()) -- tuple 即可。列表也一样。此外,str 和 unicode 都扩展了 basestring,这是真正的字符串类型,因此您需要检查它。
type(i) is list。此外,type(list()) 只是 list 本身......最后,这不能与子类正常工作。如果i 实际上是 OrderedDict 或某种命名元组,则此代码会将 is 视为字符串。