Es lebt: lambda + yield

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

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.
bremer
User
Beiträge: 109
Registriert: Sonntag 25. Mai 2008, 00:13

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'
>>> 
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

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.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

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").
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Stimmt, gibt es auch als Ausdruck, Grammatik sollte man halt lesen können :roll:
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

hmm, sieht für mich auch nach nem Bug aus
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Python 2.6 zeigt dass selbe Verhalten. Da soll mal einer sagen Perl sei schwer verständlich *duck*.
lunar

Der erste, der dieses Verhalten jetzt nutzt, um sinnvollen, aber völlig unlesbaren Code zu produzieren, darf sich toll fühlen ;)
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

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.
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

Das ist ein Bug. "return x" ist in Generatoren, die "normal" mit def definiert werden, nicht umsonst verboten.

http://bugs.python.org/issue4748
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
Antworten