If/then style question

J

John Gordon

(This is mostly a style question, and perhaps one that has already been
discussed elsewhere. If so, a pointer to that discussion will be
appreciated!)

When I started learning Python, I wrote a lot of methods that looked like
this:


def myMethod(self, arg1, arg2):

if some_good_condition:

if some_other_good_condition:

if yet_another_good_condition:

do_some_useful_stuff()
exitCode = good1

else:
exitCode = bad3

else:
exitCode = bad2

else:
exitCode = bad1

return exitCode


But lately I've been preferring this style:


def myMethod(self, arg1, arg2):

if some_bad_condition:
return bad1

elif some_other_bad_condition:
return bad2

elif yet_another_bad_condition:
return bad3

do_some_useful_stuff()
return good1

I like this style more, mostly because it eliminates a lot of indentation.

However I recall one of my college CS courses stating that "one entry,
one exit" was a good way to write code, and this style has lots of exits.

Are there any concrete advantages of one style over the other?

Thanks.
 
T

Tim Harig

I like this style more, mostly because it eliminates a lot of indentation.

However I recall one of my college CS courses stating that "one entry,
one exit" was a good way to write code, and this style has lots of exits.

So, take the good intentation from one and the single exit from the other:

def myMethod(self, arg1, arg2):

if some_bad_condition:
exitCode = bad1

elif some_other_bad_condition:
exitCode = bad2

elif yet_another_bad_condition:
exitCode = bad3

else:
exitCode = do_some_useful_stuff()

# possible common cleanup code here

return exitCode

Or, raise an exception on bad condtions rather then passing an error code.
 
E

Ethan Furman

John said:
(This is mostly a style question, and perhaps one that has already been
discussed elsewhere. If so, a pointer to that discussion will be
appreciated!)

When I started learning Python, I wrote a lot of methods that looked like
this:


def myMethod(self, arg1, arg2):
if some_good_condition:
if some_other_good_condition:
if yet_another_good_condition:
do_some_useful_stuff()
exitCode = good1
else:
exitCode = bad3
else:
exitCode = bad2
else:
exitCode = bad1
return exitCode


But lately I've been preferring this style:

def myMethod(self, arg1, arg2):
if some_bad_condition:
return bad1
elif some_other_bad_condition:
return bad2
elif yet_another_bad_condition:
return bad3
do_some_useful_stuff()
return good1

I like this style more, mostly because it eliminates a lot of indentation.

As far as if/else goes, I prefer the second style also.

As far as returning bad codes, you are better off raising exceptions:

def myMethod(self, arg1, arg2):
if some_bad_condition:
raise Bad1()
elif some_other_bad_condition:
raise Bad2()
elif yet_another_bad_condition:
raise Bad3()
do_some_useful_stuff
# no need to return 'good' code -- success means no problems

~Ethan~
 
G

Grant Edwards

(This is mostly a style question, and perhaps one that has already been
discussed elsewhere. If so, a pointer to that discussion will be
appreciated!)

When I started learning Python, I wrote a lot of methods that looked like
this:


def myMethod(self, arg1, arg2):
if some_good_condition:
if some_other_good_condition:
if yet_another_good_condition:
do_some_useful_stuff()
exitCode = good1
else:
exitCode = bad3
else:
exitCode = bad2
else:
exitCode = bad1
return exitCode


But lately I've been preferring this style:


def myMethod(self, arg1, arg2):

if some_bad_condition:
return bad1

elif some_other_bad_condition:
return bad2

elif yet_another_bad_condition:
return bad3

do_some_useful_stuff()
return good1

I like this style more, mostly because it eliminates a lot of
indentation.

There's nothing inherently wrong with indentation, but in this case
the latter style is a _lot_ easier to read (and modify without
breaking).
However I recall one of my college CS courses stating that "one
entry, one exit" was a good way to write code, and this style has
lots of exits.

Are there any concrete advantages of one style over the other?

I think the check/exit style is far more readable.

It can trip you up if there is cleanup stuff that needs to happen
before you return from the function. In that case putting the whole
function in a try statement and raising exceptions for the "bad
conditions" works nicely. Then you get the more readable style of the
check/exit style, plus the advantage of a single exit (it's easy to
verify visually that all the required cleanup is happening).
 
S

Stefan Sonnenberg-Carstens

Am 16.12.2010 22:49, schrieb John Gordon:
(This is mostly a style question, and perhaps one that has already been
discussed elsewhere. If so, a pointer to that discussion will be
appreciated!)

When I started learning Python, I wrote a lot of methods that looked like
this:


def myMethod(self, arg1, arg2):

if some_good_condition:

if some_other_good_condition:

if yet_another_good_condition:

do_some_useful_stuff()
exitCode = good1

else:
exitCode = bad3

else:
exitCode = bad2

else:
exitCode = bad1

return exitCode


But lately I've been preferring this style:


def myMethod(self, arg1, arg2):

if some_bad_condition:
return bad1

elif some_other_bad_condition:
return bad2

elif yet_another_bad_condition:
return bad3
else:
do_some_useful_stuff()
return good1

I like this style more, mostly because it eliminates a lot of indentation.

However I recall one of my college CS courses stating that "one entry,
one exit" was a good way to write code, and this style has lots of exits.

Are there any concrete advantages of one style over the other?

Thanks.




The advantage in latter case is fewer operations, because you can skip
the assignments,
and it is more readable.

The "one entry, one exit" is an advice. Not a law.
Your code is OK.

As long as it works ;-)


P.S.:
Sorry, I could not resist:
return bad1 if some_bad_condition else bad2 if some_other_bad_condition
else bad3 if yet_another_bad_condition else good1 if
do_some_useful_stuff() else good1
Or consider this:
return [x for x,y in
((bad1,some_bad_condition),(bad2,some_other_bad_condition),(bad3,yet_another_bad_condition),(good1,do_some_useful_stuff()
or True)) if x][0]

Neither self explanatory nor readable :-(
 
R

Ryan Kelly

(This is mostly a style question, and perhaps one that has already been
discussed elsewhere. If so, a pointer to that discussion will be
appreciated!)

When I started learning Python, I wrote a lot of methods that looked like
this:


def myMethod(self, arg1, arg2):
if some_good_condition:
if some_other_good_condition:
if yet_another_good_condition:
do_some_useful_stuff()
exitCode = good1
else:
exitCode = bad3
else:
exitCode = bad2
else:
exitCode = bad1
return exitCode


But lately I've been preferring this style:


def myMethod(self, arg1, arg2):
if some_bad_condition:
return bad1
elif some_other_bad_condition:
return bad2
elif yet_another_bad_condition:
return bad3
do_some_useful_stuff()
return good1

I like this style more, mostly because it eliminates a lot of indentation..

However I recall one of my college CS courses stating that "one entry,
one exit" was a good way to write code, and this style has lots of exits.

Are there any concrete advantages of one style over the other?


"one entry, one exit" has its good points, but it's *way* overquoted and
overused.

Do you raise any exceptions? Do you call any functions that might raise
exceptions? If so, you've got multiple exit points already.

I think this style a lot more important in a language like C where you
have to be super-careful about cleaning up after yourself. The single
exit point makes it easier to verify that all cleanup tasks have been
performed. Assuming you're using "with" or "try-finally" then you just
don't need such guarantees in python.

I'm not a PEP-8 pedant by any means, but I think that the first section
of PEP-8 contains the best advice I've ever read about programming
language style. In fact, I'm going to quote it right here:



A Foolish Consistency is the Hobgoblin of Little Minds
======================================================
One of Guido's key insights is that code is read much more often than it
is written. The guidelines provided here are intended to improve the
readability of code and make it consistent across the wide spectrum of
Python code. As PEP 20 says, "Readability counts".

...snip...

But most importantly: know when to be inconsistent -- sometimes the style
guide just doesn't apply. When in doubt, use your best judgment. Look
at other examples and decide what looks best. And don't hesitate to ask!



In your example, the first style is difficult to read wile the second
style is easy to read. You don't need any further justification for
preferring the latter.


Cheers,


Ryan


--
Ryan Kelly
http://www.rfk.id.au | This message is digitally signed. Please visit
(e-mail address removed) | http://www.rfk.id.au/ramblings/gpg/ for details


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)

iEYEABECAAYFAk0KmRoACgkQfI5S64uP50rpUACfVyFSBc46MjiBGxxRvlsZzE5h
7yAAoIp5zF5MaR39+3t8S/ESq8uRyrm1
=ojj2
-----END PGP SIGNATURE-----
 
I

Ian Kelly

return [x for x,y in
((bad1,some_bad_condition),(bad2,some_other_bad_condition),(bad3,yet_another_bad_condition),(good1,do_some_useful_stuff()
or True)) if x][0]

This doesn't work. do_some_usefull_stuff() gets called during the
tuple construction regardless of the conditions, not during the list
comprehension execution as you would want.

Here's my take on an unreadable one-liner:

return reduce(lambda x, y: (x or (y[0]() and y[1])),
[(some_bad_condition, bad1), (some_other_bad_condition, bad2),
(yet_another_bad_condition, bad3), (lambda: (do_some_useful_stuff() or
True), good1)], None)

This of course assumes that bad1, bad2, and bad3 all evaluate as true.

Cheers,
Ian
 
S

Steven D'Aprano

(This is mostly a style question, and perhaps one that has already been
discussed elsewhere. If so, a pointer to that discussion will be
appreciated!)

When I started learning Python, I wrote a lot of methods that looked
like this:

def myMethod(self, arg1, arg2):
if some_good_condition:
if some_other_good_condition:
if yet_another_good_condition:
do_some_useful_stuff()
exitCode = good1
else:
exitCode = bad3
else:
exitCode = bad2
else:
exitCode = bad1
return exitCode


It doesn't look like you were learning Python. It looks like you were
learning C with Python syntax :(

The above would be more Pythonically written as:


def myMethod(self, arg1, arg2):
if not some_good_condition:
raise SomeException("message")
if not some_other_good_condition:
raise SomeOtherException("another message")
if yet_another_good_condition:
do_some_useful_stuff()
else:
raise SomeThirdException("whatever")


using exceptions to communicate errors out-of-band. Since no return
result is needed, no explicit return is used and the method is the
closest thing to a procedure that Python can offer.

The problem with in-band transmission of errors is that they invite the
anti-pattern of this:

result = obj.myMethod(arg1, arg2)
if result == good1:
do_something_good()
elif result == bad1:
handle_error1()
elif result == bad2:
handle_error2()
elif result == bad3():
handle_error3()
else:
print "This can't ever happen, but if it does..."


which all too often becomes:

result = obj.myMethod(arg1, arg2)
if result == good1:
do_something_good()
else: # assume result is bad1
handle_error1()


or even:

who_cares = obj.myMethod(arg1, arg2)
do_something_good()


But lately I've been preferring this style:

def myMethod(self, arg1, arg2):
if some_bad_condition:
return bad1
elif some_other_bad_condition:
return bad2
elif yet_another_bad_condition:
return bad3
do_some_useful_stuff()
return good1

I like this style more, mostly because it eliminates a lot of
indentation.

Well, that's better, but still more like C rather than Python. Avoid the
anti-pattern of returning in-band error codes. In some languages, either
exceptions aren't available at all, or the overhead of them is so great
that for performance you have to avoid them, but Python is not one of
those languages.

In Python, exceptions are *the* primary way of communicating exceptional
cases such as (but not limited to) errors. I can only think of two, er,
exceptions to this rule: str.find() and some of the regular expression
methods.

However I recall one of my college CS courses stating that "one entry,
one exit" was a good way to write code, and this style has lots of
exits.

Functions always have one entry. The only way to have multiple entry
points is if the language allows you to GOTO into the middle of a
function, and Python sensibly does not allow this. The "one entry, one
exit" rule comes from the days when people would routinely write
spaghetti code, jumping into and out of blocks of code without using
functions at all.

If "one entry" is pointless (all functions have one entry!) then "one
exit" is actively harmful. It leads to adding unnecessary complexity to
functions, purely to meet the requirements of a rule invented to
discourage spaghetti code. This hides bugs and increases the maintenance
burden.

Don't get me wrong... spaghetti code is *bad*. But there are other ways
of writing bad code too, and hanging around inside a function long after
you've finished is also bad:

def function(arg):
done = False
do_something()
if some_condition:
result = "finished"
done = True
if not done:
do_something_else()
if another_condition:
result = "now we're finished"
done = True
if not done:
do_yet_more_work()
if third_condition:
result = "finished this time for sure"
done = True
if not done:
for i in range(1000000):
if not done:
do_something_small()
if yet_another_condition:
result = "finally done!"
done = True
return result

It's far more complicated than it need be, and does *lots* of unnecessary
work. This can be written more simply and efficiently as:

def function(arg):
do_something()
if some_condition:
return "finished"
do_something_else()
if another_condition:
return "now we're finished"
do_yet_more_work()
if third_condition:
return "finished this time for sure"
for i in range(1000000):
do_something_small()
if yet_another_condition:
return "finally done!"


Over 40% of the code in the first version is scaffolding to support
skipping the rest of the function body once you have a result. It
needlessly performs all one million iterations of the loop, even if the
final result was calculated on the first iteration (loops also should
have "one entry, one exit"). There's not one good thing that can be said
about it except that it has "one exit", and that is only a good thing
compared to the Bad Old Days of writing spaghetti code with GOTO.
 
A

alex23

John Gordon said:
But lately I've been preferring this style:

  def myMethod(self, arg1, arg2):

    if some_bad_condition:
      return bad1

    elif some_other_bad_condition:
      return bad2

    elif yet_another_bad_condition:
      return bad3

    do_some_useful_stuff()
    return good1

For more than 2 tests in a function like this, I'd probably use
dictionary dispatch:

def myMethod(self, arg1, arg2):
branches = dict(
cond1: bad1,
cond2: bad2,
cond3: bad3
)

cond = <expression>

if cond == cond_good:
do_some_useful_stuff()
exitCode = good1
else:
exitCode = branches[cond]

return exitCode
 
C

Carl Banks

"one entry, one exit" has its good points, but it's *way* overquoted and
overused.

Do you raise any exceptions? Do you call any functions that might raise
exceptions?  If so, you've got multiple exit points already.

I think this style a lot more important in a language like C where you
have to be super-careful about cleaning up after yourself.  The single
exit point makes it easier to verify that all cleanup tasks have been
performed.  Assuming you're using "with" or "try-finally" then you just
don't need such guarantees in python.

Even without the cleanup issue, sometimes you want to edit a function
to affect all return values somehow. If you have a single exit point
you just make the change there; if you have mulitple you have to hunt
them down and change all of them--if you remember to. I just got bit
by that one.

It's a trade-off. Readability and/or conciseness versus error
robustness. I tend to go for the former but mileage varies.


Carl Banks
 
S

Steven D'Aprano

Even without the cleanup issue, sometimes you want to edit a function to
affect all return values somehow. If you have a single exit point you
just make the change there; if you have mulitple you have to hunt them
down and change all of them--if you remember to. I just got bit by that
one.


If your function has so many exit points that you can miss some of them
while editing, your function is too big, does too much, or both. Refactor
and simplify.

Or wrap the function in a decorator:

def affect_all_return_values(func):
@functools.wraps(func)
def inner(*args, **kwargs):
result = func(*args, **kwargs)
do_something_to(result)
return result
return inner

@affect_all_return_values
def my_big_complicated_function(args):
do_something_with_many_exit_points()
 
J

Jean-Michel Pichavant

John said:
(This is mostly a style question, and perhaps one that has already been
discussed elsewhere. If so, a pointer to that discussion will be
appreciated!)

When I started learning Python, I wrote a lot of methods that looked like
this:


def myMethod(self, arg1, arg2):

if some_good_condition:

if some_other_good_condition:

if yet_another_good_condition:

do_some_useful_stuff()
exitCode = good1

else:
exitCode = bad3

else:
exitCode = bad2

else:
exitCode = bad1

return exitCode


But lately I've been preferring this style:


def myMethod(self, arg1, arg2):

if some_bad_condition:
return bad1

elif some_other_bad_condition:
return bad2

elif yet_another_bad_condition:
return bad3

do_some_useful_stuff()
return good1

I like this style more, mostly because it eliminates a lot of indentation.

However I recall one of my college CS courses stating that "one entry,
one exit" was a good way to write code, and this style has lots of exits.

Are there any concrete advantages of one style over the other?

Thanks.

What about,


def myMethod():
for condition, exitCode in [
(cond1, 'error1'),
(cond2, 'very bad error'),
]:
if not condition:
break
else:
do_some_usefull_stuff() # executed only if the we never hit the
break statement.
exitCode = good1

return exitCode

This version uses the 'for ... else' statement. You can easily add
conditions by simply adding a line in the list, that's it.
Note that this code uses a shadow declaration of exitCode in the for
loop. If you're not comfortable with that, you'll have to use a properly
'declared' variable retCode and write retCode = exitCode before
breaking. Actually I would advise to do so.

JM
 
D

David Robinow

On Thu, Dec 16, 2010 at 6:51 PM, Steven D'Aprano
Functions always have one entry. The only way to have multiple entry
points is if the language allows you to GOTO into the middle of a
function, and Python sensibly does not allow this. The "one entry, one
exit" rule comes from the days when people would routinely write
spaghetti code, jumping into and out of blocks of code without using
functions at all.
Only 99.7% true. Fortran still allows the appalling ENTRY statement.
 
P

Paul Rubin

Jean-Michel Pichavant said:
What about,

def myMethod():
for condition, exitCode in [
(cond1, 'error1'),
(cond2, 'very bad error'),
]:
if not condition:
break
else:
do_some_usefull_stuff() # executed only if the we never hit the
break statement.
exitCode = good1

return exitCode

This version uses the 'for ... else' statement.

For..else always has seemed ugly and confusing to me, as does that thing
of using the captured loop indexes after the loop finishes. I'd prefer
a more functional style (untested):

def myMethod():
def success():
do_some_usefull_stuff()
return good1
cond_table = [
(cond1, lambda: 'error1'),
(cond2, lambda: 'very bad error'),
(True, success)
]
func = next(f for c,f in cond_table if c)
return func()

This uses the next() builtin from Python 2.6. You could make it more
concise:

def myMethod():
cond_table = [
(cond1, lambda: 'error1'),
(cond2, lambda: 'very bad error'),
(True, lambda: (do_some_usefull_stuff(), good1)[1])
]
return next(f for c,f in cond_table if c)()
 
G

Grant Edwards

The advantage in latter case is fewer operations, because you can
skip the assignments, and it is more readable.

The "one entry, one exit" is an advice. Not a law.
Your code is OK.

As long as it works ;-)

Even that last bit isn't that important.

Give me code that's easy-to-read and doesn't work rather code that
works and can't be read any day.
 
G

Grant Edwards

It doesn't look like you were learning Python. It looks like you were
learning C with Python syntax :(

Let's not blame C for bad program structure. No good C programmer
would use that construct either.

One choice in C would look like this:

if (some_condition)
return code1;

if (other_condition)
return code2;

if (condition3)
return code3;

//do whatever work really needs to be done here.

return successCode;

Or, if there's cleanup that needs to be done, then you "raise a an
exception":


if (condition1)
{
ret = code1;
goto errexit;
}

if (condition2)
{
ret = code2;
goto errexit;
}

if (condition3)
{
ret = code3;
goto errexit;
}


// do the normal bit of work


errexit:

//cleanup

return ret;
 
K

Kev Dwyer

(This is mostly a style question, and perhaps one that has already been
discussed elsewhere. If so, a pointer to that discussion will be
appreciated!)

When I started learning Python, I wrote a lot of methods that looked
like this:


def myMethod(self, arg1, arg2):

if some_good_condition:

if some_other_good_condition:

if yet_another_good_condition:

do_some_useful_stuff()
exitCode = good1

else:
exitCode = bad3

else:
exitCode = bad2

else:
exitCode = bad1

return exitCode

Another way to look at this is as question of object-oriented style, as you
are using a method in your example...

Arguably, rather than branching your code based on the arguments being
passed to your method, you can embody the required behaviour in subclasses
of your class, and at runtime just use an object that "does the right
thing". Of course, you end up writing the same branching in some factory
object instead, but at least it isn't cluttering up your business logic
any longer. Trying to write an OO-style program without using any if
statements in the business logic can be an interesting exercise, albeit
not a terribly realistic one.

Apologies if your choice of a method for your example was entirely
incidental to your question :)

Kev
 
S

Steven D'Aprano

Give me code that's easy-to-read and doesn't work rather code that works
and can't be read any day.

Well, in that case, you'll love my new operating system, written in 100%
pure Python:

[start code]
print("this is an operating system")
[end code]

I expect it to rapidly make Windows, Linux and OS-X all obsolete. Bill
Gates and Steve Jobs, look out!

*grin*


Surely your attitude towards usefulness vs. readability will depend
strongly on whether you are intending to *use* the code, or *maintain*
the code?
 
F

Francesco

Don't get me wrong... spaghetti code is*bad*. But there are other ways
of writing bad code too, and hanging around inside a function long after
you've finished is also bad:

def function(arg):
done = False
do_something()
if some_condition:
result = "finished"
done = True
if not done:
do_something_else()
if another_condition:
result = "now we're finished"
done = True
if not done:
do_yet_more_work()
if third_condition:
result = "finished this time for sure"
done = True
if not done:
for i in range(1000000):
if not done:
do_something_small()
if yet_another_condition:
result = "finally done!"
done = True
return result

It's far more complicated than it need be, and does*lots* of unnecessary
work. This can be written more simply and efficiently as:

def function(arg):
do_something()
if some_condition:
return "finished"
do_something_else()
if another_condition:
return "now we're finished"
do_yet_more_work()
if third_condition:
return "finished this time for sure"
for i in range(1000000):
do_something_small()
if yet_another_condition:
return "finally done!"

I agree to your point, but I'm afraid you chose a wrong example (AFAIK, and that's not much).
Sure, the second version of function(arg) is much more readable, but why do you think the first one would do "*lots* of unnecessary
work"?
All the overhead in that function would be:
if some_condition, three IF tests, and you know that's NOT a lot!
if no conditions were met, (worst case) the first version would return an exception (unless result was globally defined) while
the second would happily return None. Apart from this, the overhead in the first one would amount to one million IF tests, again not
a lot these days. I don't think I would rewrite that function, if I found it written in the first way...
I don't mean that the fist example is better, just I'm sure you could imagine a more compelling proof of your concept.
Maybe there's something I don't know... in that case, please enlighten me!

Francesco
 
S

Steven D'Aprano

On Sat, 18 Dec 2010 12:29:31 +0100, Francesco wrote:

[...]
I agree to your point, but I'm afraid you chose a wrong example (AFAIK,
and that's not much). Sure, the second version of function(arg) is much
more readable, but why do you think the first one would do "*lots* of
unnecessary work"?
All the overhead in that function would be:
if some_condition, three IF tests, and you know that's NOT a lot!

Well, let's try it with a working (albeit contrived) example. This is
just an example -- obviously I wouldn't write the function like this in
real life, I'd use a while loop, but to illustrate the issue it will do.

def func1(n):
result = -1
done = False
n = (n+1)//2
if n%2 == 1:
result = n
done = True
if not done:
n = (n+1)//2
if n%2 == 1:
result = n
done = True
if not done:
n = (n+1)//2
if n%2 == 1:
result = n
done = True
if not done:
for i in range(1000000):
if not done:
n = (n+1)//2
if n%2 == 1:
result = n
done = True
return result


def func2(n):
n = (n+1)//2
if n%2 == 1:
return n
n = (n+1)//2
if n%2 == 1:
return n
n = (n+1)//2
if n%2 == 1:
return n
for i in range(1000000):
n = (n+1)//2
if n%2 == 1:
return n
return -1


Not only is the second far more readable that the first, but it's also
significantly faster:
4.530779838562012

The first function does approximately 60% more work than the first, all
of it unnecessary overhead.
 

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,743
Messages
2,569,478
Members
44,899
Latest member
RodneyMcAu

Latest Threads

Top