_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
In a word, what I'm arguing is that we need a way to assign temporary variables in a comprehension.
In my opinion, code like
[y + g(y) for x in range(10) **some syntax for `y=f(x)` here**]
is more natural than any solution we now have.
These laws define behaviour that is expected equivalent by users;[x for x in xs] = xs
[f(x) for x in [x]] = f(x)
[g(y) for y in [f(x) for x in xs]] = [g(y) for x in xs for y in f(x)]
In [14]: xs = range(3)
In [15]: [(x,y) for x in xs for y in xs]
Out[15]: [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
In [16]: def f(x):
...: return "f({})".format(x)
...:
In [17]: def g(x):
...: return "g({})".format(x)
In [18]: xs = range(3)
In [19]: [x for x in xs]
Out[19]: [0, 1, 2]
In [20]: [f(x) for x in [x]]
Out[20]: ['f(5)']
In [21]: [g(y) for y in [f(x) for x in xs]]
Out[21]: ['g(f(0))', 'g(f(1))', 'g(f(2))']
In [27]: [g(y) for x in xs for y in f(x)]
Out[27]:
['g(f)',
'g(()',
'g(0)',
'g())',
'g(f)',
'g(()',
'g(1)',
'g())',
'g(f)',
'g(()',
'g(2)',
'g())']
In [29]: def f(x):
...: return 2*x
In [30]: [g(y) for x in xs for y in f(x)]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-30-82ba3864654b> in <module>()
----> 1 [g(y) for x in xs for y in f(x)]
<ipython-input-30-82ba3864654b> in <listcomp>(.0)
----> 1 [g(y) for x in xs for y in f(x)]
TypeError: 'int' object is not iterable
In [31]: [g(y) for y in [f(x) for x in xs]]
Out[31]: ['g(0)', 'g(2)', 'g(4)']
In [33]: [g(y) for y in (f(x) for x in xs)]
Out[33]: ['g(f(0))', 'g(f(1))', 'g(f(2))']
In [34]: [f(x) + g(f(x)) for x in range(10)]
Out[34]:
['f(0)g(f(0))',
'f(1)g(f(1))',
'f(2)g(f(2))',
'f(3)g(f(3))',
'f(4)g(f(4))',
'f(5)g(f(5))',
'f(6)g(f(6))',
'f(7)g(f(7))',
'f(8)g(f(8))',
'f(9)g(f(9))']
In [36]: [fx + g(fx) for fx in (f(x) for x in range(10))]
Out[36]:
['f(0)g(f(0))',
'f(1)g(f(1))',
'f(2)g(f(2))',
'f(3)g(f(3))',
'f(4)g(f(4))',
'f(5)g(f(5))',
'f(6)g(f(6))',
'f(7)g(f(7))',
'f(8)g(f(8))',
'f(9)g(f(9))']
In [41]: fx = (f(x) for x in range(10))
In [42]: [x + g(x) for x in fx]
Out[42]:
['f(0)g(f(0))',
'f(1)g(f(1))',
'f(2)g(f(2))',
'f(3)g(f(3))',
'f(4)g(f(4))',
'f(5)g(f(5))',
'f(6)g(f(6))',
'f(7)g(f(7))',
'f(8)g(f(8))',
'f(9)g(f(9))']
In [46]: import numpy as np
In [47]: def f(x):
...: return x * 2
...:
In [48]: def g(x):
...: return x * 3
...:
...:
In [49]: xs = np.arange(3)
In [50]: f(xs) + g(f(xs))
Out[50]: array([ 0, 8, 16])
In [51]: fx = f(xs)
...: fx + g(fx)
...:
Out[51]: array([ 0, 8, 16])
I’d like to clarify that f(x) was indeed meant to be a sequence. As per monad law:
do { y <- do { x <- m;
f x
}
g y
}
===
do { x <- m;
y <- f x;
g y}
I think you might have misunderstood the types of things; f: Function[a, List[b]] and g: Function[b, List[c]]. m: List[a]
But I DO think my old the fly write up went wrong, converting do-notation to list comprehensions isn’t completely straightforward
The above is equivalent to
[g y | x <- m, y <- f x] in Haskell and the top is [g y | y <- [z |x <- m, z <- f x]]
These have analogous structures in python;
[g(y) for x in m for y in f(x)] and [g(y) for y in [z for x in m for z in f(x)]] (I think?)
And yes the left identity law I posted was missing the [f(x)] brackets.
If I’ve not made another mistake, that *should* now work?
I’d like to clarify that f(x) was indeed meant to be a sequence. As per monad law:
do { y <- do { x <- m;
f x
}
g y
}
===
do { x <- m;y <- f x;g y}I think you might have misunderstood the types of things; f: Function[a, List[b]] and g: Function[b, List[c]]. m: List[a]
But I DO think my old the fly write up went wrong, converting do-notation to list comprehensions isn’t completely straightforward
The above is equivalent to
[g y | x <- m, y <- f x] in Haskell and the top is [g y | y <- [z |x <- m, z <- f x]]
These have analogous structures in python;
[g(y) for x in m for y in f(x)] and [g(y) for y in [z for x in m for z in f(x)]] (I think?)
If I’ve not made another mistake, that *should* now work?
A bit of a nit — function call overhead is substantial in python, so
if that is an actual function, rather than a simple expression, it’ll
likely be slower to call it twice for any but trivially small
iterables.
> [(y, y**2) let y = x+1 for x in (1, 2, 3, 4)]
Do we need the let?
[ g(y) for y = f(x) for c in seq]
Or, with expressions:
[y + y**2 for y = x+1 for x in (1,2,3)]
Maybe that would be ambiguous— I haven’t thought carefully about it.
-CHB
> Back to one of your examples:
>
> [f(x) for x in [x]]
>
> What does that mean???
>
> for x in seq
>
> Means iterate through seq, and assign each item to the name x.
>
> If that seq has x in it — I’m not sure that is even legal python — the
> scope in a comprehension confuses me.
>
> But that is the equivalent is something like:
>
> it= iter(seq)
> while True:
> Try:
> x = next(it)
> Except StopIteration:
> Break
>
> (Excuse the caps — hard to write code on a phone)
>
> So not sure how x gets into that sequence before the loop starts.
Reusing a previously bound name as an iteration variable is a bad idea.
It works in 3.x because the outermost iterable, but only the outermost
iterable, is pre-calculated before executing the comprehension. Hence
'x in [x]' sometimes works, and sometimes not. ('Outermost' is topmost
in nested loops, left most in comprehension.)
>>> x = 2
>>> [x*x for x in [x]]
[4]
>>> [x*y for x in [3] for y in [x]] # here, local x is 3, not 2
[9]
>>> [x*y for y in [x] for x in [3]]
[6]
>>> [x*y for y in [3] for x in [x]]
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
[x*y for y in [3] for x in [x]]
File "<pyshell#4>", line 1, in <listcomp>
[x*y for y in [3] for x in [x]]
UnboundLocalError: local variable 'x' referenced before assignment
>>> [z*y for y in [3] for z in [x]] # no confusion here
[6]
To put it another way, l = [x for x in [x]] is actually calculated as
_temp = [x]; l = [x for x in _temp]. In general, other iterables cannot
be precalculated since they might depend on prior iteration variables.
--
Terry Jan Reedy
To be frank, I had not thought of this before.
However, in my opinion, when considering adding a new syntax, we care more about the marginal cost.
I mean, I think it is the functional-programming way which is tricky, but allowing a new syntax would not make things worse.
Well, that's just a guess, maybe only those who are puzzled with comprehensions can give us an answer.
> >> But when it comes to something like >> [f(x) + g(f(x)) for x in range(10)] >> you find you have to sacrifice some readableness if you don't want two >> f(x) which might slow down your code. > >The usual comments about premature optimisation apply here. > >Setting a new comprehension variable is not likely to be free, and may even be >more costly than calling f(x) twice if f() is a cheap expression: > > [x+1 + some_func(x+1) for x in range(10)] > >could be faster than > > [y + some_func(y) for x in range(10) let y = x + 1] > >or whatever syntax we come up with. >
It is true. But since there are still so many cases where a temporary variable is faster.
Also, even without let-clause, one can write a for-loop with a temporary variable which slow down the code.
So, it seems that "setting a new comprehension variable may even be more costly" does not show any uselessness of temporary variables in comprehensions.
> >> Someone may argue that one can write >> [y + g(y) for y in [f(x) for x in range(10)]] > >Indeed. This would be the functional-programming solution, and I >personally think it is an excellent one. The only changes are that I'd >use a generator expression for the intermediate value, avoiding the need >to make a full list, and I would lay it out more nicely, using >whitespace to make the structure more clear: > > result = [y + g(y) for y in > (f(x) for x in range(10)) > ] >
[
y + g(y)
for x in range(10)
let y = f(x)
]
is better because it's more corresponding to a for-loop
for x in range(10):
y = f(x)
result.append(y + g(y))
In my opinion, comprehensions are not real functional-programming because there is not even a function. Though there're similarities, map and filter are real functional-programming. Since the similarity between for-clause in comprehensions and the for-loop, I think it's better to write comprehensions more close to for-loop.
I don't know but I guess maybe it can also help those who fear comprehensions better embrace them?
> >> but it's not as clear as to show what `y` is in a subsequent clause, >> not to say there'll be another temporary list built in the process. > >There's no need to build the temporary list. Use a generator >comprehension. And I disagree that the value of y isn't as clear. > >An alternative is simply to refactor your list comprehension. Move the >calls to f() and g() into a helper function: > >def func(x): > y = f(x) > return y + g(y) > >and now you can write the extremely clear comprehension > >[func(x) for x in range(10)] > >that needs no extra variable. >
I think it can be a goods idea if there's a name to `func` which is easy to understand, or `func` is put close to the comprehension and is in a obvious place.
But I feel it's not for the case I gave in another mail to Paul, https://mail.python.org/pipermail/python-ideas/2018-February/048997.html,
(I'm sorry that the example is quite long, and I don't hope to copy it here)
To me, it can be confusing to have several `func` when I have several lists at the same time and have to transform them each in a similar but different way.
I think the assignment should be treated equally as for-clause and if-clause which means
[(y, y**2) for x in (1, 2, 3, 4) let y = x+1]
would be a right syntax.
And
[(x, x**2) for x in (1, 2, 3, 4) let x = x+1]would not cause an error because we can also write
[(x, x**2) for x in (1, 2, 3, 4) for x in (4, 3, 2, 1)]
now.
I didn't see any problem in
[(w, w**2) for x in (1, 2, 3, 4) let y = x+1 for a in range(y) let z = a+1 if z > 2 for b in range(z) let w = z+1]
In my opinion, it would behave the same as
for x in (1, 2, 3, 4):
y = x+1
for a in range(y):
z = a+1
if z > 2:
for b in range(z):According to my understanding, the present for-clause and if-clause does everything quite similar to this nested way,
w = z+1
mylist.append((w, w**2))
[
{
'id': goods.id,
'name': goods.name,
'category': gc.name,
'category_type': gc.type,
}
for goods_id in goods_id_list
for goods is Goods.get_by_id(goods_id)
for gc is GoodsCategory.get_by_id(goods.category_id)
]
Examples inline:
On 2018-02-15 19:57, Steven D'Aprano wrote:
> Where should the assignment go? [(y, y**2) let y = x+1 for x in (1, 2,
> 3, 4)] [(y, y**2) for x in (1, 2, 3, 4) let y = x+1]
Since y is a function of x, it must follow the for clause:
> [
> (y, y ** 2)
> for x in (1, 2, 3, 4)
> let y = x + 1
> ]
> How do they interact when you have multiple loops and if-clauses? [(w,
> w**2) for x in (1, 2, 3, 4) let y = x+1 for a in range(y) let z = a+1
> if z > 2 for b in range(z) let w = z+1]
They are applied in order:
> [
> (w, w**2)
> for x in (1, 2, 3, 4)
> let y = x+1
> for a in range(y)
> let z = a+1
> if z > 2
> for b in range(z)
> let w = z+1
> ]
which is a short form for:
> def stuff():
> for x in (1, 2, 3, 4):
> y = x+1
> for a in range(y):
> z = a+1
> if z > 2:
> for b in range(z):
> w = z+1
> yield (w, w**2)
>
> list(stuff())
_______________________________________________
Python-ideas mailing list
Python...@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/
--
---
You received this message because you are subscribed to a topic in the Google Groups "python-ideas" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/python-ideas/KwZtO4rpAGE/unsubscribe.
To unsubscribe from this group and all its topics, send an email to python-ideas...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
On Fri, Feb 23, 2018 at 12:35 PM Kyle Lahnakoski <klahn...@mozilla.com> wrote:
> [
> (w, w**2)
> for x in (1, 2, 3, 4)
> let y = x+1
> for a in range(y)
> let z = a+1
> if z > 2
> for b in range(z)
> let w = z+1
> ]
which is a short form for:
> def stuff():
> for x in (1, 2, 3, 4):
> y = x+1
> for a in range(y):
> z = a+1
> if z > 2:
> for b in range(z):
> w = z+1
> yield (w, w**2)
>
> list(stuff())
Is it that much shorter that it's worth giving up the benefit of indentation?
On 2018-02-23 12:44, Neil Girdhar wrote:
On Fri, Feb 23, 2018 at 12:35 PM Kyle Lahnakoski <klahn...@mozilla.com> wrote:
> [
> (w, w**2)
> for x in (1, 2, 3, 4)
> let y = x+1
> for a in range(y)
> let z = a+1
> if z > 2
> for b in range(z)
> let w = z+1
> ]
which is a short form for:
> def stuff():
> for x in (1, 2, 3, 4):
> y = x+1
> for a in range(y):
> z = a+1
> if z > 2:
> for b in range(z):
> w = z+1
> yield (w, w**2)
>
> list(stuff())
Is it that much shorter that it's worth giving up the benefit of indentation?
Saving the indentation? Oh yes, for sure! This code reads like a story, the indentation is superfluous to that story. Should we add it to Python? I don't know; I quick scan through my own code, and I do not see much opportunity for list comprehensions of this complexity.
Either my data structures are not that complicated, or I have try/except blocks inside a loop, or I am using a real query language (like SQL). pythonql seems to solve all these problems well enough (https://github.com/pythonql/pythonql).
As far as I can see, a comprehension like
alist = [f(x) for x in range(10)]
is better than a for-loop
for x in range(10):
alist.append(f(x))
because the previous one shows every element of the list explicitly so that we don't need to handle `append` mentally.
But when it comes to something like
[f(x) + g(f(x)) for x in range(10)]
you find you have to sacrifice some readableness if you don't want two f(x) which might slow down your code.
Someone may argue that one can write
[y + g(y) for y in [f(x) for x in range(10)]]
but it's not as clear as to show what `y` is in a subsequent clause, not to say there'll be another temporary list built in the process.
We can even replace every comprehension with map and filter, but that would face the same problems.
In a word, what I'm arguing is that we need a way to assign temporary variables in a comprehension.
In my opinion, code like
[y + g(y) for x in range(10) **some syntax for `y=f(x)` here**]
is more natural than any solution we now have.
And that's why I pro the new syntax, it's clear, explicit and readable, and is nothing beyond the functionality of the present comprehensions so it's not complicated.
And I hope the discussion could focus more on whether we should allow assigning temporary variables in comprehensions rather than how to solve the specific example I mentioned above.
Le jeudi 15 février 2018 02:03:31 UTC-5, fhsxfhsx a écrit :As far as I can see, a comprehension like
what about:items = [z * (x + y + b) / (i - j - a)with:x = f1(a - i + j)y = f2(i - j + b)z = f3(x + y - (i * j))for a in a_rangewith:i = f3(b)j = f4(b)for b in b_range]?