[Python-ideas] Temporary variables in comprehensions
Chris Barker
chris.barker at noaa.gov
Thu Feb 15 18:34:28 EST 2018
On Thu, Feb 15, 2018 at 2:13 AM, Jamie Willis <jw14896.2014 at my.bristol.ac.uk
> wrote:
> These laws define behaviour that is expected equivalent by users;
>
> [x for x in xs] = xs
>
OK -- that's the definition...
> [f(x) for x in [x]] = f(x)
>
well, not quite:
[f(x) for x in [x]] = [f(x)]
Using x in two places where they mean different things makes this odd, but
yes, again the definition (of a list comp, and a length-1 sequence)
[g(y) for y in [f(x) for x in xs]] = [g(y) for x in xs for y in f(x)]
>
well, no. using two for expressions yields the outer product -- all
combinations:
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)]
so the result is a length len(seq1)*len(seq(2)) list. Or in this case, a
len(xs)**2.
But nesting the comps applies one expression, and then the other, yielding
a length len(xs) list.
but you wrote:
[g(y) for x in xs for y in f(x)]
which I'm not sure what you were expecting, as f(x) is not a sequence
(probably)...
To play with your examples:
Define some functions that make it clear what's been applied:
In [*16*]: *def* f(x):
...: *return* "f(*{}*)".format(x)
...:
In [*17*]: *def* g(x):
...: *return* "g(*{}*)".format(x)
and a simple sequence to use:
In [*18*]: xs = range(3)
Now your examples:
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))']
OK -- all good f applied, then g, but then the last one:
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 this case, f(x) is returning a string, which is a sequence, so you get
that kind odd result. But what if f(x) was a simple scalr function:
In [*29*]: *def* f(x):
...: *return* 2*x
Then you just get an error:
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
The the nested comp is what is desired here:
In [*31*]: [g(y) *for* y *in* [f(x) *for* x *in* xs]]
Out[*31*]: ['g(0)', 'g(2)', 'g(4)']
Except you probably want a generator expression in the inner loop to avoid
bulding an extra list:
In [*33*]: [g(y) *for* y *in* (f(x) *for* x *in* xs)]
Out[*33*]: ['g(f(0))', 'g(f(1))', 'g(f(2))']
So back to the OP's example:
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))']
that is best done with comps as:
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))']
which really doesn't seem bad to me. And if the function names are longer
-- which they should be, you might want to use a temp as suggested earlier:
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))']
The truth is, comprehensions really are a bit wordy, if you are doing a lot
of this kind of thing (at least with numbers), you might be happier with an
array-oriented language or library, such as numpy:
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])
is pretty compact, and can be "optimized with a temp:
In [*51*]: fx = f(xs)
...: fx + g(fx)
...:
Out[*51*]: array([ 0, 8, 16])
pretty simple isn't it?
So this gets back to -- does anyone have a suggestion for a syntax for
comprehensions that would make this substantially clearer, more readable,
or more compact?
I'm guessing not :-)
(the compact bit comes from having to type the "for x in" part twice -- it
does *feel* a bit unnecessary. which is why I like numpy -- no "for" at all
:-)
(I'm still trying to figure out why folks think map() or filter() help
here...)
-CHB
--
Christopher Barker, Ph.D.
Oceanographer
Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception
Chris.Barker at noaa.gov
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180215/ae90cb8e/attachment-0001.html>
More information about the Python-ideas
mailing list