2009-09-22 28 views
7

(标题和阅读Alex的答案后更新的内容)函数返回一个可迭代或不可迭代的函数取决于它的输入是Pythonic吗?

总的来说,我认为,它被认为是不好的形式(非Python化)的功能,有时会返回一个迭代,有时根据其参数的单个项目。

例如,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变得混乱,并且要求用户记住两倍的功能而不增加任何值。

所以我的问题是:有时会返回一个迭代,有时会单个项目混淆和un-Pythonic?如果是这样,最好的选择是什么?


更新:我觉得一般的共识是,这是很调皮只返回一个可迭代的时候。我认为大多数情况下最好的选择是总是返回迭代,即使它只包含一个项目。

话虽如此,对于我的具体情况,我想我会去分裂成两个函数(read(item)/readlist(*items)),推理是,我认为,单个项目的情况下会更经常发生比多个项目案例,所以它使得它更易于使用,并且API更改对用户来说不会有问题。

谢谢大家。

回答

12

如果你将要返回有时迭代器,以及对他人的单个对象,我想说的回报总是一个迭代器,所以你不必想想看。一般来说,你会在需要迭代器的上下文中使用该函数,所以如果你必须检查它是在哪里迭代一个列表或者一个对象只做一次工作,那么它更容易返回一个迭代器并且总是迭代,即使它是一次。

如果你需要做一些不同的事情,如果你返回一个元素,只需使用if len(var):

请记住,一致性是一个宝贵的好处。

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

+2

+1。有时成为事物,有时候成为事物清单通常是一个错误。 Python为%格式化做了这个,这被广泛认为是一个错误和令人讨厌的陷阱。 – bobince 2009-09-22 17:32:46

+0

我很害怕人们会这样说 - 当你清楚地只询问一件物品时,只是感觉很难得到一份清单! – 2009-09-22 18:27:48

+0

@Scot Griffiths:恕我直言,潜在的错误是由于过于聪明而导致简单变量可能导致的问题。为什么不使用像'def read(a_tuple):'而不是使用'* args'的方法? – voyager 2009-09-22 19:40:45

0

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

+0

够正确!我编辑了这个问题以避免混淆。 – 2009-09-22 17:27:16

1

唯一的情况是,我会这样做的一个参数化函数或方法,调用者给出的一个或多个参数决定返回的类型;例如,一个“工厂”函数返回一个逻辑上类似于家庭对象之一:

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

在一般情况下,如果调用者没有获得指定,我会避免“一盒巧克力“行为。 :)

+0

在我的特殊情况下,返回的项目数量等于函数调用中给出的项目数量,所以我不认为用户会对返回的内容感到惊讶。 – 2009-09-22 18:18:43

2

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

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

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

最后,一旦他们明白read()返回两个可能的类型,他们将不得不问自己“是否有可能需要第三个返回类型?

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

1

它可能不是“pythonic”的问题,而是“好设计”的问题。如果你返回不同的东西没有人需要对他们进行typechecks,那么它可能是好的。这是你的多态性。 OTOH,如果调用者必须“穿透面纱”,那么你有一个设计问题,被称为违反Liskov替代原则。 Pythonic与否,显然不是OO设计,这意味着它容易出现错误和编程不便。

1

我会读(整数)和read_list(可迭代)。

通过这种方式,您可以读取(10)并获取单个结果和read_list([5,5,10,5])并获取结果列表。这更加灵活和明确。

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. *中)也是如此。这是第二种常见模式:根据参数的值,广泛的(甚至是无限的)各种可能类型构造或重建一个对象,并返回它。

我不知道你观察到的具体反模式的好例子(我确实相信这是一种反模式,轻度 - 不是因为任何高福利因素,只是因为它讨厌和不方便处理与;-)。请注意,我已经举例说明了这些情况,方便又方便 - 这是大多数标准库问题中的真正设计判别式! - )

+0

你说得对,这个问题的表达方式太笼统了,它确实归结为只是一个可重复的问题与不可解决的问题。我想如果你称它为反模式,那么这就是它的死亡之锤!) – 2009-09-23 13:01:48