当调用接受列表的函数时,谁负责(调用者 - 用户或调用 - 函数)以确保它是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
,我的问题是相当高的水平
答案 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()
如果您收到异常,您可以调用list
或tuple
来获取序列或让异常通过并让用户处理它。选择哪种“政策”取决于它,完全取决于您。 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()
调用可能会导致长列表的超额开销,如果您只想迭代列表/生成器一次,则这不是必需的。