我可以从一个实例方法
产量也没关系使用yield语句的类的实例方法?例如,我可以从一个实例方法
# Similar to itertools.islice
class Nth(object):
def __init__(self, n):
self.n = n
self.i = 0
self.nout = 0
def itervalues(self, x):
for xi in x:
self.i += 1
if self.i == self.n:
self.i = 0
self.nout += 1
yield self.nout, xi
Python不会抱怨,简单的情况下似乎工作。但是,我只看到了使用常规函数的例子。
我开始有问题,当我尝试使用itertools函数使用它。例如,假设我有两个存储在多个文件中的大数据流X和Y,并且我想通过数据只计算一个循环的总和和差。我可以用itertools.tee
和itertools.izip
像下图中
在代码中它会是这样的(不好意思,这是长)
from itertools import izip_longest, izip, tee
import random
def add(x,y):
for xi,yi in izip(x,y):
yield xi + yi
def sub(x,y):
for xi,yi in izip(x,y):
yield xi - yi
class NthSumDiff(object):
def __init__(self, n):
self.nthsum = Nth(n)
self.nthdiff = Nth(n)
def itervalues(self, x, y):
xadd, xsub = tee(x)
yadd, ysub = tee(y)
gen_sum = self.nthsum.itervalues(add(xadd, yadd))
gen_diff = self.nthdiff.itervalues(sub(xsub, ysub))
# Have to use izip_longest here, but why?
#for (i,nthsum), (j,nthdiff) in izip_longest(gen_sum, gen_diff):
for (i,nthsum), (j,nthdiff) in izip(gen_sum, gen_diff):
assert i==j, "sum row %d != diff row %d" % (i,j)
yield nthsum, nthdiff
nskip = 12
ns = Nth(nskip)
nd = Nth(nskip)
nsd = NthSumDiff(nskip)
nfiles = 10
for i in range(nfiles):
# Generate some data.
# If the block length is a multiple of nskip there's no problem.
#n = random.randint(5000, 10000) * nskip
n = random.randint(50000, 100000)
print 'file %d n=%d' % (i, n)
x = range(n)
y = range(100,n+100)
# Independent processing is no problem but requires two loops.
for i, nthsum in ns.itervalues(add(x,y)):
pass
for j, nthdiff in nd.itervalues(sub(x,y)):
pass
assert i==j
# Trying to do both with one loops causes problems.
for nthsum, nthdiff in nsd.itervalues(x,y):
# If izip_longest is necessary, why don't I ever get a fillvalue?
assert nthsum is not None
assert nthdiff is not None
# After each block of data the two iterators should have the same state.
assert nsd.nthsum.nout == nsd.nthdiff.nout, \
"sum nout %d != diff nout %d" % (nsd.nthsum.nout, nsd.nthdiff.nout)
但这种失败,除非我换itertools.izip
出来即使迭代器具有相同的长度,也可以使用itertools.izip_longest
。这是最后assert
那被击中,具有输出像
file 0 n=58581
file 1 n=87978
Traceback (most recent call last):
File "test.py", line 71, in <module>
"sum nout %d != diff nout %d" % (nsd.nthsum.nout, nsd.nthdiff.nout)
AssertionError: sum nout 12213 != diff nout 12212
编辑:我想这是不是从我写的例子明显的,但输入数据X和Y仅在块可用的(在我的真正的问题他们在文件中分块)。这很重要,因为我需要维护块之间的状态。在上面的玩具例如,这意味着Nth
需要产生的
>>> x1 = range(0,10)
>>> x2 = range(10,20)
>>> (x1 + x2)[::3]
[0, 3, 6, 9, 12, 15, 18]
不是
>>> x1[::3] + x2[::3]
[0, 3, 6, 9, 10, 13, 16, 19]
相当于我可以用itertools.chain
提前加入的时间块,然后将相当于打一个电话,给Nth.itervalues
,但我想了解什么是错的,在调用之间的Nth
类保持状态(我真正的应用程序是一个涉及多个保存的状态,而不是简单的第N /加/减图像处理)。
我不明白我的Nth
情况下如何结束在不同的状态时,它们的长度是相同的。例如,如果我给相等长度
>>> [''.join(x) for x in izip('ABCD','abcd')]
['Aa', 'Bb', 'Cc', 'Dd']
我得到同样长度的结果的izip
两个字符串;为什么我的Nth.itervalues
发电机似乎得到数量不等的next()
调用,即使每一个产生相同数量的结果?
冷凝的讨论中,有一个在一个实例方法无可厚非使用yield
本身。如果实例状态在上一次yield
后更改,则izip
会因为izip
停止在其参数上调用next()
,一旦它们中的任何一个停止产生结果而陷入困境。更明确的例子可能是
from itertools import izip
class Three(object):
def __init__(self):
self.status = 'init'
def run(self):
self.status = 'running'
yield 1
yield 2
yield 3
self.status = 'done'
raise StopIteration()
it = Three()
for x in it.run():
assert it.status == 'running'
assert it.status == 'done'
it1, it2 = Three(), Three()
for x, y in izip(it1.run(), it2.run()):
pass
assert it1.status == 'done'
assert it2.status == 'done', "Expected status=done, got status=%s." % it2.status
其打最后断言,
AssertionError: Expected status=done, got status=running.
在原来的问题,Nth
类的最后yield
后可消耗输入数据,所以和差流可以得到与izip
不同步。使用izip_longest
会起作用,因为它会尝试耗尽每个迭代器。更清晰的解决方案可能是重构以避免在最后一次收益后更改状态。
Gist repo with revisions | Quick link to solution
快速回答
你从来没有在class Nth
复位self.i
和self.nout
。此外,你应该使用这样的事情:
# Similar to itertools.islice
class Nth(object):
def __init__(self, n):
self.n = n
def itervalues(self, x):
for a,b in enumerate(islice(x, self.n - 1, None, self.n)):
self.nout = a
yield a,b
但因为你甚至不需要nout
,你应该这样做:
def Nth(iterable, step):
return enumerate(itertools.islice(iterable, step - 1, None, step))
龙答案
您的代码有一个关闭():
for (i,nthsum), (j,nthdiff) in izip(gen_sum, gen_diff):
如果y ou交换gen_sum
和gen_diff
,您会看到gen_diff
将始终为nout
之一。这是因为izip()
从gen_sum
下拉,然后从gen_diff
拉。 gen_sum
会在上次迭代中尝试gen_diff
之前引发StopIteration异常。
例如,假设你挑N个样本,其中N%步== 7.在每次迭代中,self.i
第N个实例到底应该等于0,但在最后的迭代,self.i
在gen_sum
将增加最多7,然后在x
中将不会再有元素。它会引发StopIteration。尽管如此,gen_diff
仍然位于self.i
等于0。
如果将self.i = 0
和self.nout = 0
添加到Nth.itervalues()的开头,问题就会消失。
第
您只有这个问题,因为你的代码太复杂,而不是Pythonic。如果你发现自己在循环中使用了很多计数器和索引,那么这是一个很好的迹象(用Python)让我们退一步看看你是否可以简化代码。我有很长的C编程历史,因此,我仍然不时在Python中做同样的事情。
简单的实现
把我的钱在我的嘴...
from itertools import izip, islice
import random
def sumdiff(x,y,step):
# filter for the Nth values of x and y now
x = islice(x, step-1, None, step)
y = islice(y, step-1, None, step)
return ((xi + yi, xi - yi) for xi, yi in izip(x,y))
nskip = 12
nfiles = 10
for i in range(nfiles):
# Generate some data.
n = random.randint(50000, 100000)
print 'file %d n=%d' % (i, n)
x = range(n)
y = range(100,n+100)
for nthsum, nthdiff in sumdiff(x,y,nskip):
assert nthsum is not None
assert nthdiff is not None
assert len(list(sumdiff(x,y,nskip))) == n/nskip
更说明问题
的回应布赖恩的评论:
这不会做同样的事情。不重置我和nout是 故意。我基本上有一个连续的数据流X,它是分割成几个文件的 。切片块给出了与切分连接流不同的 结果(我之前对可能使用itertools.chain的 进行了评论)。另外我的实际程序比单纯切片更复杂;这只是一个工作的例子。我不需要 了解有关StopIteration顺序的说明。如果 izip('ABCD','abcd') - > Aa Bb Cc Dd那么它看起来像等长 生成器应该获得相同数量的下一个调用,不是? - 布赖恩 霍金斯5小时前
你的问题是这么久,我错过了大约从多个文件来流的一部分。让我们看看代码本身。首先,我们需要清楚地了解itervalues(x)
的实际工作情况。
# Similar to itertools.islice
class Nth(object):
def __init__(self, n):
self.n = n
self.i = 0
self.nout = 0
def itervalues(self, x):
for xi in x:
# We increment self.i by self.n on every next()
# call to this generator method unless the
# number of objects remaining in x is less than
# self.n. In that case, we increment by that amount
# before the for loop exits normally.
self.i += 1
if self.i == self.n:
self.i = 0
self.nout += 1
# We're yielding, so we're a generator
yield self.nout, xi
# Python helpfully raises StopIteration to fulfill the
# contract of an iterable. That's how for loops and
# others know when to stop.
在上述itervalues(x)
,对于每next()
呼叫,它在内部递增self.i
通过self.n
然后收率的或它由留在x
对象的数量递增self.i
,然后退出for循环,然后退出所述发电机(itervalues ()是一个生成器,因为它产生)。当itervalues()生成器退出时,Python引发StopIteration异常。
因此,对于具有N个初始化class Nth
每个实例的self.i
排干itervalues(X)
所有元素后的值将是:
self.i = value_of_self_i_before_itervalues(X) + len(X) % N
现在,当你遍历izip(Nth_1, Nth_2)
,它会做这样的事情:
def izip(A, B):
try:
while True:
a = A.next()
b = B.next()
yield a,b
except StopIteration:
pass
所以,想象一下N=10
和len(X)=13
。在最后的next()
呼叫izip()
, A和B都有self.i==0
作为他们的状态。 A.next()
被调用,增量self.i += 3
,用完X中的元素,退出for循环,返回,然后Python提高StopIteration
。现在,在izip()
之内,我们直接跳到完全跳过B.next()
的异常块。所以,A.i==3
和B.i==0
在最后。
第二次尝试简化(用正确的要求)
这里还有一个简化版本,将所有文件数据作为一个连续的数据流。它使用链式,小型,可重复使用的发电机。我会很高兴,强烈推荐看这PyCon '14 talk about generators by David Beazley。从你的问题描述中猜测,它应该是100%适用的。
from itertools import izip, islice
import random
def sumdiff(data):
return ((x + y, x - y) for x, y in data)
def combined_file_data(files):
for i,n in files:
# Generate some data.
x = range(n)
y = range(100,n+100)
for data in izip(x,y):
yield data
def filelist(nfiles):
for i in range(nfiles):
# Generate some data.
n = random.randint(50000, 100000)
print 'file %d n=%d' % (i, n)
yield i, n
def Nth(iterable, step):
return islice(iterable, step-1, None, step)
nskip = 12
nfiles = 10
filedata = combined_file_data(filelist(nfiles))
nth_data = Nth(filedata, nskip)
for nthsum, nthdiff in sumdiff(nth_data):
assert nthsum is not None
assert nthdiff is not None
这不会做同样的事情。不重置'i'和'nout'是故意的。我已经基本上获得了连续的数据流X,它跨越了几个文件。对块进行切片会产生与切分连接流不同的结果(我之前评论过可能使用'itertools.chain')。另外我的实际程序比单纯切片更复杂;这只是一个工作的例子。我不明白关于“StopIteration”顺序的解释。如果'izip('ABCD','abcd')' - > Aa Bb Cc Dd那么似乎等长的发电机应该得到相同数量的'next'电话,不是吗? –
感谢您的支持。重构Nth/add/sub对我来说没有用,因为我真正的应用程序中的处理更复杂,而且我已经提到'itertools.chain'输入数据。关于'izip'的讨论是我发现最有用的。 –
要回答标题问题:是的,从实例方法产生'yield'ing很好。它实际上是实现'__iter__'自定义'Iterable'类型的最简单的Pythonic方式。 – ShadowRanger
难道你不能用'def Nth(x,n):return enumerate(x [:: n])'替换'class Nth'吗?哦,还是你需要将'x'切片成为一个迭代器,出于性能原因? – Harvey
'def Nth(x,n):return enumerate(xi for i,xi in enumerate(x)if i%n == 0)' – Harvey