Python:允许将单个字符串和字符串列表传递给函数

8

我有一个函数,它调用了一个REST API。其中一个参数是由逗号分隔的名称列表。为了生成它,我这样做:

','.join(names)

然而,我想让用户提供单个名称。问题是,如果names = ERII,它会得到['E', 'R', 'I', 'I']的结果,但是我需要的是['ERII']

现在,我可以强制用户输入只有一个值的列表(names=['ERRI']或者names=('ERII',))。 我更喜欢允许用户提供单个字符串。有没有巧妙的方法可以实现,而不需要使用if else语句来检查提供的值是否为Iterable

此外,我不确定在这里使用何种最佳实践,是强制提供列表还是允许提供单个字符串?


@Carcigenicate,我没有一个叫做name的变量。 - Antoine Viscardi
2
那么在参数中使用 *names 怎么样?这样用户就可以发送一个名称或五个名称,而不需要括号 - 或者,如果他有一个列表方便,他可以直接将 *lst 发送给你。 - abarnert
3个回答

24

能够既是一种事物,又可以是一个迭代器或多个事物的参数是一种代码异味。当这个事物是一个字符串时,情况就更糟了,因为字符串 可迭代的,甚至是一个序列(所以您对于isinstance(names,Iterable)的测试会做错事情)。

Python标准库确实有一些这样的情况 - 最恶名昭彰的是str.__mod__ - 但它们大多数都在另一个方向上出现错误,明确要求元组而不是任何可迭代对象,并且它们中的大多数被认为是错误,或者至少不会再添加到今天的语言中。有时候仍然是最好的答案,但是这种异味应该使您在执行操作之前三思。

我不知道您的具体用例是什么,但我认为以下方法会更加优雅:

def spam(*names):
    namestr = ','.join(names)
    dostuff(namestr)
现在用户可以像这样调用它:

现在用户可以像这样调用它:

spam('eggs')
spam('eggs', 'cheese', 'beans')

或者,如果他们碰巧有一个列表,也很容易:

spam(*ingredients)
如果那不合适,另一个选择是关键字参数,甚至只有关键字参数:
def spam(*, name=None, names=None):
    if name and names:
        raise TypeError('Not both!')
    if not names: names = [name]
但如果最佳设计确实是一个字符串或一个(非字符串的)字符串可迭代对象,或者是一个字符串或一个字符串元组,那么标准的方法就是类型转换。它看起来可能有点丑陋,但它提醒您正在做什么,并以最惯用的方式完成它。
def spam(names):
    if isinstance(names, str):
        names = [names]
    dostuff(names)

谢谢你的回答,我很感激你对最佳编码实践的建议。在我的具体情况下,这是我的函数签名:def get_symbols(names=None, ids=None, id=None)。我已经在使用ids的关键字模式。我之所以只对ids而不是names这样做,是因为这是一个Python REST API包装器的设计方式(有这三个字段),我希望保持这种方式,以便用户可以参考他们的文档并仍然似乎使用我的代码。 - Antoine Viscardi
“*”选项也很不错。但是,当函数接受多个参数时,我不熟悉这种结构。Python如何区分传递的*names列表和函数签名中的下一个参数?我在哪里可以找到相关文档? - Antoine Viscardi
@AntoineViscardi *args 必须是最后一个参数,除了关键字参数。实际文档 可能有点难以理解;首先请参阅官方教程中的 More on Defining Functions,特别是 Arbitrary Argument Lists 部分。 - abarnert
假设您正在使用Python 3,如果您编写 def get_symbols(*names, ids=None, id=None),那么 idsid 将成为仅限关键字参数——也就是说,您仍然只能通过名称来调用它们,例如 get_symbols('spam', 'eggs', ids=[1,2,3]) - abarnert

2
使用isinstance()来识别输入的类型可能会提供一种解决方案:
def generate_names(input_term):
    output_list = []
    if isinstance(input_term,str):
        output_list.append(','.join(input_term.split()))
    elif isinstance(input_term,list):
        output_list.append(','.join(input_term))
    else:
        print('Please provide a string or a list.')
    return(output_list)

这将允许您输入列表、单个名称的字符串以及包含多个名称(用空格分隔)的字符串:

name = 'Eric'
name1 = 'Alice John'
namelist = ['Alice', 'Elsa', 'George']

应用此函数:

print(generate_names(name))
print(generate_names(name1))
print(generate_names(namelist))

获取:

['Eric']

['Alice,John']

['Alice,Elsa,George']


1

我会允许列表和单个字符串作为参数。此外,我认为if/else的解决方案没有任何问题,只是你可以直接检查参数是否是 str 类的实例(而不是检查参数是否可迭代):

def foo(arg1):
    if isinstance(arg1, str):
        print("It's a string!")
    else:
        print("It's not a string!")

在检查参数是否可迭代时要小心 - 字符串和列表都是可迭代的。


是的,我想现在这就是我要做的!而且你说得对,检查Iterable不是我的好主意:事实上,字符串是Iterable导致了我的问题...哎呀:p - Antoine Viscardi

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