在 Python3.x 中了解 izip
Understanding izip in Python3.x
我的问题只是为了学习,而且只针对python3.x。在现实生活中,我会使用 zip,因为 python3 zip 和 python2 izip 做同样的工作(即返回一个生成器,而不是真实的东西)。
在python2中,izip本质上等同于下面的代码(摘自izip,加上一些调试代码)
def izip(*iterables):
iterators = map(iter, iterables)
n = 0
while iterators:
x = tuple(map(next, iterators))
print("at n={}, x={} ".format(n, x))
yield x
n += 1
if n > 10: break
Python2 工作正常。 izip('abc', 'ABC')
的输出是:
at n=0, x=('a', 'A')
('a', 'A')
at n=1, x=('b', 'B')
('b', 'B')
at n=2, x=('c', 'C')
('c', 'C')
Python3 反而进入无限循环。 this thread 中解释了原因。但还有一点我无法理解:python3 只产生第一个元组。这是同一程序的输出。为什么bs'和cs'没有出现?:
at n=0, x=('a', 'A')
('a', 'A')
at n=1, x=()
()
at n=2, x=()
()
at n=3, x=()
() etc.
我的两个问题是为什么 Python3 会这样?以及如何让这段代码工作?
问题在于 python2 和 python3 之间 map
的不同行为,特别是在第一次调用 map
(iterators = map(iter, iterables)
) .
在 python3 中,map
returns 生成器(或类似生成器的对象),而在 python2 中,它是一个列表。这意味着在第一次调用 tuple(map(next, iterators))
之后,iterators
生成器被完全消耗,因此在下一次迭代中,没有更多的迭代器可以使用。
如果您更改它应该会起作用:
iterators = map(iter, iterables)
至:
iterators = list(map(iter, iterables))
(可以说比 iterators = [ iter(it) for it in iterables ]
更好)
正如您所指出的,它现在进入了一个无限循环。同样,问题出在 map
函数中,但这次是在第二次调用中。
首先,让我们了解此实现在 python2 中的工作原理。尽管有一个 while iterators
循环,但循环不会因为条件为假而中断,而是由于对 next
的调用之一引发了 StopIteration 异常。此异常传播到调用方的循环,它正确理解没有更多结果。
像这样实现它可能很直观:
def izip(*iterables):
if not iterables: return []
iterators = map(iter, iterables)
while True:
yield tuple(map(next, iterators))
现在,map
的行为在 python3 中也发生了变化。它没有提高,而是 "trims" 输出:
list(map(next, [ iter('ab'), iter('') ]))
=> ['a']
不引发 StopIteration 会导致无限循环。
解决方案是使用列表理解, 传播 StopIteration 异常。
def izip(*iterables):
if not iterables: return []
iterators = map(iter, iterables)
while True:
yield tuple([ next(it) for it in iterators ])
经验教训:列表理解(和生成器表达式)should be favored优于map
、filter
等
我的问题只是为了学习,而且只针对python3.x。在现实生活中,我会使用 zip,因为 python3 zip 和 python2 izip 做同样的工作(即返回一个生成器,而不是真实的东西)。
在python2中,izip本质上等同于下面的代码(摘自izip,加上一些调试代码)
def izip(*iterables):
iterators = map(iter, iterables)
n = 0
while iterators:
x = tuple(map(next, iterators))
print("at n={}, x={} ".format(n, x))
yield x
n += 1
if n > 10: break
Python2 工作正常。 izip('abc', 'ABC')
的输出是:
at n=0, x=('a', 'A')
('a', 'A')
at n=1, x=('b', 'B')
('b', 'B')
at n=2, x=('c', 'C')
('c', 'C')
Python3 反而进入无限循环。 this thread 中解释了原因。但还有一点我无法理解:python3 只产生第一个元组。这是同一程序的输出。为什么bs'和cs'没有出现?:
at n=0, x=('a', 'A')
('a', 'A')
at n=1, x=()
()
at n=2, x=()
()
at n=3, x=()
() etc.
我的两个问题是为什么 Python3 会这样?以及如何让这段代码工作?
问题在于 python2 和 python3 之间 map
的不同行为,特别是在第一次调用 map
(iterators = map(iter, iterables)
) .
在 python3 中,map
returns 生成器(或类似生成器的对象),而在 python2 中,它是一个列表。这意味着在第一次调用 tuple(map(next, iterators))
之后,iterators
生成器被完全消耗,因此在下一次迭代中,没有更多的迭代器可以使用。
如果您更改它应该会起作用:
iterators = map(iter, iterables)
至:
iterators = list(map(iter, iterables))
(可以说比 iterators = [ iter(it) for it in iterables ]
更好)
正如您所指出的,它现在进入了一个无限循环。同样,问题出在 map
函数中,但这次是在第二次调用中。
首先,让我们了解此实现在 python2 中的工作原理。尽管有一个 while iterators
循环,但循环不会因为条件为假而中断,而是由于对 next
的调用之一引发了 StopIteration 异常。此异常传播到调用方的循环,它正确理解没有更多结果。
像这样实现它可能很直观:
def izip(*iterables):
if not iterables: return []
iterators = map(iter, iterables)
while True:
yield tuple(map(next, iterators))
现在,map
的行为在 python3 中也发生了变化。它没有提高,而是 "trims" 输出:
list(map(next, [ iter('ab'), iter('') ]))
=> ['a']
不引发 StopIteration 会导致无限循环。
解决方案是使用列表理解, 传播 StopIteration 异常。
def izip(*iterables):
if not iterables: return []
iterators = map(iter, iterables)
while True:
yield tuple([ next(it) for it in iterators ])
经验教训:列表理解(和生成器表达式)should be favored优于map
、filter
等