This question touches on the very smelly part of the “known” and “obvious” Python syntax - what takes precedence, lambda, or list comprehension.
I don’t think that the goal of the OP was to create a list of squares from 0 to 9. If that were the case, we could give even more solutions:
squares = [] for x in range(10): squares.append(x*x)
- this is a good old way of imperative syntax.
But that is not the point. Is the fact that W (hy) TF is an ambiguous expression, so illogical? And in the end, I have an idiotic case for you, so do not reject my answer too soon (I had it at the interview).
So, OP comprehension returned a list of lambdas:
[(lambda x: x*x) for x in range(10)]
This, of course, is just 10 different copies of the squaring function, see:
>>> [lambda x: x*x for _ in range(3)] [<function <lambda> at 0x00000000023AD438>, <function <lambda> at 0x00000000023AD4A8>, <function <lambda> at 0x00000000023AD3C8>]
Please note the memory addresses of lambdas - they are all different!
Of course, you may have a more "optimal" (ha ha) version of this expression:
>>> [lambda x: x*x] * 3 [<function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>, <function <lambda> at 0x00000000023AD2E8>]
Cm? 3 times the same lambda.
Note that I used _ as the for variable. This has nothing to do with x in lambda (it's clouded lexically!). Get it?
I omit the discussion of why the syntax priority is not such that all of this meant:
[lambda x: (x*x for x in range(10))]
which can be: [[0, 1, 4, ..., 81]] , or [(0, 1, 4, ..., 81)] , or , which I consider the most logical , it will be a list of 1 element - generator , returning values. This is simply not the case; the language does not work like that.
BUT What if ...
What if you don't shadow the for variable and don't use it in your lambda ???
Well then shit happens. Look at this:
[lambda x: x * i for i in range(4)]
this means of course:
[(lambda x: x * i) for i in range(4)]
But it does not mean:
[(lambda x: x * 0), (lambda x: x * 1), ... (lambda x: x * 3)]
This is just crazy!
Lambdas in the list comprehension are closures throughout the whole scope of this comprehension. lexical closure, so they refer to i through the link, and not to its value in the evaluation!
So this expression:
[(lambda x: x * i) for i in range(4)]
roughly equivalent to:
[(lambda x: x * 3), (lambda x: x * 3), ... (lambda x: x * 3)]
I'm sure we could see more here using the Python decompiler (I mean, for example, the dis module), but this is enough for a discussion not related to Python-VM. So much for the interview question.
Now, how to make a list lambda factors that are really multiplied by consecutive integers? So, similar to the accepted answer, we need to break the direct connection with i , wrapping it in another lambda , which is called inside the list comprehension expression:
Before:
>>> a = [(lambda x: x * i) for i in (1, 2)] >>> a[1](1) 2 >>> a[0](1) 2
After:
>>> a = [(lambda y: (lambda x: y * x))(i) for i in (1, 2)] >>> a[1](1) 2 >>> a[0](1) 1
(I also had an external lambda variable = i , but I decided that this is a clearer solution - I introduced y so that we all can see what kind of witch is).
Edit 2019-08-30:
Following the suggestion of @josoler, which is also present in the answer of @sheridp, the value of the "loop variable" for understanding the list can be "embedded" in the object - the key to access it at the right time. The After section above does this by wrapping it in another lambda and invoking it immediately with the current value of i . Another way (a little easier to read - it does not produce the 'WAT' effect) is to store the value of i inside the partial object, and the "internal" (original) lambda take it as an argument (passed by the provided partial object during the call), that is:
After 2:
>>> from functools import partial >>> a = [partial(lambda y, x: y * x, i) for i in (1, 2)] >>> a[0](2), a[1](2) (2, 4)
Great, but there is still a small twist for you! Suppose we don’t want to make it easier to read the code and pass the factor by name (as a partial argument). Let's do the renaming:
After 2.5:
>>> a = [partial(lambda coef, x: coef * x, coef=i) for i in (1, 2)] >>> a[0](1) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: <lambda>() got multiple values for argument 'coef'
Wat?
>>> a[0]() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: <lambda>() missing 1 required positional argument: 'x'
Wait ... Are we changing the number of arguments to 1 and moving from "too much" to "too little"?
Well, this is not a real WAT when we pass coef to partial this way, it becomes the key argument, so it should follow the positional argument x , like so:
After 3:
>>> a = [partial(lambda x, coef: coef * x, coef=i) for i in (1, 2)] >>> a[0](2), a[1](2) (2, 4)
I would prefer the latest version over the nested lambda, but to each his own ...