Pythonic方式将变量转换为列表
我有一个函数,它的输入参数可以是一个元素,也可以是一个元素的列表。如果这个参数是单个元素,我就把它放进一个列表里,这样我就可以以一致的方式来处理输入。
现在我有这样的代码:
def my_func(input):
if not isinstance(input, list): input = [input]
for e in input:
...
我在使用一个现有的API,所以我不能改变输入参数。用isinstance()来判断感觉有点不太好,有没有什么更“正规”的方法来做到这一点呢?
8 个回答
首先,没有一种通用的方法可以区分“单个元素”和“元素列表”,因为根据定义,列表本身可以是另一个列表的一个元素。
我认为你需要先定义一下你可能会遇到的数据类型,这样你可以有:
- 任何列表的子类与其他任何东西进行比较
- 可以用
isinstance(input, list)
来测试(所以你的例子是正确的)
- 可以用
- 除了字符串以外的任何序列类型(在 Python 2.x 中是
basestring
,在 Python 3.x 中是str
)- 使用序列元类:
isinstance(myvar, collections.Sequence) and not isinstance(myvar, str)
- 使用序列元类:
- 某些序列类型与已知的情况进行比较,比如
int
、str
、MyClass
- 可以用
isinstance(input, (int, str, MyClass))
来测试
- 可以用
- 任何可迭代的对象,除了字符串:
- 可以进行测试
.
try:
input = iter(input) if not isinstance(input, str) else [input]
except TypeError:
input = [input]
通常情况下,字符串(包括普通字符串和Unicode字符串)是你希望当作“单个元素”来处理的唯一可迭代对象。basestring
这个内置类型就是为了让你可以用isinstance
来检查这两种字符串,所以在这种特殊情况下,它非常好用;-)。
所以我建议的最通用的方法是:
if isinstance(input, basestring): input = [input]
else:
try: iter(input)
except TypeError: input = [input]
else: input = list(input)
这就是处理除了字符串以外的所有可迭代对象的方法,把字符串、数字和其他不可迭代的东西当作单个值(转化为单项列表)。
我明确地把每种可迭代对象都转成一个列表,这样你就知道可以进行各种列表操作,比如排序、重复遍历、添加或删除项目以便于遍历等等,而不会改变实际输入的列表(如果它确实是一个列表的话;-)。如果你只需要一个简单的for
循环,那么最后一步就不必要了(如果输入是一个很大的文件,这一步反而没什么帮助),我建议用一个辅助生成器来代替:
def justLoopOn(input):
if isinstance(input, basestring):
yield input
else:
try:
for item in input:
yield item
except TypeError:
yield input
在你每一个需要这种参数规范化的函数中,你只需使用:
for item in justLoopOn(input):
你甚至可以在其他情况下使用一个辅助的规范化函数(如果你需要一个真正的列表来做其他复杂的事情);实际上,在这种(比较少见的)情况下,你可以直接这样做:
thelistforme = list(justLoopOn(input))
这样的话,复杂的规范化逻辑就只在一个地方,正如它应该的那样!-)
我很喜欢Andrei Vajna的建议,使用hasattr(var,'__iter__')
来检查一个变量是否可以被迭代。这里有一些常见的Python类型的结果:
>>> hasattr("abc","__iter__")
False
>>> hasattr((0,),"__iter__")
True
>>> hasattr({},"__iter__")
True
>>> hasattr(set(),"__iter__")
True
这个方法还有一个好处,就是把字符串当作不可迭代的对象来处理。因为字符串有点特殊,有时候我们想把它当作一个整体来看,有时候又想把它看作一串字符。
需要注意的是,在Python 3中,str
类型实际上是有__iter__
这个属性的,所以这个方法在这里不适用:
>>> hasattr("abc", "__iter__")
True