More efficient/elegant branching

Peter Otten __peter__ at web.de
Mon Dec 9 08:49:13 EST 2019


Musbur wrote:

> Hello,
> 
> I have a function with a long if/elif chain that sets a couple of
> variables according to a bunch of test expressions, similar to function
> branch1() below. I never liked that approach much because it is clumsy
> and repetetive, and pylint thinks so as well. I've come up with two
> alternatives which I believe are less efficient due to the reasons given
> in the respective docstrings. Does anybody have a better idea?
> 
> def branch1(a, b, z):
>      """Inelegant, unwieldy, and pylint complains
>      about too many branches"""
>      if a > 4 and b == 0:
>          result = "first"
>      elif len(z) < 2:
>          result = "second"
>      elif b + a == 10:
>          result = "third"
>      return result

I agree with Chris that this is the way to go.
pylint be damned ;)

> 
> def branch2(a, b, z):
>      """Elegant but inefficient because all expressions
>      are pre-computed althogh the first one is most likely
>      to hit"""

Also, it doesn't work in the general case, like

decision = [
   (a == 0, "first"),
   (b/a > 1, "second"),
   ...

>      decision = [
>          (a > 4 and b == 0, "first"),
>          (len(z) < 2,       "second"),
>          (b + a == 10,      "third")]
>      for (test, result) in decision:
>          if test: return result
> 
> def branch3(a, b, z):
>      """Elegant but inefficient because expressions
>      need to be parsed each time"""
>      decision = [
>          ("a > 4 and b == 0", "first"),
>          ("len(z) < 2",       "second"),
>          ("b + a == 10",      "third")]
>      for (test, result) in decision:
>          if eval(test): return result

You can shave off most of the overhead by precompiling the expressions:

DECISIONS = [
        ("a > 4 and b == 0", "first"),
        ("len(z) < 2",       "second"),
        ("b + a == 10",      "third")
]

DECISIONS4 = [
    (compile(expr, "<nofile>", "eval"), result)
    for expr, result in DECISIONS
]

def branch4(a, b, z):
    for test, result in DECISIONS4:
        if eval(test):
            return result
    raise ValueError

Using lambdas instead of precompiled expressions is a tad faster:

DECISIONS5 = [
    (eval("lambda a, b, z: " + expr), result)
    for expr, result in DECISIONS
]

def branch5(a, b, z):
    for test, result in DECISIONS5:
        if test(a, b, z):
            return result
    raise ValueError

This is is a slippery slope as you might now consider building branch1() 
from the DECISIONS list...



More information about the Python-list mailing list