18

我想编写一个接受参数的函数,该参数可以是序列或单个值。值的类型是 str、int 等,但我希望它被限制为硬编码列表。换句话说,我想知道参数 X 是序列还是我必须转换为序列以避免以后出现特殊情况的东西。我可以做

type(X) in (list, tuple)

但可能还有其他我不知道的序列类型,也没有通用的基类。

-N。

编辑:请参阅下面的“答案”,了解为什么大多数这些答案对我没有帮助。也许你有更好的建议。

4

12 回答 12

19

从 2.6 开始,使用抽象基类

>>> import collections
>>> isinstance([], collections.Sequence)
True
>>> isinstance(0, collections.Sequence)
False

此外,可以自定义 ABC 以解决例外情况,例如不将字符串视为序列。这里有一个例子:

import abc
import collections

class Atomic(object):
    __metaclass__ = abc.ABCMeta
    @classmethod
    def __subclasshook__(cls, other):
        return not issubclass(other, collections.Sequence) or NotImplemented

Atomic.register(basestring)

注册后,原子类可以与isinstanceissubclass一起使用:

assert isinstance("hello", Atomic) == True

这还是比硬编码列表好很多,因为你只需要注册规则的例外,代码的外部用户可以注册自己的。

请注意,在Python 3中,用于指定元类的语法发生了变化,并且basestring抽象超类被删除了,这需要使用类似以下的东西来代替:

class Atomic(metaclass=abc.ABCMeta):
    @classmethod
    def __subclasshook__(cls, other):
        return not issubclass(other, collections.Sequence) or NotImplemented

Atomic.register(str)

如果需要,可以编写同时兼容 Python 2.6+3.x 的代码,但是这样做需要使用稍微复杂一点的技术来动态创建所需的抽象基类,从而避免由于元类语法差异而导致的语法错误. 这与本杰明彼得森的六个模块的with_metaclass()功能基本相同。

class _AtomicBase(object):
    @classmethod
    def __subclasshook__(cls, other):
        return not issubclass(other, collections.Sequence) or NotImplemented

class Atomic(abc.ABCMeta("NewMeta", (_AtomicBase,), {})):
    pass

try:
    unicode = unicode
except NameError:  # 'unicode' is undefined, assume Python >= 3
    Atomic.register(str)  # str includes unicode in Py3, make both Atomic
    Atomic.register(bytes)  # bytes will also be considered Atomic (optional)
else:
    # basestring is the abstract superclass of both str and unicode types
    Atomic.register(basestring)  # make both types of strings Atomic

operator在 2.6 之前的版本中,模块中有类型检查器。

>>> import operator
>>> operator.isSequenceType([])
True
>>> operator.isSequenceType(0)
False
于 2008-11-20T17:52:46.520 回答
5

上述所有方式的问题在于 str 被认为是一个序列(它是可迭代的,具有getitem等),但它通常被视为单个项目。

例如,一个函数可以接受一个参数,该参数可以是文件名或文件名列表。函数从后者中检测第一个的最 Pythonic 方式是什么?

根据修改后的问题,听起来您想要的更像是:

def to_sequence(arg):
    ''' 
    determine whether an arg should be treated as a "unit" or a "sequence"
    if it's a unit, return a 1-tuple with the arg
    '''
    def _multiple(x):  
        return hasattr(x,"__iter__")
    if _multiple(arg):  
        return arg
    else:
        return (arg,)

>>> to_sequence("a string")
('a string',)
>>> to_sequence( (1,2,3) )
(1, 2, 3)
>>> to_sequence( xrange(5) )
xrange(5)

这不能保证处理所有类型,但它可以很好地处理您提到的情况,并且应该为大多数内置类型做正确的事情。

使用它时,请确保接收到它的输出的任何东西都可以处理迭代。

于 2009-01-08T19:35:43.250 回答
4

此处描述了序列: https ://docs.python.org/2/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer-xrange

所以序列与可迭代对象不同。我认为序列必须实现 __getitem__,而可迭代对象必须实现__iter__。因此,例如 string 是序列并且不实现__iter__,xrange 对象是序列并且不实现__getslice__

但是从你看到的想要做的事情来看,我不确定你想要序列,而是可迭代的对象。所以去hasattr("__getitem__", X)你想要的序列,但hasattr("__iter__", X)如果你不想要字符串,那就去吧。

于 2008-11-20T15:12:49.137 回答
4

恕我直言,python 方法是将列表作为 *list 传递。如:

myfunc(item)
myfunc(*items)
于 2008-12-01T18:59:11.283 回答
3

在这种情况下,我更喜欢始终采用序列类型或始终采用标量。字符串不会是唯一在此设置中表现不佳的类型。相反,任何具有聚合使用并允许对其部分进行迭代的类型都可能行为不端。

于 2008-11-20T14:52:34.247 回答
3

最简单的方法是检查是否可以将其转换为迭代器。IE

try:
    it = iter(X)
    # Iterable
except TypeError:
    # Not iterable

如果您需要确保它是可重新启动或随机访问序列(即不是生成器等),那么这种方法是不够的。

正如其他人所指出的,字符串也是可迭代的,因此如果您需要排除它们(如果递归通过项目尤其重要,因为 list(iter('a')) 再次给出 ['a'] ,那么您可能需要专门排除他们与:

 if not isinstance(X, basestring)
于 2008-11-20T15:39:32.700 回答
2

我是新来的,所以我不知道正确的方法是什么。我想回答我的答案:

上述所有方式的问题在于,它str被认为是一个序列(它是可迭代的、具有__getitem__等),但它通常被视为单个项目。

例如,一个函数可以接受一个参数,该参数可以是文件名或文件名列表。函数从后者中检测第一个的最 Pythonic 方式是什么?

我应该将此作为新问题发布吗?修改原版?

于 2008-11-23T10:32:51.570 回答
1

我想我要做的是检查对象是否有某些方法表明它是一个序列。我不确定是否有关于什么构成序列的官方定义。我能想到的最好的是,它必须支持切片。所以你可以说:

is_sequence = '__getslice__' in dir(X)

您还可以检查您将要使用的特定功能。

正如 pi 在评论中指出的那样,一个问题是字符串是一个序列,但您可能不想将其视为一个序列。您可以添加一个类型不是 str 的显式测试。

于 2008-11-20T14:05:04.280 回答
1

如果字符串是问题,检测一个序列并过滤掉字符串的特殊情况:

def is_iterable(x):
  if type(x) == str:
    return False
  try:
    iter(x)
    return True
  except TypeError:
    return False
于 2008-12-04T14:43:24.013 回答
0

修改后的答案:

我不知道您对“序列”的想法是否与 Python 手册中所说的“序列类型”相匹配,但如果确实如此,您应该寻找 __Contains__ 方法。这就是 Python 用来实现检查“如果对象中有东西:”的方法

if hasattr(X, '__contains__'):
    print "X is a sequence"

我原来的答案:

我会检查您收到的对象是否实现了迭代器接口:

if hasattr(X, '__iter__'):
    print "X is a sequence"

对我来说,这是与您对序列的定义最接近的匹配,因为这将允许您执行以下操作:

for each in X:
    print each
于 2008-11-20T14:18:29.853 回答
0

你问错问题了。您不要尝试在 Python 中检测类型;你检测行为。

  1. 编写另一个处理单个值的函数。(我们称之为_use_single_val)。
  2. 编写一个处理序列参数的函数。(我们称之为_use_sequence)。
  3. 编写调用上述两个的第三个父函数。(称之为 use_seq_or_val)。用异常处理程序包围每个调用以捕获无效参数(即不是单个值或序列)。
  4. 编写单元测试以将正确和不正确的参数传递给父函数,以确保它正确捕获异常。

    def _use_single_val(v):
        print v + 1  # this will fail if v is not a value type

    def _use_sequence(s):
        print s[0]   # this will fail if s is not indexable

    def use_seq_or_val(item):    
        try:
            _use_single_val(item)
        except TypeError:
            pass

        try:
            _use_sequence(item)
        except TypeError:
            pass

        raise TypeError, "item not a single value or sequence"

编辑:修改为处理问题中询问的“序列或单个值”。

于 2008-11-20T16:46:48.213 回答
-1

您可以在内置 len() 函数中传递参数并检查这是否会导致错误。正如其他人所说,字符串类型需要特殊处理。

根据文档, len 函数可以接受序列(字符串、列表、元组)或字典。

您可以使用以下代码检查对象是否为字符串:

x.__class__ == "".__class__
于 2008-11-20T14:08:25.053 回答