2016-04-05 8 views
13

Bayes 'Netsプログラムの "Variable Elimination"アルゴリズムを実装する過程で、一連のオブジェクトの反復的なマップ変換の結果として予期しないバグが発生しました。map対list;なぜ違う振る舞いですか?

簡単にするため、私はここで、コードの類似した部分を使用します:

>>> nums = [1, 2, 3] 
>>> for x in [4, 5, 6]: 
...  # Uses n if x is odd, uses (n + 10) if x is even 
...  nums = map(
...   lambda n: n if x % 2 else n + 10, 
...   nums) 
... 
>>> list(nums) 
[31, 32, 33] 

これは間違いなく間違った結果です。 [4,5,6]には2つの偶数が含まれているため、各要素には最大で2回、10を追加する必要があります。 VEアルゴリズムでもこれが予期せぬ動作をしていたので、各繰り返しの後にイテレータをlistに変換するように変更しました。

>>> nums = [1, 2, 3] 
>>> for x in [4, 5, 6]: 
...  # Uses n if x is odd, uses (n + 10) if x is even 
...  nums = map(
...   lambda n: n if x % 2 else n + 10, 
...   nums) 
...  nums = list(nums) 
... 
>>> list(nums) 
[21, 22, 23] 

イテレート可能オブジェクトの私の理解から、この変更は何も変わらないはずですが、それはありません。明らかに、not x % 2ケースのn + 10変換は、list -edバージョンでは1回少ない回数だけ適用されます。

My Bayes Netsのプログラムもこのバグを見つけてもうまくいきましたが、なぜそれが発生したのかについての説明を探しています。

+4

このようなコードは書かないでください。それは私の脳を傷つける。 – Kevin

+0

あなたのコードはあなたが説明すべきことをしません。もしそれらが奇数であればその数を保持し、それらが偶数であれば10を足したければ、nums = map(x%2!= 0 else n + 10、numsならlambda n: if関数を評価するための何かが必要です。そうでなければ、常にtrueと評価されます。 BTW。あなたの編集が示唆するように、コードの問題は改行ではありませんでした。私はケビンが左に点と矢印であると不平を言っていると思います。 –

+1

Python3では 'map'がイテレータなのでしょうか? – hpaulj

答えて

12

答えは非常に単純です:maplazyのPython 3の関数で、繰り返し可能なオブジェクトを返します(Python 2ではlistを返します)。私はあなたの例に、いくつかの出力を追加してみましょう:

In [6]: nums = [1, 2, 3] 

In [7]: for x in [4, 5, 6]: 
    ...:  nums = map(lambda n: n if x % 2 else n + 10, nums) 
    ...:  print(x) 
    ...:  print(nums) 
    ...:  
4 
<map object at 0x7ff5e5da6320> 
5 
<map object at 0x7ff5e5da63c8> 
6 
<map object at 0x7ff5e5da6400> 

In [8]: print(x) 
6 

In [9]: list(nums) 
Out[9]: [31, 32, 33] 

In[8]からxの値は、我々はまた、xの値を追跡するためにmapに渡さlambda機能、変換でき6.次のとおりです。

In [10]: nums = [1, 2, 3] 

In [11]: for x in [4, 5, 6]: 
    ....:  nums = map(lambda n: print(x) or (n if x % 2 else n + 10), nums) 
    ....:  

In [12]: list(nums) 
6 
6 
6 
6 
6 
6 
6 
6 
6 
Out[12]: [31, 32, 33] 

mapが遅延しているため、listが呼び出されているときに評価されます。ただし、xの値は6であるため、混乱する出力が生成されます。ループ内のnumsを評価すると、と予想され、の出力が生成されます。

In [13]: nums = [1, 2, 3] 

In [14]: for x in [4, 5, 6]: 
    ....:  nums = map(lambda n: print(x) or (n if x % 2 else n + 10), nums) 
    ....:  nums = list(nums) 
    ....:  
4 
4 
4 
5 
5 
5 
6 
6 
6 

In [15]: nums 
Out[15]: [21, 22, 23] 
+0

ああ!それは 'x'でした!ありがとうございました。イテレータは一般的に「怠け者」であることがわかっていますが、私はその怠惰の一部を見逃していました。ループの各反復中に変数を即座に評価するラムダ式を設定する方法はありますか? (編集:心配しないで、それを持って!)これは、機能的な命令的プログラミングを混在させる危険性があるようです! – cosmicFluke

+2

"x値が6に変更されたときに評価されます。むしろ、mapオブジェクトに対して 'list'が呼び出されたときに評価されます。これは' x = 6'のときに発生します。 – Evert

+0

@Evert、はい、私はそれを修正しています、ありがとうございます – soon

3

あなたが怠惰なバージョンを使用したい場合は、各ループにxを修正する必要があります。 functools.partialはまさにそれを行います。

from functools import partial 

def myfilter(n, x): 
    return n if x % 2 else n + 10 

nums = [1, 2, 3] 
for x in [4, 5, 6]: 
    f = partial(myfilter, x=x) 
    nums = map(f, nums) 

>>> list(nums) 
[21, 22, 23] 
+0

これは私のフォローアップの質問に対する回答です(最初の回答)。ありがとう! – cosmicFluke

+0

ループの各繰り返しで 'map(f、nums)'に 'list'を呼び出すのはどうでしょう? – Evert

+0

デフォルト引数は関数(λ)の作成時に評価されるので、デフォルト引数hack: 'lambda n、x = x:...'もあります。 –

5

問題がx変数を作成しているラムダ関数にアクセスする方法に関係しています。 Pythonのスコープが動作する方法では、lambda関数は、定義されたときの値ではなく、呼び出されたときに外部スコープの最新バージョンxを常に使用します。

mapは怠け者なのでそのループ(あなたがlistに渡すことで、ネストされたmap秒を消費する場合)とした後、彼らはすべての最後x値を使用するまで、ラムダ関数は呼び出されません。それらが定義されていたときに値xを保存し、各ラムダ関数を作るために

は、このようなx=xを追加し、持っている:

lambda n, x=x: n if x % 2 else n + 10 

これは、引数とそのデフォルト値を指定します。デフォルトはラムダが定義された時点で評価されるので、ラムダが(2番目の引数なしで)後で呼び出されると、式の中のxは保存されたデフォルト値になります。

関連する問題