列表和生成器 - 呼叫者或职能责任

时间:2013-02-07 07:47:27

标签: python

当调用接受列表的函数时,谁负责(调用者 - 用户或调用 - 函数)以确保它是list而不是generator

一个例子:

>>> def print_collection(coll):
...     for element in coll:
...         print element

>>> def print_collection_twice(coll):
...     print_collection(coll)
...     print_collection(coll)

使用列表可以毫无意外地使用:

>>> print_collection_twice( [x*2 for x in xrange(3)] )
0
2
4
0
2
4

使用生成器,显然它只打印一次,这可能会导致一个讨厌的错误:

>>> print_collection_twice( (x*2 for x in xrange(3)) )
0
2
4

最佳做法在这里是什么?函数是否应该采用列表,并且用户负责提供list,或者函数是否应该始终real_list = list(input_list),以便用户不关心?

修改

知道如何检查元素的类型和assert,我的问题是相当高的水平

3 个答案:

答案 0 :(得分:3)

任何一种方法都是可以防御的。函数负责记录它想要什么类型的参数,以及调用者传递与文档一致的参数的责任。如果函数说它想要一个列表而你传递一个生成器,则无法保证它能够正常工作。

真正的问题是该功能应该它想要什么,答案是它应该说出它需要什么,而不是更多。所以如果你真正需要的只是一个可迭代的话,不要说你需要一个列表。一般来说,如果你的函数需要使用一般迭代所没有的列表特征(例如索引),那么它应该只使用这些特性,如果有人传入的参数没有,那么自然会引发异常支持他们。如果您的功能不需要这些功能,那么它不需要列表。

你的例子有点不切实际,因为它只是打印参数。在现实生活中,除了消耗迭代之外,你几乎总是需要做一些事情,而“你需要做的事情”的性质将澄清你应该接受什么样的论证。但是,对于您的具体示例,我会说是,请在其上调用list(在print_collection_twice内,而不是在print_collection内)。原因是print_collection_twice想要多次使用数据,这对于通用迭代是不可能的。

答案 1 :(得分:1)

最佳做法当然是记录您需要的内容。记录参数应该是可迭代的还是序列的。 Python的哲学是使用duck-typing,所以你应该只是尝试使用参数,就像它是一个序列一样。

如果要检查参数是否为序列,在不创建新列表的情况下执行此操作的简单方法是使用len内置函数:

>>> len(iter([1,2,3]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'listiterator' has no len()

如果您收到异常,您可以调用listtuple来获取序列或让异常通过并让用户处理它。选择哪种“政策”取决于它,完全取决于您。 Python程序员应该仔细阅读文档并传递可以正常工作的参数,这样你就可以声明你想要一个iterable作为参数,并且总是调用list来获取序列,或者说你想要一个序列并引发错误的状态如果对象是可迭代的。我不认为当你允许迭代时,说明参数应该是一个序列。

顺便说一下,如果您只是想在迭代上多次迭代,可以使用itertools.tee

例如:

def print_twice(iterable):
    old, new = itertools.tee(iterable)
    for element in old:
        # do stuff
    for element in new:
        # do stuff

答案 2 :(得分:0)

在我看来,它取决于函数内部的应用程序。重要的是,你文档你的函数是否也只接受列表或迭代器。函数内部的显式list()调用可能会导致长列表的超额开销,如果您只想迭代列表/生成器一次,则这不是必需的。