我可以从实例方法中获得

时间:2015-11-20 03:20:18

标签: python itertools

在类的实例方法中使用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.teeitertools.izip

data flow

在代码中它会是这样的(抱歉,它很长)

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类(我的真实应用是涉及更多保存状态的图像处理,而不是简单的Nth /加/减)。

我不明白我的Nth实例在长度相同时最终会处于不同的状态。例如,如果我给izip两个长度相等的字符串

>>> [''.join(x) for x in izip('ABCD','abcd')]
['Aa', 'Bb', 'Cc', 'Dd']

我得到相同长度的结果;为什么我的Nth.itervalues生成器似乎获得了不等数量的next()个调用,即使每个调用生成相同数量的结果?

2 个答案:

答案 0 :(得分:2)

Gist repo with revisions | Quick link to solution

快速回答

您永远不会重置self.i中的self.noutclass Nth。此外,你应该使用这样的东西:

# 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_sumgen_diff,您会发现gen_diff始终是nout更大的izip()。这是因为gen_sum在从gen_diff拉出之前从gen_sum拉出。 gen_diff在最后一次迭代中尝试self.i之前会引发StopIteration异常。

例如,假设您选择N个样本,其中N%step == 7.在每次迭代结束时,第N个实例的self.i应该等于0.但是在最后一次迭代中,{{1} gen_sum中的{}将增加到7,然后x中将不再有任何元素。它会引发StopIteration。 gen_diff仍然位于self.i等于0。

如果您将self.i = 0self.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的评论:

  

这不会做同样的事情。不重置i和nout是   故意的。我基本上得到了一个连续的数据流X   分成几个文件。切块给出了不同的结果   结果比切换连接流(我之前评论过的   可能使用itertools.chain)。我的实际节目也更多   复杂而不仅仅是切片;它只是一个有效的例子。我没有   了解有关StopIteration顺序的解释。如果   izip(&#39; ABCD&#39;,&#39; abcd&#39;) - &gt; Aa Bb Cc Dd然后看起来像是等长的   发电机应该获得相同数量的下一次通话,不是吗? - 布莱恩   Hawkins 6小时前

你的问题太长了,我错过了关于来自多个文件的流的部分。我们来看看代码本身。首先,我们需要非常清楚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,然后产生OR,它会增加self.i个对象的数量保留在x然后退出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=10len(X)=13。在最后一次next()izip()的电话中, A和B都有self.i==0作为状态。调用A.next(),增量self.i += 3,用完X中的元素,退出for循环,返回,然后Python引发StopIteration。现在,在izip()内,我们直接跳转到完全跳过B.next()的异常块。最后,A.i==3B.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

答案 1 :(得分:1)

结束讨论,在实例方法本身中使用yield并没有错。如果实例状态在最后izip之后发生更改,则yield会遇到问题,因为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会起作用,因为它会试图耗尽每个迭代器。一个更清晰的解决方案可能是重构以避免在最后一次收益后改变状态。