if/elif chain with assignment expressions

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

  1. Paul Rubin

    Paul Rubin Guest

    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
    #1
    1. Advertising

  2. Paul Rubin

    steve holden Guest

    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
    #2
    1. Advertising

  3. Paul Rubin

    steve holden Guest

    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
  4. Paul Rubin

    Paul Rubin Guest

    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
    #4
  5. 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
    #5
  6. Paul Rubin

    John Roth Guest

    "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
    #6
  7. Paul Rubin

    Neil Hodgson Guest

    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
    #7
  8. 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
    #8
  9. Paul Rubin

    Paul Rubin Guest

    "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
    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.
     
    Paul Rubin, Jul 12, 2004
    #9
  10. Paul Rubin

    Donn Cave Guest

    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
    #10
  11. Paul Rubin

    Larry Bates Guest

    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.


    "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
    #11
  12. Paul Rubin

    Duncan Booth Guest

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

    > 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.
     
    Duncan Booth, Jul 12, 2004
    #12
  13. Paul Rubin

    Paul Rubin Guest

    "Larry Bates" <> writes:
    > 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
     
    Paul Rubin, Jul 12, 2004
    #13
  14. Paul Rubin

    Paul Rubin Guest

    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
    #14
  15. Paul Rubin

    Peter Abel Guest

    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
    #15
  16. Paul Rubin

    Paul Rubin Guest

    (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
    #16
  17. Paul Rubin

    David Fraser Guest

    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
    #17
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Christopher M. Lusardi
    Replies:
    1
    Views:
    428
    Victor Bazarov
    Nov 19, 2004
  2. Jim
    Replies:
    13
    Views:
    484
  3. Christopher M. Lusardi
    Replies:
    1
    Views:
    363
    Victor Bazarov
    Nov 19, 2004
  4. Charles Sullivan

    Is #elif portable?

    Charles Sullivan, Feb 24, 2007, in forum: C Programming
    Replies:
    2
    Views:
    384
    Charles Sullivan
    Feb 24, 2007
  5. Replies:
    2
    Views:
    319
    John Machin
    May 9, 2007
Loading...

Share This Page