# if/elif chain with assignment expressions

Discussion in 'Python' started by Paul Rubin, Jul 11, 2004.

1. ### Paul RubinGuest

Sometimes you want to compute an expression, then do something with
the value if it meets a certain criterion; otherwise, try a different
expression (and maybe different criterion) instead, etc. With := as
an assignment expression operator, you could write:

if (y := f(x)) < 5:
fred(y)
elif (y := g(x)) < 7:
ted(y)
elif (y := h(x)) < 9:
ned(y)
etc.

Of course there are alternative ways of doing the same thing, but they
all seem to be messier.

Paul Rubin, Jul 11, 2004

2. ### steve holdenGuest

Paul Rubin wrote:
> Sometimes you want to compute an expression, then do something with
> the value if it meets a certain criterion; otherwise, try a different
> expression (and maybe different criterion) instead, etc. With := as
> an assignment expression operator, you could write:
>
> if (y := f(x)) < 5:
> fred(y)
> elif (y := g(x)) < 7:
> ted(y)
> elif (y := h(x)) < 9:
> ned(y)
> etc.
>
> Of course there are alternative ways of doing the same thing, but they
> all seem to be messier.

Indeed, you might think so. But this topic has been discussed
exhaustively (which isn't to say you aren't about to hear a lot more
about the subject) in this group, and the assignment operation is *not*
an operator for specific reasons the FAQ attempts to make clear in
http://www.python.org/doc/faq/general.html#why-can-t-i-use-an-assignment-in-an-expression

regards
Steve

steve holden, Jul 11, 2004

3. ### steve holdenGuest

Paul Rubin wrote:
> Sometimes you want to compute an expression, then do something with
> the value if it meets a certain criterion; otherwise, try a different
> expression (and maybe different criterion) instead, etc. With := as
> an assignment expression operator, you could write:
>
> if (y := f(x)) < 5:
> fred(y)
> elif (y := g(x)) < 7:
> ted(y)
> elif (y := h(x)) < 9:
> ned(y)
> etc.
>
> Of course there are alternative ways of doing the same thing, but they
> all seem to be messier.

Indeed, you might think so. But this topic has been discussed
exhaustively (which isn't to say you aren't about to hear a lot more
about the subject) in this group, and the assignment operation is *not*
an operator for specific reasons the FAQ attempts to make clear in
http://www.python.org/doc/faq/general.html#why-can-t-i-use-an-assignment-in-an-expression

regards
Steve

steve holden, Jul 11, 2004
4. ### Paul RubinGuest

steve holden <> writes:
> Indeed, you might think so. But this topic has been discussed
> exhaustively (which isn't to say you aren't about to hear a lot more
> about the subject) in this group, and the assignment operation is
> *not* an operator for specific reasons the FAQ attempts to make clear
> in
> http://www.python.org/doc/faq/general.html#why-can-t-i-use-an-assignment-in-an-expression

Yeah, I've seen those discussions before. The if/elif/elif example was
something I hadn't seen in those discussions, and it came up in some
code I was writing yesterday, so I posted about it.

Paul Rubin, Jul 11, 2004
5. ### Fernando PerezGuest

Paul Rubin wrote:

> steve holden <> writes:
>> Indeed, you might think so. But this topic has been discussed
>> exhaustively (which isn't to say you aren't about to hear a lot more
>> about the subject) in this group, and the assignment operation is
>> *not* an operator for specific reasons the FAQ attempts to make clear
>> in
>>

http://www.python.org/doc/faq/general.html#why-can-t-i-use-an-assignment-in-an-expression
>
> Yeah, I've seen those discussions before. The if/elif/elif example was
> something I hadn't seen in those discussions, and it came up in some
> code I was writing yesterday, so I posted about it.

And your example is an excellent argument, IMHO, of why the current situation is
bad. While the usage cases in the faq have easy alternatives (iterators), your
example does not, and you do end up forced to write a bunch of unnecessarily
verbose code. In general I applaud python's efforts for being clear, readable,
and not error-prone. But this is one of those cases where leaving the training
wheels on causes genuine grief (I have in the past many times run into the
example you mentioned, and have cursed it silently).

I very much doubt this will change in the language, but we can always hope

Cheers,

f

Fernando Perez, Jul 11, 2004
6. ### John RothGuest

"Paul Rubin" <http://> wrote in message
news:...
> steve holden <> writes:
> > Indeed, you might think so. But this topic has been discussed
> > exhaustively (which isn't to say you aren't about to hear a lot more
> > about the subject) in this group, and the assignment operation is
> > *not* an operator for specific reasons the FAQ attempts to make clear
> > in
> >

http://www.python.org/doc/faq/general.html#why-can-t-i-use-an-assignment-in-an-expression
>
> Yeah, I've seen those discussions before. The if/elif/elif example was
> something I hadn't seen in those discussions, and it came up in some
> code I was writing yesterday, so I posted about it.

I thoroughly agree. I've come up with that any number
of times, and allowing an assignment in an expression
is one of the things that I think should be done.

Note that I don't really care about the syntax, and
I care even less if it's really intuitive to novices. It's
one of those things that leads to simpler, more expressive
code when you need it, but it's not something that's
absolutely necessary to hack together a working
program while you're learning the language.

The use case is an expression in an if statement, not
an expression in a while statement. The latter can
be handled with an iterator and a for statement,
which might be a good idea, and it might not depending
on the specific situation.

There is an alternative, which is to find all of the
cases where this is likely to be a problem, and fix
the calls so that they naturally return an iterator.

To make this specific, let's take a look at the
<string>.find and <string>.rfind. This method
pair is probably the poster child of how not to
do it in a pythonic manner.

Let's assume we had a <string>.findall()
method, defined to return a list of all
indexes of the first character of each
substring that matches the given string.

Then we could say something like:

for firstMatch in myString.findall("fubar")[:1]:
do something with it

While this is not exactly obvious to the novice,
it not only gets the job done, but the findall()
method also has a number of other very
nice properties. It behaves properly in a boolean
context, and it never returns an incorrect index
(that is, -1.)

If you really did want to process all matches,
it's even easier.

John Roth

John Roth, Jul 12, 2004
7. ### Neil HodgsonGuest

Fernando Perez:

> In general I applaud python's efforts for being clear, readable,
> and not error-prone. But this is one of those cases where leaving
> the training wheels on causes genuine grief (I have in the past
> many times run into the example you mentioned, and have cursed
> it silently).

I hope this does not change as it makes analysis of the code easier.
Reading and understanding code is as important as writing it. Analysis of a
problem depends as much on restricting the set of possible causes ('it is
*not* the case that') as it does on generating a larger set of possible
causes ('it is the case that'). Pruning the possible cause tree is extremely
valuable and Python cooperates with a well thought out set of restrictions.
Being able to depend on the absence of assignments in if and while
statements is a benefit when fixing Python code over C/C++. Characterizing
these as "training wheels" and thus only of use to beginners is
unreasonable.

Neil

Neil Hodgson, Jul 12, 2004
8. ### Fernando PerezGuest

Neil Hodgson wrote:

> I hope this does not change as it makes analysis of the code easier.
> Reading and understanding code is as important as writing it. Analysis of a
> problem depends as much on restricting the set of possible causes ('it is
> *not* the case that') as it does on generating a larger set of possible
> causes ('it is the case that'). Pruning the possible cause tree is extremely
> valuable and Python cooperates with a well thought out set of restrictions.
> Being able to depend on the absence of assignments in if and while
> statements is a benefit when fixing Python code over C/C++. Characterizing
> these as "training wheels" and thus only of use to beginners is
> unreasonable.

Well, the problem here (and apparently others share this opinion) is that this
particular restriction genuinely forces kludgy code in certain circumstances.
As John Roth pointed out, the looping case is easily solved with iterators, but
Paul's original one is not.

I know that the if(x=0) bug in C/C++ is a potentially nasty one, having been
bitten by it myself in the past. It is also true that having this particular
option available allows certain kinds of code to be written in a clear and
concise manner. So yes, it is a slightly dangerous feature, but one which
offers true expressive power. Hence my characterization of it as 'training
wheels': I don't mean to say that only beginners can fall into the trap (even
Lance Armstrong can fall off a bike), but rather that experienced users will
know what to watch for, and will be willing to pay the price of potentially
falling for the liberty of going faster

Cheers,

f

Fernando Perez, Jul 12, 2004
9. ### Paul RubinGuest

"Neil Hodgson" <> writes:
> Pruning the possible cause tree is extremely valuable and Python
> cooperates with a well thought out set of restrictions.

If you're trying to add restrictions to prune a cause tree, how about
starting with private instance variables in classes and modules
instead of the silly underscore name mangling hack. Maybe you can

def f():
p = 3
def g():
p = 4
print 'g',p
g()
print 'f',p

f()

There's no way in g to set f's variable called 'p', which defeats the
purpose of nested functions as generally used since the days of Algol-60.

> Being able to depend on the absence of assignments in if and while
> statements is a benefit when fixing Python code over C/C++.

Not if it makes the code more complicated and bug-prone.

Paul Rubin, Jul 12, 2004
10. ### Donn CaveGuest

Quoth Fernando Perez <>:
....
| Well, the problem here (and apparently others share this opinion) is that this
| particular restriction genuinely forces kludgy code in certain circumstances.
| As John Roth pointed out, the looping case is easily solved with iterators, but
| Paul's original one is not.
|
| I know that the if(x=0) bug in C/C++ is a potentially nasty one, having been
| bitten by it myself in the past. It is also true that having this particular
| option available allows certain kinds of code to be written in a clear and
| concise manner. So yes, it is a slightly dangerous feature, but one which
| offers true expressive power. Hence my characterization of it as 'training
| wheels': I don't mean to say that only beginners can fall into the trap (even
| Lance Armstrong can fall off a bike), but rather that experienced users will
| know what to watch for, and will be willing to pay the price of potentially
| falling for the liberty of going faster

I know people who would like this feature a lot, if it existed in
Python and if they wrote in Python. I know this from their C code,
which I have had miserable occasion to deal with. If what we get
is kludgy code, I accept that gladly in return for the guarantee
that I shall never have to deal with this kind of mess in Python.

Donn Cave,

Donn Cave, Jul 12, 2004
11. ### Larry BatesGuest

e_list=[{'condition': 'f(y) < 5', 'call': fred},
{'condition': 'f(y) < 7', 'call': ted},
{'condition': 'f(y) < 9', 'call': ned}]

for e in e_list:
if eval(e['condition']):
e['call'](y)
break

Its easily extendable and would support an unlimited
number of function calls and conditions by merely
extending e_list definition. It also already works
with current implementation of Python.

HTH,
Larry Bates
Syscon, Inc.

"Paul Rubin" <http://> wrote in message
news:...
> Sometimes you want to compute an expression, then do something with
> the value if it meets a certain criterion; otherwise, try a different
> expression (and maybe different criterion) instead, etc. With := as
> an assignment expression operator, you could write:
>
> if (y := f(x)) < 5:
> fred(y)
> elif (y := g(x)) < 7:
> ted(y)
> elif (y := h(x)) < 9:
> ned(y)
> etc.
>
> Of course there are alternative ways of doing the same thing, but they
> all seem to be messier.

Larry Bates, Jul 12, 2004
12. ### Duncan BoothGuest

"Larry Bates" <> wrote in news:RPudnSx-4L-GOW_dRVn-
:
> "Paul Rubin" <http://> wrote in message
> news:...
>> Sometimes you want to compute an expression, then do something with
>> the value if it meets a certain criterion; otherwise, try a different
>> expression (and maybe different criterion) instead, etc.

>
> e_list=[{'condition': 'f(y) < 5', 'call': fred},
> {'condition': 'f(y) < 7', 'call': ted},
> {'condition': 'f(y) < 9', 'call': ned}]
>
> for e in e_list:
> if eval(e['condition']):
> e['call'](y)
> break

(I've moved the response to after the original text so that people can

It isn't generally a good idea to use eval when there are better options
around. Your suggestion could equally well be written without the eval:
(Changed back to f, g, h as per original post)

e_list=[{'condition': lambda: f(y) < 5, 'call': fred},
{'condition': lambda: g(y) < 7, 'call': ted},
{'condition': lambda: h(y) < 9, 'call': ned}]

for e in e_list:
if e['condition']():
e['call'](y)
break

Whilst I see your reason for using a dictionary, I would tend more towards
a tuple for code like this:

def doit(y):
e_list=[ (lambda: f(y) < 5, fred),
(lambda: g(y) < 7, ted),
(lambda: h(y) < 9, ned)]
for (condition,action) in e_list:
if condition():
action(y)
break

If the program contains multiple bits of code like this, you could
naturally extract the for loop out into another function, whereupon this
becomes:

def doit(y):
do_first_true((lambda: f(y) < 5, fred),
(lambda: g(y) < 7, ted),
(lambda: h(y) < 9, ned))

def do_first_true(*branches):
for (condition,action) in branches:
if condition():
action(y)
break

which is definitely less messy than assignments in conditionals and allows
for variations on the theme such as do_all_true.

Duncan Booth, Jul 12, 2004
13. ### Paul RubinGuest

"Larry Bates" <> writes:
>
> e_list=[{'condition': 'f(y) < 5', 'call': fred},
> {'condition': 'f(y) < 7', 'call': ted},
> {'condition': 'f(y) < 9', 'call': ned}]
>
> for e in e_list:
> if eval(e['condition']):
> e['call'](y)
> break
>
> Its easily extendable and would support an unlimited number of
> function calls and conditions by merely extending e_list definition.
> It also already works with current implementation of Python.

It throws away the value from evaluating the condition. Also, it uses
eval a lot, which adds a lot of overhead. You wanted something like:

e_list = [{'expr': lambda: f(x), 'condition': lambda y: y<5,
'call': lambda y: fred(y)},
{'expr': lambda: g(x), 'condition': lambda y: y<7,
'call': lambda y: ted(y)},
{'expr': lambda: h(x), 'condition': lambda y: y<9,
'call': lambda y: ned(y)}]
for e in e_list:
y = e.expr()
if e.cond(y):
e.call(y)
break

which is an unbelievably contorted way of replacing a straightforward
if/elif chain. And of course that wants an assignment expression too:

for e in e_list:
if e.cond(y := e.expr()):
e.call(y)
break

Paul Rubin, Jul 12, 2004
14. ### Paul RubinGuest

Paul Rubin <http://> writes:
> e_list = [{'expr': lambda: f(x), 'condition': lambda y: y<5,
> 'call': lambda y: fred(y)},
> {'expr': lambda: g(x), 'condition': lambda y: y<7,
> 'call': lambda y: ted(y)},
> {'expr': lambda: h(x), 'condition': lambda y: y<9,
> 'call': lambda y: ned(y)}]
> for e in e_list:
> y = e.expr()

Bah, I forgot you can't even say e.expr(), you have to say e['expr']().
(I've been doing too much Javascript).

Paul Rubin, Jul 12, 2004
15. ### Peter AbelGuest

Paul Rubin <http://> wrote in message news:<>...
> Sometimes you want to compute an expression, then do something with
> the value if it meets a certain criterion; otherwise, try a different
> expression (and maybe different criterion) instead, etc. With := as
> an assignment expression operator, you could write:
>
> if (y := f(x)) < 5:
> fred(y)
> elif (y := g(x)) < 7:
> ted(y)
> elif (y := h(x)) < 9:
> ned(y)
> etc.
>
> Of course there are alternative ways of doing the same thing, but they
> all seem to be messier.

When I started learning C, I remember there were big efforts to
be done to comprehend that an assignment returns a value that could
be used for a boolean decision. Once I had understood it I was glad
to have it. But always there was a little pitfall cause an assignment
and a comparison of equality were legal C-code. And the compiler did
what I wrote and not alway what I wanted it to do. So Python forces
me to say excactly what to do and there are solutions for any case.
Let me change your lines to the followings:

> if (assign('y',f(x)) < 5:
> fred(y)
> elif (assign('y',g(x)) < 7:
> ted(y)
> elif (assign('y',h(x)) < 9:
> ned(y)

And I think this code is clear enough to be readable and
self-documenting. Python gives us the utils to do so as the
following snippet shows:

>>> def assign(symbol,value):

.... globals()[symbol]=value
.... return value
....
>>> x=2
>>> f=lambda val:val*val
>>> if assign('y',f(x))<5:

.... print 'y<5; y=',y
.... else:
.... print 'y>=5; y=',y
....
y<5; y= 4
>>> if assign('y',f(4))<5:

.... print 'y<5; y=',y
.... else:
.... print 'y>=5; y=',y
....
y>=5; y= 16
>>>

Hope it helps a bit.

Regards
Peter

Peter Abel, Jul 12, 2004
16. ### Paul RubinGuest

(Peter Abel) writes:
> But always there was a little pitfall cause an assignment
> and a comparison of equality were legal C-code. And the compiler did
> what I wrote and not alway what I wanted it to do.

I have never for the life of me comprehended why so many people think
that's a problem. Yes, you can confuse '=' with '==', but you can also
confuse '<' with '<=', or '<' with '>'. Anyway, the usual solution
is to make the assignment-expression operator something like ':='
(the standard assignment operator in Algol) so that '=' in an expression
remains invalid. That seems to make the '='/'==' confusion go away.

> Let me change your lines to the followings:
>
> > if (assign('y',f(x)) < 5:
> > fred(y)
> >>> def assign(symbol,value):

> ... globals()[symbol]=value
> ... return value

Yuch, now you're going to make a GLOBAL variable to hold that saved
value? You're surely better off using a class instance or closure.
But either way is a mess compared to just using a local variable in
the natural way.

Paul Rubin, Jul 12, 2004
17. ### David FraserGuest

John Roth wrote:
> "Paul Rubin" <http://> wrote in message
> news:...
>
>>steve holden <> writes:
>>
>>>Indeed, you might think so. But this topic has been discussed
>>>exhaustively (which isn't to say you aren't about to hear a lot more
>>>about the subject) in this group, and the assignment operation is
>>>*not* an operator for specific reasons the FAQ attempts to make clear
>>>in
>>>

>
> http://www.python.org/doc/faq/general.html#why-can-t-i-use-an-assignment-in-an-expression
>
>>Yeah, I've seen those discussions before. The if/elif/elif example was
>>something I hadn't seen in those discussions, and it came up in some
>>code I was writing yesterday, so I posted about it.

>
>
> I thoroughly agree. I've come up with that any number
> of times, and allowing an assignment in an expression
> is one of the things that I think should be done.
>
> Note that I don't really care about the syntax, and
> I care even less if it's really intuitive to novices. It's
> one of those things that leads to simpler, more expressive
> code when you need it, but it's not something that's
> absolutely necessary to hack together a working
> program while you're learning the language.
>
> The use case is an expression in an if statement, not
> an expression in a while statement. The latter can
> be handled with an iterator and a for statement,
> which might be a good idea, and it might not depending
> on the specific situation.
>
> There is an alternative, which is to find all of the
> cases where this is likely to be a problem, and fix
> the calls so that they naturally return an iterator.
>
> To make this specific, let's take a look at the
> <string>.find and <string>.rfind. This method
> pair is probably the poster child of how not to
> do it in a pythonic manner.
>
> Let's assume we had a <string>.findall()
> method, defined to return a list of all
> indexes of the first character of each
> substring that matches the given string.
>
> Then we could say something like:
>
> for firstMatch in myString.findall("fubar")[:1]:
> do something with it
>
> While this is not exactly obvious to the novice,
> it not only gets the job done, but the findall()
> method also has a number of other very
> nice properties. It behaves properly in a boolean
> context, and it never returns an incorrect index
> (that is, -1.)
>
> If you really did want to process all matches,
> it's even easier.

I support str.findall ! Yes! Please!

David

David Fraser, Jul 13, 2004