2015-11-20 112 views
4

产量也没关系使用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类保持状态(我真正的应用程序是一个涉及多个保存的状态,而不是简单的第N /加/减图像处理)。

我不明白我的Nth情况下如何结束在不同的状态时,它们的长度是相同的。例如,如果我给相等长度

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

我得到同样长度的结果的izip两个字符串;为什么我的Nth.itervalues发电机似乎得到数量不等的next()调用,即使每一个产生相同数量的结果?

+5

要回答标题问题:是的,从实例方法产生'yield'ing很好。它实际上是实现'__iter__'自定义'Iterable'类型的最简单的Pythonic方式。 – ShadowRanger

+0

难道你不能用'def Nth(x,n):return enumerate(x [:: n])'替换'class Nth'吗?哦,还是你需要将'x'切片成为一个迭代器,出于性能原因? – Harvey

+0

'def Nth(x,n):return enumerate(xi for i,xi in enumerate(x)if i%n == 0)' – Harvey

回答

1

冷凝的讨论中,有一个在一个实例方法无可厚非使用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会起作用,因为它会尝试耗尽每个迭代器。更清晰的解决方案可能是重构以避免在最后一次收益后更改状态。

2

Gist repo with revisions | Quick link to solution

快速回答

你从来没有在class Nth复位self.iself.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_sumgen_diff,您会看到gen_diff将始终为nout之一。这是因为izip()gen_sum下拉,然后从gen_diff拉。 gen_sum会在上次迭代中尝试gen_diff之前引发StopIteration异常。

例如,假设你挑N个样本,其中N%步== 7.在每次迭代中,self.i第N个实例到底应该等于0,但在最后的迭代,self.igen_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 

更说明问题

的回应布赖恩的评论:

这不会做同样的事情。不重置我和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=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

这不会做同样的事情。不重置'i'和'nout'是故意的。我已经基本上获得了连续的数据流X,它跨越了几个文件。对块进行切片会产生与切分连接流不同的结果(我之前评论过可能使用'itertools.chain')。另外我的实际程序比单纯切片更复杂;这只是一个工作的例子。我不明白关于“StopIteration”顺序的解释。如果'izip('ABCD','abcd')' - > Aa Bb Cc Dd那么似乎等长的发电机应该得到相同数量的'next'电话,不是吗? –

+1

感谢您的支持。重构Nth/add/sub对我来说没有用,因为我真正的应用程序中的处理更复杂,而且我已经提到'itertools.chain'输入数据。关于'izip'的讨论是我发现最有用的。 –

相关问题