Seite 1 von 1

Es lebt: lambda + yield

Verfasst: Dienstag 23. Dezember 2008, 11:08
von str1442
Vorhin drauf gestossen:

Code: Alles auswählen

n [1]: a = lambda: (yield 1)

In [2]: a()
Out[2]: <generator object at 0xb78327ac>

In [3]: a().next()
Out[3]: 1

In [4]: a = lambda: ((yield 1), (yield 2))

In [5]: b = a()

In [6]: b.next()
Out[6]: 1

In [7]: b.next()
Out[7]: 2

In [8]: b.next()
Out[8]: (None, None)

In [9]: b.next()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
/home/str1442/<ipython console> in <module>()

StopIteration: 
Grade der Tupel in Zeile 8 ist interessant, es wird also kein Generator zurückgegeben, sondern ein Tupel mit yield Rückgabewerten, da yield ja seit 2.5? ja ein Ausdruck ist, und diese kommen dann durch lambda auch in den Generator... oder so. Auf jeden Fall sieht das nicht gesund aus :D

Hier nochmal mit gen.send():

Code: Alles auswählen

In [10]: a = lambda: ((yield 1), (yield 2))

In [11]: b = a()

In [12]: b.next()
Out[12]: 1

In [13]: b.send(5)
Out[13]: 2

In [14]: b.next()
Out[14]: (5, None)

In [15]: b.next()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)

/home/str1442/<ipython console> in <module>()

StopIteration: 
Disassembliert:

Code: Alles auswählen

In [18]: dis.dis(a)
  1           0 LOAD_CONST               0 (1)
              3 YIELD_VALUE         
              4 LOAD_CONST               1 (2)
              7 YIELD_VALUE         
              8 BUILD_TUPLE              2
             11 RETURN_VALUE        
(Return Value? Also kein yield im letzten Schritt und trotzdem über den Generator erreichbar?)

Py 2.5. ist das in 2.6 / 3.0 immernoch so (habe es nicht kompiliert vorliegen)? Im 3.0 Changelog (http://www.python.org/download/releases/3.0/NEWS.txt, war das Einzige was ich mittels python.org Suche gefunden habe und einem Changelog nahekam) habe ich zu lambda oder yield nichts gefunden.

Verfasst: Dienstag 23. Dezember 2008, 11:27
von bremer
In Python 3.0:

Code: Alles auswählen

>>> a = lambda: (yield 1)
>>> a()
<generator object <lambda> at 0x0138B300>
>>> a().next()
Traceback (most recent call last):
  File "<pyshell#9>", line 1, in <module>
    a().next()
AttributeError: 'generator' object has no attribute 'next'
>>> 

Verfasst: Dienstag 23. Dezember 2008, 11:36
von Trundle
Funktioniert auch noch mit Py3k (wenn man die Syntax anpasst (``next(a())``)).

Sieht für mich ja nach einem Bug aus: zum Einen ist yield ein Statement und sollte deshalb gar nicht in lambda benutzbar sein, zum anderen können Generatoren keine returns mit Rückgabewert haben. Nur scheint der Parser wohl irgendwie ein yield in lambdas zuzulassen, wodurch dieses obskure Verhalten ermöglicht wird.

Verfasst: Dienstag 23. Dezember 2008, 11:48
von str1442
yield ist seit 2.5 (afaik, vllt auch 2.4) ein Ausdruck, damit man es in Verbindung mit der Zuweisung von Werten benutzen kann. Vermutlich hat man dabei vergessen, die Benutzung von yield in lambda's einzuschränken. So kann man beispielsweise auch ein lambda in einem lambda definieren, aber das ist natürlich definiertes Verhalten.
Generatoren keine returns mit Rückgabewert haben.
Richtig, ich habe auch grade nochmal mit dem dis Modul einen echten Generator überprüft, da wird auch etwas mit Return zurückgegeben, das ist aber der Generator selber. Oben hingegen findet dafür einmal "YIELD_VALUE" zu wenig statt (nämlich nach dem BUILD_TUPLE, der Tuple wird ja nicht "geyieldet").

Verfasst: Dienstag 23. Dezember 2008, 12:34
von Trundle
Stimmt, gibt es auch als Ausdruck, Grammatik sollte man halt lesen können :roll:

Verfasst: Dienstag 23. Dezember 2008, 12:51
von Darii
hmm, sieht für mich auch nach nem Bug aus

Verfasst: Dienstag 23. Dezember 2008, 16:26
von DasIch
Python 2.6 zeigt dass selbe Verhalten. Da soll mal einer sagen Perl sei schwer verständlich *duck*.

Verfasst: Dienstag 23. Dezember 2008, 17:28
von lunar
Der erste, der dieses Verhalten jetzt nutzt, um sinnvollen, aber völlig unlesbaren Code zu produzieren, darf sich toll fühlen ;)

Verfasst: Mittwoch 24. Dezember 2008, 01:20
von str1442
Vielleicht so:

Code: Alles auswählen

In [9]: def square_and_sum(*args):
   ...:     _ = eval("lambda: (" + " ".join("(yield %i)," % arg for arg in map(int, args)) + ")")()
   ...:     
   ...:     inject = _.next()
   ...:     while True:
   ...:         inject = _.send(inject ** 2)
   ...:         
   ...:         if isinstance(inject, tuple):
   ...:                  break
   ...:     squares = inject
   ...:     
   ...:     return squares, sum(squares)
   ...: 

In [12]: square_and_sum(*range(12))
Out[12]: ((0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121), 506)
Meines Wissens nach sind hier im Forum einige "Python Core Developer" (birkenfeld?), insofern wird es ja an die "Oberfläche" dringen.

Verfasst: Freitag 26. Dezember 2008, 10:57
von birkenfeld
Das ist ein Bug. "return x" ist in Generatoren, die "normal" mit def definiert werden, nicht umsonst verboten.

http://bugs.python.org/issue4748