2015-11-20 128 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'的討論是我發現最有用的。 –

相關問題