2012-01-06 31 views
7

我想在列表理解中構造一個值,但也要對該值進行過濾。例如:可能從Python列表中理解返回的值以使用條件?

[expensive_function(x) for x in generator where expensive_function(x) < 5] 

我想避免調用expensive_function每次迭代兩次。

generator可能會返回一個無限序列,列表理解不會被延遲評估。所以這是行不通的:

[y in [expensive_function(x) for x in generator where expensive_function(x)] where y < 5] 

我可以寫這個法子,但感覺正確的列表理解,我敢肯定,這是一種常見的使用模式(可能還是不行!)。

+0

任何答案值得接受嗎?如果不是,你還在尋找什麼信息? – 2012-03-09 23:23:36

+0

很抱歉標記是這個人。謝謝您的回答! – Joe 2012-03-11 02:28:32

+0

沒問題。希望你只需要提醒一下。 :) – 2012-03-11 05:02:32

回答

10

如果generator可能是無限的,您不想使用列表理解。並非所有事情都必須是一攬子的。

def filtered_gen(gen): 
    for item in gen: 
     result = expensive_function(item) 
     if result < 5: 
      yield result 
+2

'item'的最後兩次出現應該由'result'替換嗎? – Chris 2012-01-06 17:10:22

+0

@克里斯:是的,謝謝。 – 2012-01-06 18:22:25

+0

+1。您可以使用itertools和生成器表達式,但這更容易理解。 – 2012-01-06 20:14:51

2

你應該讓2號發電機表達式:

ys_all = (expensive(x) for x in xs) 
ys_filtered = (y for y in ys_all if y <5) 

from itertools import imap, ifilter 
ys = ifilter(lambda y : y < 5, imap(expensive, xs)) 
+0

沒有。不是如果'xs'是無限的。可悲的是,Python沒有Haskell列表解析功能。某處某處會爆炸。 – Joe 2012-01-06 17:17:18

+0

迭代器更新答案 – Simon 2012-01-06 17:59:04

+0

是的!在我看來這是最好的答案!你可能會稱他們爲「發電機綜合」而不是列表解析? – 2016-06-26 13:40:42

1

警告這是一個有點令人費解,但做這項工作。我將用一個例子來解釋它。

讓說expensive_function = math.sin

infinite generator = collections.count(0.1,0.1)

然後

[z for z in (y if y < 5 else next(iter([])) 
    for y in (math.sin(x) for x in itertools.count(0.1,0.1)))] 

[0.09983341664682815, 
0.19866933079506122, 
0.2955202066613396, 
0.3894183423086505, 
0.479425538604203] 

所以你的問題歸結爲

[z for z in (y if y < 0.5 else next(iter([])) \ 
     for y in (expensive_function(x) for x in generator))] 

訣竅是迫使來自發電機的並沒有什麼飄逸StopIterationnext(iter([]))

這裏expensive_function只調用每次迭代一次。

使用有限生成器和停止條件擴展無限生成器。 由於發電機不允許raise StopIteration,我們選擇一種複雜的方式,即next(iter([])) 現在你有一個有限生成器,它可以在列表解析

可以作爲OP與上述方法的應用在這裏非monotonic功能而言是一個虛構的非單調函數

昂貴的非單調函數f(x) = random.randint(1,100)*x

停止條件= < 7

[z for z in (y if y < 7 else next(iter([])) for y in 
     (random.randint(1,10)*x for x in itertools.count(0.1,0.1)))] 

[0.9, 
0.6000000000000001, 
1.8000000000000003, 
4.0, 
0.5, 
6.0, 
4.8999999999999995, 
3.1999999999999997, 
3.5999999999999996, 
5.999999999999999] 

BTW:真正意義上的在整個範圍內是非單調的(0,2pi)

+0

瘋了!這是微妙的不同,因爲'sin'是一個非單調函數(我沒有說'expensive_function'是單調的,但是它!)並且這首次停止**條件不成立,而不是在條件成立的情況下繼續**。也就是說,如果它是非單調的,這將導致無限的評估... – Joe 2012-01-06 17:18:36

+0

@Joe,即使對於非單調函數,這也可以工作。查看我的更新 – Abhijit 2012-01-06 17:30:12

2

我要回答關於如何在條件中使用的列表理解中捕獲中間結果的問題的部分,並忽略了一個問題由無限生成器構建的列表理解(顯然不會起作用),以防在標題中尋找問題答案的人來到這裏。

所以,你有一個列表的理解是這樣的:

[expensive_function(x) for x in xrange(5) if expensive_function(x) % 2 == 0] 

而且要避免計算expensive_function兩次當它通過過濾器。與更富有表現力的理解語法的語言(斯卡拉,哈斯克爾等),讓您只需指定名稱從理解變量表達式計算,它可以讓你不喜歡的東西如下:

# NOT REAL PYTHON 
[result for x in xrange(5) for result = expensive_function(x) if result % 2 == 0] 

但是你可以很容易地通過旋轉效仿這一分配result = expensive_function(x)到另一個for迭代超過一個元素的序列:

[result for x in xrange(5) for result in (expensive_function(x),) if result % 2 == 0] 

和論證:

>>> def expensive_function(x): 
     print 'expensive_function({})'.format(x) 
     return x + 10 
>>> [expensive_function(x) for x in xrange(5) if expensive_function(x) % 2 == 0] 
expensive_function(0) 
expensive_function(0) 
expensive_function(1) 
expensive_function(2) 
expensive_function(2) 
expensive_function(3) 
expensive_function(4) 
expensive_function(4) 
[10, 12, 14] 
>>> [result for x in xrange(5) for result in (expensive_function(x),) if result % 2 == 0] 
expensive_function(0) 
expensive_function(1) 
expensive_function(2) 
expensive_function(3) 
expensive_function(4) 
[10, 12, 14]