Pythonic是否可以根据函数的输入返回可迭代或不可迭代的函数?

时间:2009-09-22 17:16:58

标签: python return-value

(阅读Alex答案后更新了标题和内容)

一般来说,我认为函数有时会根据其参数返回一个可迭代的,有时是一个单项,这被认为是不好的形式(非Pythonic)。

例如,struct.unpack总是返回一个元组,即使它只包含一个项目。

我正在尝试最终确定模块的API,并且我有一些函数可以采用一个或多个参数(通过*args),如下所示:

a = s.read(10)        # reads 10 bits and returns a single item
b, c = s.read(5, 5)   # reads 5 bits twice and returns a list of two items.

因此,如果只有一个参数,则返回单个项目,否则返回一个列表。现在我认为这很好,并没有让人感到困惑,但我怀疑其他人可能不同意。

这些函数最常见的用例是只返回一个项目,所以总是返回一个列表(或元组)感觉不对:

a, = s.read(10)       # Prone to bugs when people forget to unpack the object
a = s.read(10)[0]     # Ugly and it's not clear only one item is being returned

另一种选择是有两个功能:

a = s.read(10)
b, c = s.read_list(5, 5)

这没关系,但它使API混乱,要求用户记住两倍的功能而不添加任何值。

所以我的问题是:有时会返回一个可迭代的,有时候一个项目会让人感到困惑和非Pythonic?如果是这样,最好的选择是什么?


更新:我认为普遍的共识是,有时只返回一个迭代非常顽皮。我认为大多数情况下最好的选择是始终返回iterable,即使它只包含一个项目。

话虽如此,对于我的具体情况,我想我会选择分成两个函数(read(item) / readlist(*items)),理由是我认为单项案例会发生很多比多项目案例更常见,因此它更易于使用,并且API更改对用户来说不那么成问题。

谢谢大家。

7 个答案:

答案 0 :(得分:12)

如果你有时会返回迭代器,而另一些则返回单个对象,我会说返回总是一个迭代器,所以你不必考虑它。

通常,你会在需要迭代器的上下文中使用该函数,所以如果你必须检查它是一个迭代列表或者一个对象只做一次工作,那么它更容易返回一个迭代器并且总是迭代,即使它只有一次。

如果您需要返回一个元素,那么只需使用if len(var):

请记住,一致性是一种有价值的好处。

我倾向于返回一个一致的对象,而不是相同的类型,但如果我返回一个可迭代的,我总是返回一个可迭代的。

答案 1 :(得分:2)

一般来说,我不得不说返回两种不同的类型是不好的做法。

想象一下,下一位开发人员会来阅读和维护您的代码。首先,他/她将使用您的函数读取一个方法并认为“啊,read()返回一个项目。”

稍后他们会看到代码将read()的结果视为列表。充其量只会混淆它们并迫使它们检查read()的用法。在最坏的情况下,他们可能会认为使用read()实现了一个错误并尝试修复它。

最后,一旦他们理解了read()返回两种可能的类型,他们将不得不问自己“我可能需要准备第三种返回类型吗?”

这让我想起了这样一句话:“代码好像下一个维护你的代码的人是一个知道你住在哪里的杀人狂。”

答案 2 :(得分:2)

根据参数返回单个对象或可迭代对象,绝对难以处理。但是你的标题中的问题更加通用,并且标准库的函数避免(或“大多数避免”)基于参数返回不同类型的断言是非常不正确的。有许多反例。

函数copy.copycopy.deepcopy返回与其参数相同的类型,因此它们当然“根据参数返回不同的类型”。 “返回与输入相同的类型”实际上非常常见 - 你可以在这里分类,“从一个放置它的容器中取回一个对象”,虽然通常是用方法而不是函数完成的;-) 。但同样,请考虑itertools.repeat(一旦迭代其返回的迭代器),或者说,filter ......:

>>> filter(lambda x: x>'f', 'zaplepidop')
'zplpiop'
>>> filter(lambda x: x>'f', list('zaplepidop'))
['z', 'p', 'l', 'p', 'i', 'o', 'p']

过滤字符串会返回一个字符串,过滤列表会返回一个列表。

但等等,还有更多! - )函数pickle.loads及其朋友(例如在模块marshal& c中)根据您的值返回完全类型的对象作为一个论点传递。内置函数eval(以及类似input,在Python 2中也是如此。*)。这是第二种常见模式:构造或重构一个对象,由一个或多个可能类型的参数值决定,并返回它。

我知道你所观察到的特定反模式没有很好的例子(而且我确实认为这是一种反模式,温和地 - 不是因为任何高调的原因,只是因为它讨厌并且不方便处理;-)。请注意,我举例说明的这些案例非常方便 - 这是大多数标准库问题中的真正设计判别! -

答案 3 :(得分:1)

我这样做的唯一情况是使用参数化函数或方法,其中调用者给出的一个或多个参数确定返回的类型;例如,一个“工厂”函数,它返回一个逻辑上相似的对象系列之一:

newCharacter = characterFactory("human", "male", "warrior")

在一般情况下,调用者无法指定,我会避免使用“巧克力盒”行为。 :)

答案 4 :(得分:1)

这可能不是“pythonic”的问题,而是“好设计”的问题。如果你回归不同的东西 AND 没有人必须对它们进行类型检查,那么它可能没问题。那是你的多态性。 OTOH,如果打电话者必须“刺破面纱”那么你就会遇到设计问题,这被认为违反了Liskov替代原则。 Pythonic与否,它显然不是OO设计,这意味着它将容易出现错误和编程不便。

答案 5 :(得分:1)

我会阅读(整数)和read_list( iterable )。

这样你就可以读取(10)并获得单个结果和read_list([5,5,10,5])并返回结果列表。这更加灵活和明确。

答案 6 :(得分:0)

在python列表中是对象:)所以没有类型不匹配