我可以从实例方法中产生吗
Can I yield from an instance method
在 class 的实例方法中使用 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
,但我想了解在 [=21= 中维护状态有什么问题] class 调用之间(我的真实应用是涉及更多保存状态的图像处理,不简单 Nth/add/subtract)。
我不明白我的 Nth
实例在长度相同时如何以不同的状态结束。例如,如果我给 izip
两个等长的字符串
>>> [''.join(x) for x in izip('ABCD','abcd')]
['Aa', 'Bb', 'Cc', 'Dd']
我得到了相同长度的结果;为什么我的 Nth.itervalues
生成器似乎得到了不等数量的 next()
调用,即使每个生成器产生相同数量的结果?
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))
长答案
你的代码有一股异味,导致我在 NthSumDiff.itervalues():
中看到这一行
for (i,nthsum), (j,nthdiff) in izip(gen_sum, gen_diff):
如果交换 gen_sum
和 gen_diff
,您会发现 gen_diff
始终是 nout
大一的那个。这是因为 izip()
在从 gen_diff
拉取之前从 gen_sum
拉取。 gen_sum
在 gen_diff
甚至在最后一次迭代中尝试之前引发 StopIteration 异常。
例如,假设您选择了 N 个样本,其中 N % step == 7。在每次迭代结束时,第 N 个实例的 self.i
应等于 0。但是在最后一次迭代中,[= gen_sum
中的 18=] 将增加到 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
问题的更多解释
回复 Brian 的评论:
This doesn't do the same thing. Not resetting i and nout is
intentional. I've basically got a continuous data stream X that's
split across several files. Slicing the blocks gives a different
result than slicing the concatenated stream (I commented earlier about
possibly using itertools.chain). Also my actual program is more
complicated than mere slicing; it's just a working example. I don't
understand the explanation about the order of StopIteration. If
izip('ABCD','abcd') --> Aa Bb Cc Dd then it seems like equal-length
generators should get an equal number of next calls, no? – Brian
Hawkins 6 hours ago
你的问题太长了,我错过了关于来自多个文件的流的部分。让我们看看代码本身。首先,我们需要真正清楚 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
然后产生或它递增 self.i
数字剩余在 x
中的对象,然后退出 for 循环,然后退出生成器(itervalues() 是一个生成器,因为它产生)。当 itervalues() 生成器退出时,Python 引发 StopIteration 异常。
因此,对于每个用 N 初始化的 class Nth
实例,在用尽 itervalues(X)
中的所有元素后 self.i
的值将是:
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 循环,returns,然后 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
浓缩讨论,在实例方法 本身 中使用 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
class 可以在其最后一个 yield
之后消耗输入数据,因此和流和差流可能与 [=13= 不同步].使用 izip_longest
会起作用,因为它会尝试耗尽每个迭代器。一个更清晰的解决方案可能是重构以避免在最后一次 yield 之后改变状态。
在 class 的实例方法中使用 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
,但我想了解在 [=21= 中维护状态有什么问题] class 调用之间(我的真实应用是涉及更多保存状态的图像处理,不简单 Nth/add/subtract)。
我不明白我的 Nth
实例在长度相同时如何以不同的状态结束。例如,如果我给 izip
两个等长的字符串
>>> [''.join(x) for x in izip('ABCD','abcd')]
['Aa', 'Bb', 'Cc', 'Dd']
我得到了相同长度的结果;为什么我的 Nth.itervalues
生成器似乎得到了不等数量的 next()
调用,即使每个生成器产生相同数量的结果?
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))
长答案
你的代码有一股异味,导致我在 NthSumDiff.itervalues():
中看到这一行for (i,nthsum), (j,nthdiff) in izip(gen_sum, gen_diff):
如果交换 gen_sum
和 gen_diff
,您会发现 gen_diff
始终是 nout
大一的那个。这是因为 izip()
在从 gen_diff
拉取之前从 gen_sum
拉取。 gen_sum
在 gen_diff
甚至在最后一次迭代中尝试之前引发 StopIteration 异常。
例如,假设您选择了 N 个样本,其中 N % step == 7。在每次迭代结束时,第 N 个实例的 self.i
应等于 0。但是在最后一次迭代中,[= gen_sum
中的 18=] 将增加到 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
问题的更多解释
回复 Brian 的评论:
This doesn't do the same thing. Not resetting i and nout is intentional. I've basically got a continuous data stream X that's split across several files. Slicing the blocks gives a different result than slicing the concatenated stream (I commented earlier about possibly using itertools.chain). Also my actual program is more complicated than mere slicing; it's just a working example. I don't understand the explanation about the order of StopIteration. If izip('ABCD','abcd') --> Aa Bb Cc Dd then it seems like equal-length generators should get an equal number of next calls, no? – Brian Hawkins 6 hours ago
你的问题太长了,我错过了关于来自多个文件的流的部分。让我们看看代码本身。首先,我们需要真正清楚 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
然后产生或它递增 self.i
数字剩余在 x
中的对象,然后退出 for 循环,然后退出生成器(itervalues() 是一个生成器,因为它产生)。当 itervalues() 生成器退出时,Python 引发 StopIteration 异常。
因此,对于每个用 N 初始化的 class Nth
实例,在用尽 itervalues(X)
中的所有元素后 self.i
的值将是:
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 循环,returns,然后 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
浓缩讨论,在实例方法 本身 中使用 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
class 可以在其最后一个 yield
之后消耗输入数据,因此和流和差流可能与 [=13= 不同步].使用 izip_longest
会起作用,因为它会尝试耗尽每个迭代器。一个更清晰的解决方案可能是重构以避免在最后一次 yield 之后改变状态。