2012-08-24 86 views
4

我正在使用Python來分析大文件。我想要做的是Python生成器表達式if-else

If condition =True 
    append to list A 
else 
    append to list B 

我想爲此使用生成器表達式 - 以節省內存。我正在輸入實際的代碼。

def is_low_qual(read): 
    lowqual_bp=(bq for bq in phred_quals(read) if bq < qual_threshold) 
    if iter_length(lowqual_bp) > num_allowed: 
     return True 
    else: 
     return False 

lowqual=(read for read in SeqIO.parse(r_file,"fastq") if is_low_qual(read)==True) 
highqual=(read for read in SeqIO.parse(r_file,"fastq") if is_low_qual(read)==False) 


SeqIO.write(highqual,flt_out_handle,"fastq") 
SeqIO.write(lowqual,junk_out_handle,"fastq") 

def iter_length(the_gen): 
    return sum(1 for i in the_gen) 
+2

作爲一個方面說明,不要與真/假比較。使用'if is_condition_true(r)'和'if is not_condition_true(r)'。 – delnan

+1

delnan是對的,其他的東西都可以。 – MostafaR

+0

這看起來很好。這是否失敗?這就是你問的原因嗎? – inspectorG4dget

回答

6

您可以結合使用itertools.teeitertools.ifilteritertools.ifilterfalse

import itertools 
def is_condition_true(x): 
    ... 

gen1, gen2 = itertools.tee(sequences) 
low = itertools.ifilter(is_condition_true, gen1) 
high = itertools.ifilterfalse(is_condition_true, gen2) 

使用tee確保功能正常工作,即使序列本身就是一臺發電機。

注意,雖然,tee可以單獨使用的內存公平位(最多尺寸len(sequences)的列表),如果lowhigh以不同的速率消耗(例如,如果lowhigh之前耗盡時)。

+0

哦,我必須避免高內存的情況,所以不能使用它。序列是一個迭代器,而不是一個生成器。 sequences = SeqIO.parse(read_file,「fastq」)它應該還是破? – Nupur

+0

什麼樣的迭代器?迭代器是任何可以在Python中迭代的通用術語。 – nneonneo

+0

它來自Biopython包。「.. Bio.SeqIO.parse()接受一個文件句柄和格式名稱,並返回一個SeqRecord迭代器」 – Nupur

0

我認爲你正努力避免迭代你的收藏兩次。如果是這樣,這種類型的方法的工作原理:

high, low = [], [] 
_Nones = [high.append(x) if is_condition_true() else low.append(x) for x in sequences] 

這可能比建議少,因爲它使用列表理解的副作用。這通常是反pythonic。

+1

那麼,這也會創建一個[None] * len(sequences)列表,這是不受歡迎的,因爲它使用更多的內存作爲他的原始建議。 – nneonneo

+1

而不是使用列表理解,你可以使用'any(...)'和等價的生成器表達式。由於每個項目都是'None',因此是false,所以'any()'保證消耗整個迭代器。 (你也可以使用'collections.deque(maxlen = 0)'來使用迭代器;它可能會更快,因爲它沒有真實性測試。) – kindall

+0

我發現它通常更簡單(也更易讀)使用當涉及副作用時(尤其是追加),可以進行for-loop。所以在這種情況下,我肯定會把它寫成一個循環。 – nneonneo

0

這很難做到優雅。這裏的一些作品:

from itertools import tee, ifilter, ifilterfalse 
low, high = [f(condition, g) for f, g in zip((ifilter, ifilterfalse), tee(seq))] 

注意,當你使用來自一個導致迭代器項目(比如low),在發球內部雙端隊列將擴大到包含您尚未從high消耗任何物品(包括,不幸的是,那些ifilterfalse將拒絕)。因此,這可能不會像您希望的那樣節省更多的記憶。

下面是一個使用盡可能少的額外內存儘可能的實現:

def filtertee(func, iterable, codomain=(False, True)): 
    it = iter(iterable) 
    deques = dict((r, deque()) for r in codomain) 
    def gen(mydeque): 
     while True: 
      while not mydeque:   # as long as the local deque is empty 
       newval = next(it)  # fetch a new value, 
       result = func(newval) # find its image under `func`, 
       try: 
        d = deques[result] # find the appropriate deque, and 
       except KeyError: 
        raise ValueError("func returned value outside codomain") 
       d.append(newval)  # add it. 
      yield mydeque.popleft() 
    return dict((r, gen(d)) for r, d in deques.items()) 

這從函數的值域返回dict到發電機提供採取func下,該值項:

gen = filtertee(condition, seq) 
low, high = gen[True], gen[False] 

請注意,您有責任確保condition僅返回codomain中的值。

+0

他不希望生成列表來節省內存。 – nneonneo

+0

@nneonneo謝謝,這簡化了它! – ecatmur

1

只是添加一個更一般的答案:如果你主要關心的是內存,你應該使用一個生成器循環遍歷整個文件,並將每個項目處理爲低或高。例如:

for r in sequences: 
    if condition_true(r): 
     handle_low(r) 
    else: 
     handle_high(r) 

如果您需要在使用前收集所有高/低元素,則無法防範潛在的內存命中。原因在於直到閱讀它們時才能知道哪些元素是高/低。如果您必須先處理低位,並且事實證明所有元素都很高,那麼除了將它們存儲在列表中時,它們將會使用內存,因此您別無選擇。用一個循環做它可以讓你一次處理一個元素,但是你必須平衡這個與其他問題的關係(例如,這樣做很麻煩,這取決於你想要做什麼與數據)。

+0

對不起,我不明白。以上是發電機嗎?我想爲每個數組使用一個生成器,因爲我認爲這不會將其存儲在內存中,對,它只是一個表達式?我需要處理的數據是使用Bio.SeqIO.write打印出來 - 如果每次循環都不會調用它,效率會更高。或者,我可以使用簡單的打印語句在每一步打印。歸結到這一點 - 創造一臺發電機真的很貴?在這種情況下創建2更多? – Nupur

+0

@Nupur:上面只是一個循環。創建生成器的唯一原因是在循環中使用它。看來你目前有一個發生器(稱爲「序列」)。我說的是,如果你真的想要節省內存,那麼不要試圖從中創建兩個生成器,而是直接在原始生成器上循環。如果你所做的只是寫出來,那麼我的解決方案應該可以正常工作。如果您需要更多細節,則必須編輯您的問題,以提供有關使用數據的代碼的更多詳細信息。 – BrenBarn