我想将生成器或迭代器转换为递归列表。
我在下面编写了一个代码,但是看起来很幼稚和丑陋,在doctest中可能会被删除。
Q1。帮我好版本。
Q2。如何指定对象是不可变的?
import itertools
def isiterable(datum):
return hasattr(datum, '__iter__')
def issubscriptable(datum):
return hasattr(datum, "__getitem__")
def eagerlize(obj):
""" Convert generator or iterator to list recursively.
return a eagalized object of given obj.
This works but, whether it return a new object, break given one.
test 1.0 iterator
>>> q = itertools.permutations('AB', 2)
>>> eagerlize(q)
[('A', 'B'), ('B', 'A')]
>>>
test 2.0 generator in list
>>> q = [(2**x for x in range(3))]
>>> eagerlize(q)
[[1, 2, 4]]
>>>
test 2.1 generator in tuple
>>> q = ((2**x for x in range(3)),)
>>> eagerlize(q)
([1, 2, 4],)
>>>
test 2.2 generator in tuple in generator
>>> q = (((x, (y for y in range(x, x+1))) for x in range(3)),)
>>> eagerlize(q)
([(0, [0]), (1, [1]), (2, [2])],)
>>>
test 3.0 complex test
>>> def test(r):
... for x in range(3):
... r.update({'k%s'%x:x})
... yield (n for n in range(1))
>>>
>>> def creator():
... r = {}
... t = test(r)
... return r, t
>>>
>>> a, b = creator()
>>> q = {'b' : a, 'a' : b}
>>> eagerlize(q)
{'a': [[0], [0], [0]], 'b': {'k2': 2, 'k1': 1, 'k0': 0}}
>>>
test 3.1 complex test (other dict order)
>>> a, b = creator()
>>> q = {'b' : b, 'a' : a}
>>> eagerlize(q)
{'a': {'k2': 2, 'k1': 1, 'k0': 0}, 'b': [[0], [0], [0]]}
>>>
test 4.0 complex test with tuple
>>> a, b = creator()
>>> q = {'b' : (b, 10), 'a' : (a, 10)}
>>> eagerlize(q)
{'a': ({'k2': 2, 'k1': 1, 'k0': 0}, 10), 'b': ([[0], [0], [0]], 10)}
>>>
test 4.1 complex test with tuple (other dict order)
>>> a, b = creator()
>>> q = {'b' : (b, 10), 'a' : (a, 10)}
>>> eagerlize(q)
{'a': ({'k2': 2, 'k1': 1, 'k0': 0}, 10), 'b': ([[0], [0], [0]], 10)}
>>>
"""
def loop(obj):
if isiterable(obj):
for k, v in obj.iteritems() if isinstance(obj, dict) \
else enumerate(obj):
if isinstance(v, tuple):
# immutable and iterable object must be recreate,
# but realy only tuple?
obj[k] = tuple(eagerlize(list(obj[k])))
elif issubscriptable(v):
loop(v)
elif isiterable(v):
obj[k] = list(v)
loop(obj[k])
b = [obj]
loop(b)
return b[0]
def _test():
import doctest
doctest.testmod()
if __name__=="__main__":
_test()
最佳答案
为了避免严重影响原始对象,您基本上需要对copy.deepcopy
...进行一些微调,因为您需要将生成器和迭代器转换为列表(deepcopy不会对生成器进行深拷贝)。请注意,不幸的是,对原始对象的某些影响是不可避免的,因为生成器和迭代器是“穷尽”的,这是对它们进行一路迭代的副作用(无论是将其转换为列表还是出于其他目的)–因此,简直是无法同时保留原始对象并使生成器或其他迭代器变成“variant-deepcopied”结果中的列表的方式。
不幸的是,copy
模块没有被编写为自定义的,因此替代方案是,复制粘贴编辑,或者在私有(private)模块变量_deepcopy_dispatch
上(双叹)微妙(叹息)的猴子补丁铰链(这意味着您的补丁版本可能无法从Python版本升级(假设从2.6升级到2.7)生存下来)。另外,每次使用eagerize
后都必须卸载Monkey-patch(以避免影响deepcopy
的其他用途)。因此,假设我们选择了复制粘贴编辑路径。
假设我们从最新版本开始,即在线here。当然,您需要重命名模块;在第145行将外部可见函数deepcopy
重命名为eagerize
;实质性的更改是在第161-165行,在上述版本中,其注释为:
161 : copier = _deepcopy_dispatch.get(cls)
162 : if copier:
163 : y = copier(x, memo)
164 : else:
165 : tim_one 18729 try:
我们需要在第163和164行之间插入逻辑“否则,如果可迭代,则将其扩展到列表(即,使用
_deepcopy_list
函数作为复印机”)。因此,这些行变为:161 : copier = _deepcopy_dispatch.get(cls)
162 : if copier:
163 : y = copier(x, memo)
elif hasattr(cls, '__iter__'):
y = _deepcopy_list(x, memo)
164 : else:
165 : tim_one 18729 try:
仅此而已:仅添加了两行。请注意,我将原始行号留给了自己,以使其完全清楚地将这两行插入到正确的位置,而没有对这两行进行编号。您还需要将标识符
deepcopy
(间接递归调用)的其他实例重命名为eagerize
。您还应该删除第66-144行(您不需要关心的浅拷贝功能),并适本地调整第1-65行(文档字符串,导入,
__all__
等)。当然,您想要的是纯文本版本
copy.py
,here的副本,而不是我一直在使用的带注释的版本(我使用带注释的版本只是为了明确需要更改的地方!-)。