方法中的单个字符串或字符串列表

13

我遇到过这种情况多次。我正在处理很多可以接受字符串列表的方法。有几次我不小心传递了一个单独的字符串,然后它被拆成一个列表,并且每个字符都被使用了,这不是期望的行为。

def test(a,b):
    x = []
    x.extend(a)
    x.extend(b)
    return x

x = [1,2,3,4]

我不希望发生的事情:

test(x,'test')
[1, 2, 3, 4, 't', 'e', 's', 't']

我不得不使用一种奇怪的语法:

test(x,['list']) 

我希望这些可以隐式地工作:
test(x,'list')
[1, 2, 3, 4, 'test']

test(x,['one', 'two', 'three'])
[1, 2, 3, 4, 'one', 'two', 'three']

我觉得有一种“Pythonic”的方式或者涉及鸭子类型的方法可以解决这个问题,但我还没有想到。我知道我可以使用isinstance()函数来检查是否为字符串,但我觉得有更好的方法。
编辑:我正在使用Python 2.4.3。

重新解释 list.extend() 预期行为以克服与意外输入相关的问题可能不是一个好主意。 - Austin Marshall
6个回答

13
请使用这个。
def test( x, *args ):

现在你可以做到:
test( x, 'one' )

并且。
test( x, 'one', 'two' )

并且

test( x, *['one', 'two',] )

@NullUserExceptionఠ_ఠ:你说得对。单元测试比这种语法上的废话更重要。然而,我想我会采用“没有单元测试”的方法,因为问题似乎暗示了这一点。 - S.Lott
1
@NullUserException:Downvotes 的意思是“这是错误的”或“在给定的上下文中没有帮助”。它们并不意味着“我在风格上不同意”。这个答案肯定是有帮助的。 - Sven Marnach
@SvenMarnach 是的,我已经撤销了投反对票。但我仍然不喜欢这种方法。 - NullUserException
使用API设计来消除歧义是一个非常合理和干净的解决方案。 - Raymond Hettinger

9

很抱歉,但这确实是使用 isinstance() 的少数几种情况之一。然而,建议测试 list 的其他答案其实是在反过来: str(对于Python 2.x为 unicode)才是异常类型,所以应该测试这些类型:

def test(a,b):
    x = []
    if isinstance(b, str): b = [b]
    x.extend(a)
    x.extend(b)
    return x

或者在 Python 2.x 中测试 isinstance(b, basestring)

我有几个实例,其中我传递了一个参数,该列表是字典键的列表(作为字符串)。传递“test”将尝试查找mydict ['t'],mydict ['e']等。也许isinstance对于上面的示例最好。这肯定是看到意图的最简单方法。 - shadowland
1
进一步说,API 应该提供一个默认参数,以便调用者可以决定哪些可迭代类型被视为原子。这避免了硬编码行为。def test(a, b, atomic=(basestring, buffer)): ... - Raymond Hettinger

3

来自Python之禅

Explicit is better than implicit.

在我看来,您的第一个例子十分明确。它接受两个值,并以众所周知的方式处理它们。虽然这对于新的Python程序员来说有点奇怪,但它的行为就像期望一样。list.extend接受列表,因此它将字符串视为字符列表。
您的提议改变了list.extend的语义,使其在处理字符串时等同于list.append,但这仅适用于字符串。这会让希望您的函数将字符串视为字符列表的人感到非常惊讶,而且他们还必须像test(x,list('test'))这样调用它。
您确实可以按照要求做,但请不要这样做。 :-)

同意。如果可以避免,最好不要在输入上强制指定显式类型要求。 - Austin Marshall
我认为pod2metra的回答已经解决了这个问题,你同意吗?它使用了__iter__属性,并且没有强制任何特定类型。 - shadowland
我对此没有任何问题。 - Kirk Strauser

2

嗯,也许这是一种简单的方法:

>>> def a(*args):
...     x=[]
...     for i in args:
...             if '__iter__' in dir(i):
...                     x+=list(i)
...             else:
...                     x.append(i)
...     return x
... 
>>> a(1,2,3,4)
[1, 2, 3, 4]
>>> a(1,2,[3,4])
[1, 2, 3, 4]
>>> a(1,2,[3,4],'123')
[1, 2, 3, 4, '123']
>>> a(1,2,[3,4],'123', 1231, (1, 2, 3, 4))
[1, 2, 3, 4, '123', 1231, 1, 2, 3, 4]

我甚至没想过查看 dir() 来看它是否包含 __iter__。这很棒。 - shadowland
2
在Python3中,这将失败,因为字符串具有“__iter__”属性。 - JrBenito
为了兼容Python2和3,请使用six模块,它提供了一个跨版本的方式来检查字符串类型:if isinstance(value, six.string_types): value = [value] - Dakota

1
稍作修改即可让pod2metra的答案处理所有嵌套层次的函数:
>>> def b(*args):
...     x=[]
...     for i in args:
...             if '__iter__' in dir(i):
...                     x += b(*i)
...             else:
...                     x.append(i)
...     return x
... 
>>> b(1, (2, (3, 4)))
[1, 2, 3, 4]

使用函数'a',您将得到以下内容:
>>> a(1, (2, (3, 4)))
[1, 2, (3, 4)]

我知道这不是原问题的意图,但或许这种概括可以有所帮助。


0

测试参数的类型:

def test(a,b):
    x = a if isinstance(a, list) else [a]
    x += b if isinstance(b, list) else [b]
    return x

-1 这正是 OP 想要避免的。 - NullUserException
另外,我正在使用Python 2.4.3(怪罪RedHat的低版本)。那种使用“if”的方式似乎不起作用。 - shadowland
1
@user179256 不是这样的。这个语法是在2.5版本中添加的。 - NullUserException
我刚刚注意到上面的代码似乎修改了变量'a',因为在a是列表时使用了x = a。我认为当使用x = [a]时,它不会修改方法外部的a。 - shadowland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接