if/elif chain with assignment expressions

P

Paul Rubin

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.
 
S

steve holden

Paul said:
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
 
S

steve holden

Paul said:
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
 
P

Paul Rubin

steve holden said:
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.
 
F

Fernando Perez

Paul said:
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
 
J

John Roth

Paul Rubin said:
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
 
N

Neil Hodgson

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
 
F

Fernando Perez

Neil said:
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
 
P

Paul Rubin

Neil Hodgson said:
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
also do something about scoping madness:

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.
 
D

Donn Cave

Quoth Fernando Perez <[email protected]>:
....
| 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, (e-mail address removed)
 
L

Larry Bates

What about:

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.
 
D

Duncan Booth

Larry Bates said:
Paul Rubin said:
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.
What about:

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
follow the thread)

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.
 
P

Paul Rubin

Larry Bates said:
What about:

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
 
P

Paul Rubin

Paul Rubin said:
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).
 
P

Peter Abel

Paul Rubin said:
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:
.... globals()[symbol]=value
.... return value
.... .... print 'y<5; y=',y
.... else:
.... print 'y>=5; y=',y
....
y<5; y= 4.... print 'y<5; y=',y
.... else:
.... print 'y>=5; y=',y
....
y>=5; y= 16
Hope it helps a bit.

Regards
Peter
 
P

Paul Rubin

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)
... 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.
 
D

David Fraser

John said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,169
Latest member
ArturoOlne
Top