# Re: One liners

Discussion in 'Python' started by Michael Torrie, Dec 7, 2013.

1. ### Michael TorrieGuest

On 12/06/2013 05:14 PM, Dan Stromberg wrote:
> I'm thinking mostly of stackoverflow, but here's an example I ran into (a
> lot of) on a job:
>
> somevar = some_complicated_thing(somevar) if
> some_other_complicated_thing(somevar) else somevar
>
> Would it really be so bad to just use an if statement? Why are we
> assigning somevar to itself? This sort of thing was strewn across 3 or 4
> physical lines at a time.

You're right that a conventional "if" block is not only more readable,
but also faster and more efficient code. Sorry you have to deal with
code written like that! That'd frustrate any sane programmer. It might
bother me enough to write code to reformat the program to convert that
style to something sane! There are times when the ternary (did I get
that right?) operator is useful and clear.

Michael Torrie, Dec 7, 2013

2. ### Steven D'ApranoGuest

On Fri, 06 Dec 2013 17:20:27 -0700, Michael Torrie wrote:

> On 12/06/2013 05:14 PM, Dan Stromberg wrote:
>> I'm thinking mostly of stackoverflow, but here's an example I ran into
>> (a lot of) on a job:
>>
>> somevar = some_complicated_thing(somevar) if
>> some_other_complicated_thing(somevar) else somevar
>>
>> Would it really be so bad to just use an if statement? Why are we
>> assigning somevar to itself? This sort of thing was strewn across 3 or
>> 4 physical lines at a time.

Unless you're embedding it in another statement, there's no advantage to
using the ternary if operator if the clauses are so large you have to
split the line over two or more lines in the first place. I agree that:

result = (spam(x) + eggs(x) + toast(x)
if x and condition(x) or another_condition(x)
else foo(x) + bar(x) + foobar(x))

is probably better written as:

if x and condition(x) or another_condition(x):
result = spam(x) + eggs(x) + toast(x)
else:
result = foo(x) + bar(x) + foobar(x)

The ternary if is slightly unusual and unfamiliar, and is best left for
when you need an expression:

ingredients = [spam, eggs, cheese, toast if flag else bread, tomato]

As for your second complaint, "why are we assigning somevar to itself", I
see nothing wrong with that. Better that than a plethora of variables
used only once:

# Screw this for a game of soldiers.
def function(arg, param_as_list_or_string):
if isinstance(param_as_list_or_string, str):
param = param_as_list_or_string.split()
else:
param = param_as_list_or_string

# Better.
def function(arg, param):
if isinstance(param, str):
param = param.split()

"Replace x with a transformed version of x" is a perfectly legitimate
technique, and not one which ought to be too hard to follow.

> You're right that a conventional "if" block is not only more readable,
> but also faster and more efficient code.

Really? I don't think so. This is using Python 2.7:

[steve@ando ~]\$ python -m timeit --setup="flag = 0" \
> "if flag: y=1
> else: y=2"

10000000 loops, best of 3: 0.0836 usec per loop

[steve@ando ~]\$ python -m timeit --setup="flag = 0" "y = 1 if flag else 2"
10000000 loops, best of 3: 0.0813 usec per loop

There's practically nothing between the two, but the ternary if operator
is marginally faster.

As for readability, I accept that ternary if is unusual compared to other
languages, but it's still quite readable in small doses. If you start
chaining them:

result = a if condition else b if flag else c if predicate else d

you probably shouldn't.

--
Steven

Steven D'Aprano, Dec 7, 2013

3. ### Chris AngelicoGuest

On Sat, Dec 7, 2013 at 1:28 PM, Steven D'Aprano
<> wrote:
> As for readability, I accept that ternary if is unusual compared to other
> languages...

All the C-derived ternary operators put the condition first, but
Python puts the condition in the middle. What that does for
readability I don't really know. Which is more important?

ChrisA

Chris Angelico, Dec 7, 2013
4. ### Roy SmithGuest

In article <52a287cb\$0\$30003\$c3e8da3\$>,
Steven D'Aprano <> wrote:

> The ternary if is slightly unusual and unfamiliar

It's only unusual an unfamiliar if you're not used to using it
Coming from a C/C++ background, I always found the lack of a ternary
expression rather limiting. There was much rejoicing in these parts
when it was added to the language relatively recently. I use them a lot.

On the other hand, I found list comprehensions to be mind-bogglingly
confusing when I first saw them (read: slightly unusual and unfamiliar).
It took me a long time to warm up to the concept. Now I love them.

> As for readability, I accept that ternary if is unusual compared to other
> languages, but it's still quite readable in small doses. If you start
> chaining them:
>
> result = a if condition else b if flag else c if predicate else d
>
> you probably shouldn't.

That I agree with (and it's just as true in C as it is in Python).

Just for fun, I took a look through the Songza code base. 66 kloc of
non-whitespace Python. I found 192 ternary expressions. Here's a few
of the more bizarre ones (none of which I consider remotely readable):

--------------------------------------------------
extracols = sorted(set.union(*(set(t.data.keys()) for t in tracks))) if
tracks else []
--------------------------------------------------
c2s = compids2songs(set(targets.keys()) |
set.union(*map(set,targets.itervalues())),self.docmap,self.logger) if
targets else {}
--------------------------------------------------
code = 2 if (pmp3,paac)==(mmp3,maac) else 3 if any(x is None for x in
(pmp3,paac,mmp3,maac)) else 4
--------------------------------------------------

Anybody else have some fun ternary abuse examples?

Roy Smith, Dec 7, 2013
5. ### Chris AngelicoGuest

On Sat, Dec 7, 2013 at 2:27 PM, Roy Smith <> wrote:
> --------------------------------------------------
> extracols = sorted(set.union(*(set(t.data.keys()) for t in tracks))) if
> tracks else []
> --------------------------------------------------
> c2s = compids2songs(set(targets.keys()) |
> set.union(*map(set,targets.itervalues())),self.docmap,self.logger) if
> targets else {}

Easy rewrites:

extracols = tracks and sorted(set.union(*(set(t.data.keys()) for t in tracks)))

Assumes that tracks is a list, which it most likely is given the
context. Parallel with the other.

ChrisA

Chris Angelico, Dec 7, 2013
6. ### Steven D'ApranoGuest

On Fri, 06 Dec 2013 22:27:00 -0500, Roy Smith wrote:

> Just for fun, I took a look through the Songza code base. 66 kloc of
> non-whitespace Python. I found 192 ternary expressions. Here's a few
> of the more bizarre ones (none of which I consider remotely readable):
>
> --------------------------------------------------
> extracols = ( sorted(set.union(*(set(t.data.keys()) for t in tracks)))
> if tracks else [] )

[extra parentheses added so I can split the line over two]

I actually don't find that too bad, readability-wise. Not ideal, but I
can follow it. However, I wonder why t.data.keys() is converted to a set
before being unpacked? Even assuming that data.keys are not necessarily
unique, wouldn't building the union make them so?

extracols = ( sorted(set.union(*(t.data.keys()) for t in tracks)))
if tracks else [] )

Also, you can get rid of the `if tracks` check altogether by using a
bound method instead of an unbound method:

extracols = sorted(set().union(*(t.data.keys()) for t in tracks))

ought to work even if tracks is empty.

--
Steven

Steven D'Aprano, Dec 7, 2013
7. ### Jussi PiitulainenGuest

Steven D'Aprano writes:

> On Fri, 06 Dec 2013 22:27:00 -0500, Roy Smith wrote:
>
> > Just for fun, I took a look through the Songza code base. 66 kloc of
> > non-whitespace Python. I found 192 ternary expressions. Here's a few
> > of the more bizarre ones (none of which I consider remotely readable):
> >
> > --------------------------------------------------
> > extracols = ( sorted(set.union(*(set(t.data.keys()) for t in tracks)))
> > if tracks else [] )

>
> [extra parentheses added so I can split the line over two]
>
> I actually don't find that too bad, readability-wise. Not ideal, but I
> can follow it. However, I wonder why t.data.keys() is converted to a set
> before being unpacked? Even assuming that data.keys are not necessarily
> unique, wouldn't building the union make them so?
>
> extracols = ( sorted(set.union(*(t.data.keys()) for t in tracks)))
> if tracks else [] )
>
>
> Also, you can get rid of the `if tracks` check altogether by using a
> bound method instead of an unbound method:
>
> extracols = sorted(set().union(*(t.data.keys()) for t in tracks))
>
> ought to work even if tracks is empty.

I suspect in the original code tracks could be None (or False) and
then the part of the code that bombs is 'for t in tracks' because
tracks is not iterable.

One could write [f(t) for t in tracks or []] and it wouldn't be blamed
on a binary operator - or maybe it would - but I suspect the cleaner
design would be to first make sure that an empty tracks is an empty
list (or an empty whatever-it-needs-to-be):

if tracks is None:
tracks = []
...
extracols = sorted(set.union(*(t.data.keys()) for t in tracks))

Still, is t.data.keys() a collection of sets? Maybe frozensets? And
then their unions are being sorted by set inclusion, which is not a
total order. Does sorted work correctly with a partial order? I don't
think it does.

Jussi Piitulainen, Dec 7, 2013
8. ### RotwangGuest

On 07/12/2013 12:41, Jussi Piitulainen wrote:
> [...]
>
> if tracks is None:
> tracks = []

Sorry to go off on a tangent, but in my code I often have stuff like
this at the start of functions:

tracks = something if tracks is None else tracks

or, in the case where I don't intend for the function to be passed
non-default Falsey values:

tracks = tracks or something

Is there any reason why the two-line version that avoids the ternary
operator should be preferred to the above?

Rotwang, Dec 7, 2013
9. ### Steven D'ApranoGuest

On Sat, 07 Dec 2013 16:13:09 +0000, Rotwang wrote:

> On 07/12/2013 12:41, Jussi Piitulainen wrote:
>> [...]
>>
>> if tracks is None:
>> tracks = []

>
> Sorry to go off on a tangent, but in my code I often have stuff like
> this at the start of functions:
>
> tracks = something if tracks is None else tracks
>
> or, in the case where I don't intend for the function to be passed
> non-default Falsey values:
>
> tracks = tracks or something
>
> Is there any reason why the two-line version that avoids the ternary
> operator should be preferred to the above?

Only if you need to support Python 2.4, which doesn't have the ternary if
operator

--
Steven

Steven D'Aprano, Dec 7, 2013
10. ### Michael TorrieGuest

On 12/07/2013 09:13 AM, Rotwang wrote:
> On 07/12/2013 12:41, Jussi Piitulainen wrote:
>> [...]
>>
>> if tracks is None:
>> tracks = []

>
> Sorry to go off on a tangent, but in my code I often have stuff like
> this at the start of functions:
>
> tracks = something if tracks is None else tracks
>
> or, in the case where I don't intend for the function to be passed
> non-default Falsey values:
>
> tracks = tracks or something
>
> Is there any reason why the two-line version that avoids the ternary
> operator should be preferred to the above?

I think for such a short operation, and for a common need like this,
what you do is fine. Personally I prefer the part you quoted from
I think Dan's gripes come when cleverness is taken to the extreme.

Michael Torrie, Dec 7, 2013
11. ### Michael TorrieGuest

On 12/06/2013 08:27 PM, Roy Smith wrote:
> In article <52a287cb\$0\$30003\$c3e8da3\$>,
> Steven D'Aprano <> wrote:
>
>> The ternary if is slightly unusual and unfamiliar

>
> It's only unusual an unfamiliar if you're not used to using it
> Coming from a C/C++ background, I always found the lack of a ternary
> expression rather limiting. There was much rejoicing in these parts
> when it was added to the language relatively recently. I use them a lot.
>
> On the other hand, I found list comprehensions to be mind-bogglingly
> confusing when I first saw them (read: slightly unusual and unfamiliar).
> It took me a long time to warm up to the concept. Now I love them.
>
>> As for readability, I accept that ternary if is unusual compared to other
>> languages, but it's still quite readable in small doses. If you start
>> chaining them:
>>
>> result = a if condition else b if flag else c if predicate else d
>>
>> you probably shouldn't.

>
> That I agree with (and it's just as true in C as it is in Python).
>
> Just for fun, I took a look through the Songza code base. 66 kloc of
> non-whitespace Python. I found 192 ternary expressions. Here's a few
> of the more bizarre ones (none of which I consider remotely readable):
>
> --------------------------------------------------
> extracols = sorted(set.union(*(set(t.data.keys()) for t in tracks))) if
> tracks else []

This is a generator expressions, and ternary ifs are common and often
needed in generator expressions.

> --------------------------------------------------
> c2s = compids2songs(set(targets.keys()) |
> set.union(*map(set,targets.itervalues())),self.docmap,self.logger) if
> targets else {}

I suspect the ternary distracted you on this one. The ternary here is
needed because if targets is None the expression fails. This part
anyway is a common idiom.

The rest is basically making a set (list of unique items only) of the
combined keys and values from the "targets" dictionary. Now I'm not
sure why the programmer needs do this, but nevertheless that's what it's
doing. set.union is used because that can iterate over a list of sets,
which is what the map returns. I suppose they could have done this, but
it wouldn't be much clearer unless you knew what sets, map and
itervalues do:

if targets:
c2s = compids2songs(
set(targets.keys()) |
set.union(*map(set,targets.itervalues())),
self.docmap,
self.logger )
else:
c2s = {}

In any case the ternary operator isn't really the part you were
complaining about. Personally if I needed to do this particular
operation a lot (combine keys and values into a set), I'd write a
function that returned the set. Still can't avoid the ternary, though,
unless you made compids2songs a little smarter (and we don't know what
compids2songs does with an empty set):

def dict_keys_and_values_set (some_dict):
return set(some_dict.keys()) |
set.union(*map(set,some_dict.itervalues()))

c2s = compids2songs( dict_keys_and_values_set(targets) ) if targets else {}

or I suppose you could o this:

c2s = {}
if targets: c2s = compids2songs( dict_keys_and_values_set(targets) )

Just a matter of taste.

> --------------------------------------------------
> code = 2 if (pmp3,paac)==(mmp3,maac) else 3 if any(x is None for x in
> (pmp3,paac,mmp3,maac)) else 4
> --------------------------------------------------

This one probably could stand to be reworked for sure! A standard if
block would be much clearer. Definitely an example of a programmer
thinking he was clever... maybe a git bisect could identify the author
and we can shame him.

> Anybody else have some fun ternary abuse examples?

Only the last one seems to be problematic to me.

Michael Torrie, Dec 7, 2013
12. ### Michael TorrieGuest

On 12/07/2013 09:56 AM, Michael Torrie wrote:
>> extracols = sorted(set.union(*(set(t.data.keys()) for t in tracks))) if
>> tracks else []

>
> This is a generator expressions, and ternary ifs are common and often
> needed in generator expressions.

Oops. This is not a generator expression at all!

Michael Torrie, Dec 7, 2013
13. ### rusiGuest

On Saturday, December 7, 2013 10:26:04 PM UTC+5:30, Michael Torrie wrote:
> On 12/06/2013 08:27 PM, Roy Smith wrote:
> > Steven D'Aprano wrote:
> >> The ternary if is slightly unusual and unfamiliar

> > It's only unusual an unfamiliar if you're not used to using it
> > Coming from a C/C++ background, I always found the lack of a ternary
> > expression rather limiting. There was much rejoicing in these parts
> > when it was added to the language relatively recently. I use them a lot.
> > On the other hand, I found list comprehensions to be mind-bogglingly
> > confusing when I first saw them (read: slightly unusual and unfamiliar).
> > It took me a long time to warm up to the concept. Now I love them.
> >> As for readability, I accept that ternary if is unusual compared to other
> >> languages, but it's still quite readable in small doses. If you start
> >> chaining them:
> >> result = a if condition else b if flag else c if predicate else d
> >> you probably shouldn't.

> > That I agree with (and it's just as true in C as it is in Python).
> > Just for fun, I took a look through the Songza code base. 66 kloc of
> > non-whitespace Python. I found 192 ternary expressions. Here's a few
> > of the more bizarre ones (none of which I consider remotely readable):
> > --------------------------------------------------
> > extracols = sorted(set.union(*(set(t.data.keys()) for t in tracks))) if
> > tracks else []

> This is a generator expressions, and ternary ifs are common and often
> needed in generator expressions.

> > --------------------------------------------------
> > c2s = compids2songs(set(targets.keys()) |
> > set.union(*map(set,targets.itervalues())),self.docmap,self.logger) if
> > targets else {}

> I suspect the ternary distracted you on this one. The ternary here is
> needed because if targets is None the expression fails. This part
> anyway is a common idiom.

> The rest is basically making a set (list of unique items only) of the
> combined keys and values from the "targets" dictionary. Now I'm not
> sure why the programmer needs do this, but nevertheless that's what it's
> doing. set.union is used because that can iterate over a list of sets,
> which is what the map returns. I suppose they could have done this, but
> it wouldn't be much clearer unless you knew what sets, map and
> itervalues do:

> if targets:
> c2s = compids2songs(
> set(targets.keys()) |
> set.union(*map(set,targets.itervalues())),
> self.docmap,
> self.logger )
> else:
> c2s = {}

> In any case the ternary operator isn't really the part you were
> complaining about. Personally if I needed to do this particular
> operation a lot (combine keys and values into a set), I'd write a
> function that returned the set. Still can't avoid the ternary, though,
> unless you made compids2songs a little smarter (and we don't know what
> compids2songs does with an empty set):

> def dict_keys_and_values_set (some_dict):
> return set(some_dict.keys()) |
> set.union(*map(set,some_dict.itervalues()))

> c2s = compids2songs( dict_keys_and_values_set(targets) ) if targets else {}

> or I suppose you could o this:

> c2s = {}
> if targets: c2s = compids2songs( dict_keys_and_values_set(targets) )

> Just a matter of taste.

> > --------------------------------------------------
> > code = 2 if (pmp3,paac)==(mmp3,maac) else 3 if any(x is None for x in
> > (pmp3,paac,mmp3,maac)) else 4
> > --------------------------------------------------

Just trying to rewrite that in a way which I try to use for long if-exprs

code = 2 if (pmp3,paac)==(mmp3,maac) else
3 if any(x is None for x in (pmp3,paac,mmp3,maac)) else
4

> This one probably could stand to be reworked for sure! A standard if
> block would be much clearer. Definitely an example of a programmer
> thinking he was clever... maybe a git bisect could identify the author
> and we can shame him.

The logic for writing (and hopefully reading) it this way is like this:

In math we often have equations that are defined by cases -- typically typeset
with a large curly bracket. Those else's hanging at the end are to be read as
a signal to read this whole expr and though under a big curly brace.

rusi, Dec 7, 2013
14. ### Jussi PiitulainenGuest

Rotwang writes:

> On 07/12/2013 12:41, Jussi Piitulainen wrote:
> > [...]
> >
> > if tracks is None:
> > tracks = []

>
> Sorry to go off on a tangent, but in my code I often have stuff like
> this at the start of functions:
>
> tracks = something if tracks is None else tracks
>
> or, in the case where I don't intend for the function to be passed
> non-default Falsey values:
>
> tracks = tracks or something
>
> Is there any reason why the two-line version that avoids the ternary
> operator should be preferred to the above?

My motivation is that the "one-armed if" highlights the condition,
together with the fact that nothing is changed unless the condition
holds. That said, I'm also fine with the forms you use.

On another tangent, I wish people called the conditional expression
the conditional expression. The number of its slots is about the least
salient property of the thing.

Jussi Piitulainen, Dec 7, 2013
15. ### Terry ReedyGuest

On 12/7/2013 11:13 AM, Rotwang wrote:
> On 07/12/2013 12:41, Jussi Piitulainen wrote:
>> [...]
>>
>> if tracks is None:
>> tracks = []

>
> Sorry to go off on a tangent, but in my code I often have stuff like
> this at the start of functions:
>
> tracks = something if tracks is None else tracks
>
> or, in the case where I don't intend for the function to be passed
> non-default Falsey values:
>
> tracks = tracks or something
>
> Is there any reason why the two-line version that avoids the ternary
> operator should be preferred to the above?

The 'extra' line is not necessary, as one can write

if tracks is None: tracks = [] # or something

I prefer this because it exactly expresses what one want done. The other
branch

else: tracks = tracks

is superfluous and to me unaesthetic.

--
Terry Jan Reedy

Terry Reedy, Dec 7, 2013
16. ### RotwangGuest

On 07/12/2013 16:25, Steven D'Aprano wrote:
> On Sat, 07 Dec 2013 16:13:09 +0000, Rotwang wrote:
>
>> On 07/12/2013 12:41, Jussi Piitulainen wrote:
>>> [...]
>>>
>>> if tracks is None:
>>> tracks = []

>>
>> Sorry to go off on a tangent, but in my code I often have stuff like
>> this at the start of functions:
>>
>> tracks = something if tracks is None else tracks
>>
>> or, in the case where I don't intend for the function to be passed
>> non-default Falsey values:
>>
>> tracks = tracks or something
>>
>> Is there any reason why the two-line version that avoids the ternary
>> operator should be preferred to the above?

>
> Only if you need to support Python 2.4, which doesn't have the ternary if
> operator

Thanks, and likewise to everyone else who replied.

Rotwang, Dec 8, 2013