Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Silly Python riddle (garlicsim.org)
77 points by cool-RR on Dec 20, 2011 | hide | past | favorite | 43 comments


>>> f = lambda: g((yield))

>>> f()

<generator object <lambda> at 0x00B50828>


Sorry for the dumb question (I'm still inexperienced with python), but can someone explain why the yield needs to be enclosed in ()?


yield was first introduced as a statement similar to return. In Python not every statement is an expression; another example of non-expression statement is assignment, which was banned from expressions so that it couldn't be confused with ==.

Since then someone proposed (PEP 342) that yield could be used not just to yield values from coroutines, but to send one back in. yield becomes an expression, mostly used as the right side of an assignment. The parentheses are there because the grammar seems to handle the yield statement and yield expressions differently (the PEP mentions that "yield 12, 42" would have become confusing).


Stuff like this makes me seriously doubt whether Python critiques of Ruby's "blocks and methods" (SO MANY CALLABLES; ONE AND ONLY ONE) are actually made in good faith.


I might misunderstand the point you are making, but this actually stems from a "one and only one" mentality: there is only a single entry for "yield" in the grammar, and it is defined to take a specific kind of expression as its argument.

(edit:) (Additionally, there is the "one and only one" mentality that if yield is valid in a expression for a normal method, it should be valid in an expression for a lambda as well; however, that doesn't cause this syntax issue: statement vs. expression does.)

This level of expression is allowed to contain a comma. As an example, you can do "yield 1, 2", which will yield a 2-tuple containing 1 and 2 as elements.

However, if you wish to embed it as an argument in a function call, you will end up with an ambiguity, as arguments to functions are comma separated. You therefore must embed this expression in parentheses, lest you get a weird grammar conflict.

To be clear, this is identical to the case with tuples themselves, with the one caveat that they decided to make it actually be a syntax error to use it in a way that causes the ambiguity (I will be clear that I do not know if I agree with their decision on that regard).

"return 1, 2" <- valid

"a, b = 1, 2" <- valid

"a((1, 2))" <- valid

"a(1, 2)" <- not what you meant

(edit:) JavaScript has a similar problem, by the way: they have all sorts of special variants of expressions that are not allowed to start with a brace or the keyword "function" due to ambiguities at the top levels of statements.

However, the most similar issue in JavaScript is with relation to the keyword "in": "for (var x = 5 in a" could either mean "loop over x, initialized to 5, as all keys of a" or "initialize x as whether 5 is a key in a".

You can figure out the difference once you get to the next token: ")" (the former) or ";" (the latter), but once you get that far you've already committed to specific parse tree formulations for the code you've already seen.


There are two kinds of callables. Functions, which can contain multiple expressions, and lambdas, which can contain only one.


Ah, ok; that didn't seem related to this specific discussion of yield (yield is valuable to have as an expression even in a multi-statement function), but I can see how it is reminiscent.

It should be noted, though, that when you use the lambda keyword, the result is a function, and has the same properties and behaviors as a function (including that it can contain yield and be a generator).

The lambda syntax is simply a way to declare a function as an expression. You can't have a multi-statement block embedded in an expression due to fundamental limitations of normative whitespace.

There really, though, is only one kind of "callable": "function". There are two syntaxes for declaring it, however: "def", which takes a block of statements, and "lambda", which takes an expression.

This is (to me) drastically different than the behavior difference that I believe is what you are referring to in Ruby, where Proc and lambda have different semantics for embedded usages of the "return" keyword.

(That said, I want to be clear, to both you and any random audience: I personally agree with Ruby's decision to have these two forms of callable, per real-world conversations I've had with wycats.)

(afterword) Someone else might find this interesting: I was going to also make the point that both lambda and def result in the same class (function), but it turns out that Proc.new and lambda do as well (Proc).


Whatever goes inside a lambda must be an expression. The parentheses around yield make it a yield expression. Otherwise yield would be a statement which is a different thing as far as Python is concerned.

You can also use it inside generators:

  >>> def f():
         while True:
             xyz = (yield)
             print xyz
  >>> g = f()
  >>> g.next()
  # nothing happens as xyz == None
  >>> g.send('huhu')
  huhu


Not a dumb question at all.

Short answer: it throws a syntax error otherwise.

Long answer: I don't know.


Yep, that's the one! Congrats!


lurking around here for years ... this riddle finally made me register an account with HN ...


Then I have achieved something of value today! :)


Can someone explain. It would seem like g still isn't defined. What's special about 'yield' that makes g valid?


You're right, it isn't. The presence of yield makes the return value of f() a generator object. The function body is not executed initially. When you call .next() on the generator object execution starts and runs until it hits the first yield, raising a NameError because g is not defined.

edit: typo


minor nit: it will not even reach the yield expression because it fails to resolve g:

  >>> import dis
  >>> f = lambda: g((yield))
  >>> dis.dis(f)
    1           0 LOAD_GLOBAL              0 (g)
                3 LOAD_CONST               0 (None)
                6 YIELD_VALUE
                7 CALL_FUNCTION            1
               10 RETURN_VALUE
  >>>


I'm not sure, why you think the value of the lambda function is thrown away. The lambda function returns a generator object. The function body of a generator is generally not executed until you call .next() on it, so that's why you don't get a NameError instantly. Also the value, that you .send() into the generator is not thrown away either. In your example the generator just runs into an exception before it goes into a state in which it would accept a .send() call. Just consider this modified example:

  >>> f=lambda: (yield)
  >>> gen=f()
  >>> gen.next()
  >>> gen.send('foobar')
  'foobar'


I think you misunderstood which value gets thrown away.

Consider this:

    >>> g = lambda x: 7
    >>> f = lambda: g((yield))
    >>> list(f())
    [None]
Can you tell me where the 7 went to? As far as I see, it went into oblivion.


Strange. With Python 2.6 I get [None, 7] while with Python 2.7 it is [None]. Looking at the OP codes the 2.7 one's look weird.

Python 2.6:

  >>> dis.dis(f)
    1           0 LOAD_GLOBAL              0 (g)
                3 LOAD_CONST               0 (None)
                6 YIELD_VALUE
                7 CALL_FUNCTION            1
               10 RETURN_VALUE
Python 2.7:

  >>> dis.dis(f)
    1           0 LOAD_GLOBAL              0 (g)
                3 LOAD_CONST               0 (None)
                6 YIELD_VALUE
                7 CALL_FUNCTION            1
               10 POP_TOP
               11 LOAD_CONST               0 (None)
               14 RETURN_VALUE
In essence the Python 2.7 compiler decides to throw away the result of the g() function call and replaces it with None. Does anyone understand why?



Ahh, thanks!


As i said, f() returns a generator object in this case. So you're calling list on a generator that yields None once, the result of which is a list that contains None. If you change the `yield` to `yield "foobar"`, list(f()) will get you `["foobar"]`.

Edit: Perhaps, to clear things up more, about where the value 7 went- your snippet can be expressed without using lambdas as

  >>> def g(x):
  ...  return 7
  ... 
  >>> def f():
  ...  v = (yield)
  ...  g(v)
  ... 
  >>> list(f())
  [None]
Note that there's no return before g(v) because generators (at least in 2.7) can only yield, not return a value.


I'm thinking that the parent is expecting 'yield' to return 'None' into g(), and then for g() to return 7 since it doesn't do anything with the 'x' parameter, and is confused why that isn't the case.


Yes, but I think this is cleared up if you consider that calling a generator function does not execute its body, but return generator object. It doesn't really matter if it's a lambda or a regular named function.


Sure, I understand all of that. I'm just saying it's unusual to see a lambda function throwing away the value like that.


The value of the lambda is the generator object. How else could you call list() on it if it was thrown away?


You're just using the word "value" in a different way. I meant "value" as in the 7.


You're right. I am still referring to that particular part of your blog post:

  >> So that’s the only case I can think of where Python
  >> completely throws away the value of a lambda function.


I had this (with 8 characters):

  );g=(int
giving:

  f = lambda: g();g=(int)
  f() -> 0


There is also this: f = lambda: g();g=(f)

Which produce a 'RuntimeError: maximum recursion depth exceeded'; But that's only because my computer is not powerful enough :)


And it's even shorter than the yield one.


I haven't been following the thread, but Ram posted the same riddle on Python-IL mailing list a few days ago. You can see the discussion there.

http://hamakor.org.il/pipermail/python-il/2011-December/thre...


  """


Doesn't count... If only for the fact that you won't get a >>> prompt after it.


At 12 characters, this is definitely not the shortest, but it is a totally different approach than the other solutions:

    >>> f = lambda: g()if 0 else(1)
    >>> f()
    1


Creative solution! You can knock it down a character by removing the "1" and simply returning an empty tuple.

    >>> f = lambda: g()if 0 else()
    >>> f()
    ()


I was going to attempt this, but the solution was posted inline in the blog entry, and I read it by accident. My window was just big enough that it was the last line displayed. =(


Sorry, I couldn't find a way to blacken the text with Tumblr.

I now did a (Scroll down...) thing to "hide" the solution.


Something like

);f=(set

Leading to the whole line reading:

f = lambda: g();f=(set)

8 characters


Alternatively

);f=set#


the winning solutions seems to be 7 chars


0);g=(id

8 chars


f = lambda g:(0) f() <function <lambda> at 0x113478>.


f = lambda: g(""")

f()

technically not two commands, and it will look like:

>>> f = lambda: g(""")

. . . f()




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: