如何判断一个变量是可迭代的但不是字符串
我有一个函数,它可以接收一个参数,这个参数可以是一个单独的项目,也可以是两个项目:
def iterable(arg)
if #arg is an iterable:
print "yes"
else:
print "no"
所以:
>>> iterable( ("f","f") ) yes >>> iterable( ["f","f"] ) yes >>> iterable("ff") no
问题是,字符串在技术上是可以被遍历的,所以当我尝试用 arg[1]
时,不能仅仅捕捉到值错误(ValueError)。我不想使用 isinstance(),因为听说那样做不是个好习惯。
11 个回答
自从Python 2.6开始,引入了抽象基类(abstract base classes),所以在抽象基类上使用isinstance
(而不是具体的类)现在是完全可以接受的。具体来说:
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
这段代码实际上是_abcoll.py
中定义的Iterable
的一个完全复制(只改了类名)……之所以这样做能达到你想要的效果,而collections.Iterable
却不行,是因为后者特别处理了字符串,使其被认为是可迭代的,方法是在这个class
声明后,明确调用了Iterable.register(str)
。
当然,你可以很容易地通过在any
调用之前返回False
来扩展__subclasshook__
,这样可以排除你想要特别排除的其他类。
无论如何,在你将这个新模块导入为myiter
后,isinstance('ciao', myiter.NonStringIterable)
会返回False
,而isinstance([1,2,3], myiter.NonStringIterable)
会返回True
,正如你所要求的那样——在Python 2.6及以后的版本中,这被认为是进行这种检查的正确方式……定义一个抽象基类,然后在其上检查isinstance
。
截至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
使用 isinstance(我不明白为什么这被认为是不好的做法)
import types
if not isinstance(arg, types.StringTypes):
注意使用 StringTypes。这可以确保我们不会忘记一些不常见的字符串类型。
好的一面是,这也适用于派生的字符串类。
class MyString(str):
pass
isinstance(MyString(" "), types.StringTypes) # true
另外,你可能想看看这个 之前的问题。
祝好。
注意: 在 Python 3 中,行为发生了变化,因为 StringTypes
和 basestring
不再被定义。根据你的需要,你可以在 isinstance
中用 str
替换它们,或者用一个包含 (str, bytes, unicode)
的元组,比如对于 Cython 用户。
正如 @Theron Luhn 提到的,你也可以使用 six
。