tags:

views:

241

answers:

7

Hello, according to this page one can't use code like

if variable = something():
#do something with variable, whose value is the result of something() and is true

So in case I want to have the following code structure:

if a = something():
#do something with a
elif a = somethingelse():
#...
#5 more elifs

where something() functions are compute-intensive (I mean that using the function and then doing it again to assign value to a variable in case the first one was true can't be done), what should I write instead in Python? Add 7 more variables instead of 1?

+2  A: 

You could do this:

a = something()
if a:
    #do something with a
else:
    a = somethingelse()
    if a:
        #...
    else:
        #5 more nested ifs

Or, inside a function you can limit the nesting level with a return in each matching case:

def f():
    a = something()
    if a:
        #do something with a
        return
    a = somethingelse()
    if a:
        #...
        return
    #5 more ifs
Greg Hewgill
+3  A: 

Another alternative which offers some flexibility:

# Functions to be tested (can be expanded):
tests = [something, somethingelse, yetsomethingelse, anotherfunction, another]
for i, f in enumerate(tests):
    a = f()
    if a:
        if i == 0:
            # do something with a
        elif 1 <= i <= 3:
            # do something else with a
        else:
            # ...
        break

Or you can explicitly compare to the function:

tests = [something, somethingelse, yetsomethingelse, anotherfunction, another]
for i, f in enumerate(tests):
    a = f()
    if a: break
if not a:
    # no result
elif f == something:
    # ...
elif f == somethingelse:
    # ...

If some of the functions take arguments, you can use lambda to keep the function paradigm:

tests = [lambda: something(args), somethingelse, lambda: something(otherargs)]
for i, f in enumerate(tests):
    a = f()
    if a: break
if not a:
    # no result
elif i == 0:
    # ...
elif i == 1:
    # ...
RedGlyph
+1  A: 

You could use a decorator like this memorise for those functions - assuming they always return the same value. Notice that you can call expensive_foo and expensive_bar as many times as you like and the function body only ever gets executed once

def memoize(f):
    mem = {}
    def inner(*args):
        if args not in mem:
            mem[args] = f(*args)
        return mem[args]
    return inner

@memoize
def expensive_foo():
    print "expensive_foo"
    return False

@memoize
def expensive_bar():
    print "expensive_bar"
    return True

if expensive_foo():
    a=expensive_foo()
    print "FOO"
elif expensive_bar():
    a=expensive_bar()
    print "BAR"
gnibbler
This trick is generally called "memoization" (or "memoisation" if you use the Queen's English). No 'r'. References: http://en.wikipedia.org/wiki/Memoization http://antoniocangiano.com/2009/05/18/memoization-in-ruby-and-python/
steveha
+1  A: 

I might be missing something, but couldn't you factor each of the branches in your top-level if statement into separate functions, create a list of test to action tuples and loop over them? You should be able to apply this pattern to mimic if (value=condition()) {} else if (value=other_condition()) {} style logic.

This is really an extension of redglyph's response and can probably be compressed down to a iterator that raises StopIteration once it has hit a truth value.

#
# These are the "tests" from your original if statements. No
# changes should be necessary.
#
def something():
    print('doing something()')
    # expensive stuff here
def something_else():
    print('doing something_else()')
    # expensive stuff here too... but this returns True for some reason
    return True
def something_weird():
    print('doing something_weird()')
    # other expensive stuff

#
# Factor each branch of your if statement into a separate function.
# Each function will receive the output of the test if the test
# was selected.
#
def something_action(value):
    print("doing something's action")
def something_else_action(value):
    print("doing something_else's action")
def something_weird_action(value):
    print("doing something_weird's action")

#
# A simple iteration function that takes tuples of (test,action). The
# test is called. If it returns a truth value, then the value is passed
# onto the associated action and the iteration is stopped.
#
def do_actions(*action_tuples):
    for (test,action) in action_tuples:
        value = test()
        if value:
            return action(value)

#
# ... and here is how you would use it:
#
result = do_actions(
             (something, something_action),
             (something_else, something_else_action),
             (something_weird, something_weird_action)
)
D.Shawley
A: 

Make yourself a simple callable object that saves its returned value:

class ConditionValue(object):
    def __call__(self, x):
        self.value = x
        return bool(x)

Now use it like this:

# example code
makelower = lambda c : c.isalpha() and c.lower()
add10 = lambda c : c.isdigit() and int(c) + 10

test = "ABC123.DEF456"
val = ConditionValue()
for t in test:
    if val(makelower(t)):
        print t, "is now lower case ->", val.value
    elif val(add10(t)):
        print t, "+10 ->", val.value
    else:
        print "unknown char", t

Prints:

A is now lower case -> a
B is now lower case -> b
C is now lower case -> c
1 +10 -> 11
2 +10 -> 12
3 +10 -> 13
unknown char .
D is now lower case -> d
E is now lower case -> e
F is now lower case -> f
4 +10 -> 14
5 +10 -> 15
6 +10 -> 16
Paul McGuire
+8  A: 

I had this problem years ago, in 2001 -- since I was transliterating to Python from a reference-algorithm in C which used assign-and-test heavily, I was keen to keep a similar structure for the first draft (then refactor later once correctness was well tested). So I wrote a recipe in the Cookbook (see also here), which boils down to...:

class DataHolder(object):
    def set(self, value): self.value = value; return value

so the if/elif tree can become:

dh = DataHolder()
if dh.set(something()):
  # do something with dh.value
elif dh.set(somethingelse()):
  # ...

the DataHolder class can clearly be embellished in various ways (and is so embellished in both the online and book versions), but this is the gist of it, and quite sufficient to answer your question.

Alex Martelli
A: 

I would solve this the same way I solve several other problems of tricky flow control: move the tricky bit into a function, and then use return to cause an early exit from the function. Like so:

def do_correct_something():
    a = something()
    if a:
        # do something with a
        return a

    a = somethingelse()
    if a:
        # do something else with a
        return a

    # 5 more function calls, if statements, do somethings, and returns

    # then, at the very end:
    return None

a = do_correct_something()

The major other "tricky problem of flow control" for which I do this is breaking out of multiple nested loops:

def find_in_3d_matrix(matrix, x):
    for 2d_slice in matrix:
        for row in 2d_slice:
            for item in row:
                if test_function(x, item):
                    return item
    return None

You can also solve the stated question by writing a for loop that will iterate only once, and use "break" for the early exit, but I prefer the function-with-return version. It's less tricky, and clearer what is going on; and the function-with-return is the only clean way to break out of multiple loops in Python. (Putting "if break_flag: break" in each of the for loops, and setting break_flag when you want to break, is not IMHO clean.)

steveha