2016-04-05 15 views
13

Im Zuge der Implementierung des "Variable Elimination" -Algorithmus für ein Bayes 'Nets-Programm stieß ich auf einen unerwarteten Fehler, der das Ergebnis einer iterativen Kartenumwandlung einer Sequenz von Objekten war.Karte vs Liste; Warum unterschiedliches Verhalten?

Der Einfachheit halber werde ich ein analoges Stück Code hier verwenden:

>>> 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] 

Dies ist definitiv das falsche Ergebnis. Da [4, 5, 6] zwei gerade Zahlen enthält, sollte 10 jedem Element höchstens zweimal hinzugefügt werden. Ich bekam auch im VE-Algorithmus ein unerwartetes Verhalten, also modifizierte ich es, um den map Iterator nach jeder Iteration in einen list umzuwandeln.

>>> 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] 

Von meinem Verständnis von Iterables, diese Modifikation sollte nicht ändern nichts, aber es tut. Klar, die n + 10 Transformation für den not x % 2 Fall wird in der list-Version ein Mal weniger angewendet.

Mein Bayes Nets-Programm funktionierte auch nach dem Finden dieses Fehlers, aber ich suche nach einer Erklärung, warum es aufgetreten ist.

+4

Bitte schreiben Sie keinen Code wie diesen. Es macht mein Gehirn weh. – Kevin

+0

Ihr Code tut nicht das, was Sie erklären sollten. Wenn du möchtest, dass die Zahlen ungerade bleiben und wenn sie gerade sind, addiere zehn, dann musst du 'nums = map (lambda n: n wenn x% 2! = 0 else n + 10, nums)' setzen ... Es muss etwas für die if-Funktion ausgewertet werden, andernfalls wird es immer als wahr ausgewertet. Übrigens. Das Problem mit dem Code war keine Zeilenumbrüche, wie Ihre Änderungen andeuten. Ich denke, worüber Kevin sich beschwert, sind die Punkte und Pfeile auf der linken Seite. –

+1

Könnte es sein, dass 'map' in Python3 ein Iterator ist? – hpaulj

Antwort

12

Die Antwort ist sehr einfach: map a 3 lazy Funktion in Python heißt, es gibt ein iterable Objekt (in Python 2 Sie gibt ein list). Lassen Sie mich eine Ausgabe an Ihrem Beispiel hinzufügen:

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] 

Notiere die In[8] - der Wert x ist 6. Wir haben auch die lambda Funktion verwandeln konnte, übergeben map, um den Wert von x zu verfolgen:

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] 

Da map faul ist, wird ausgewertet, wenn list aufgerufen wird. Der Wert von x ist jedoch 6 und deshalb erzeugt es eine verwirrende Ausgabe. Die Auswertung von nums innerhalb der Schleife führt zu erwarteten Ausgabe.

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

Ah! Es war das 'x'! Vielen Dank. Ich weiß, dass Iteratoren im Allgemeinen "faul" sind, aber ich habe diesen Teil der Faulheit vermisst. Gibt es eine Möglichkeit, einen Lambda-Ausdruck einzurichten, um eine Variable bei jeder Iteration einer Schleife sofort auszuwerten? (edit: Macht nichts, hab es!) Das scheint eine Gefahr zu sein, funktionale und imperative Programmierung zu mischen! – cosmicFluke

+2

"Es wird ausgewertet, wenn der x-Wert auf 6" geändert wurde. Vielmehr wird ausgewertet, wann "list" auf dem Kartenobjekt aufgerufen wird, was geschieht, wenn "x = 6" ist. – Evert

+0

@Evert, ja, ich repariere das, danke – soon

3

Wenn Sie die faulen Version verwenden möchten, müssen Sie x in jeder Schleife beheben. functools.partial tut genau das:

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

Das ist die Antwort auf meine Follow-up-Frage (auf die erste Antwort). Vielen Dank! – cosmicFluke

+0

Wie ist es faul, wenn Sie 'list' auf' map (f, nums) 'in jeder Iteration der Schleife aufrufen? – Evert

+0

Es gibt auch das Standardargument hack: 'lambda n, x = x: ...', da Standardargumente während der Erstellung der Funktion (lambda) ausgewertet werden. –

5

Das Problem hat damit zu tun, wie die x Variablen durch die Lambda-Funktionen zugegriffen werden, die Sie erstellen. Die Arbeitsweise von Python funktioniert so, dass die Lambda-Funktionen immer die neueste Version von x aus dem externen Bereich verwenden, wenn sie aufgerufen werden, und nicht den Wert, den sie hatten, als sie definiert wurden.

Da map faul ist, die Lambda-Funktionen werden nicht erst nach der Schleife aufgerufen (wenn Sie die verschachtelten map s verbrauchen, indem sie zu list vorbei) und so, sie alle nutzen den letzten x Wert.

Jedem Lambda-Funktion machen den Wert speichern x hat, wenn sie definiert sind, fügen Sie x=x wie folgt aus:

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

Dies gibt ein Argument und seinen Standardwert. Der Standardwert wird zu dem Zeitpunkt ausgewertet, zu dem das Lambda definiert wird. Wenn das Lambda später aufgerufen wird (ohne ein zweites Argument), ist der x innerhalb des Ausdrucks der gespeicherte Standardwert.

Verwandte Themen