Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Python > If/then style question

Reply
Thread Tools

If/then style question

 
 
Steven D'Aprano
Guest
Posts: n/a
 
      12-17-2010
On Thu, 16 Dec 2010 20:32:29 -0800, Carl Banks wrote:

> 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()



--
Steven


 
Reply With Quote
 
 
 
 
Jean-Michel Pichavant
Guest
Posts: n/a
 
      12-17-2010
John Gordon wrote:
> (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
 
Reply With Quote
 
 
 
 
David Robinow
Guest
Posts: n/a
 
      12-17-2010
On Thu, Dec 16, 2010 at 6:51 PM, Steven D'Aprano
<(E-Mail Removed)> wrote:
....
> 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.
 
Reply With Quote
 
Paul Rubin
Guest
Posts: n/a
 
      12-17-2010
Jean-Michel Pichavant <(E-Mail Removed)> writes:
> 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)()
 
Reply With Quote
 
Grant Edwards
Guest
Posts: n/a
 
      12-17-2010
On 2010-12-16, Stefan Sonnenberg-Carstens <(E-Mail Removed)> wrote:

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


--
Grant Edwards grant.b.edwards Yow! What's the MATTER
at Sid? ... Is your BEVERAGE
gmail.com unsatisfactory?
 
Reply With Quote
 
Grant Edwards
Guest
Posts: n/a
 
      12-17-2010
On 2010-12-16, Steven D'Aprano <(E-Mail Removed)> wrote:
> On Thu, 16 Dec 2010 21:49:07 +0000, John Gordon wrote:
>
>> (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


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;


--
Grant Edwards grant.b.edwards Yow! Awright, which one of
at you hid my PENIS ENVY?
gmail.com
 
Reply With Quote
 
Kev Dwyer
Guest
Posts: n/a
 
      12-17-2010
On Thu, 16 Dec 2010 21:49:07 +0000, John Gordon wrote:

> (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

 
Reply With Quote
 
Steven D'Aprano
Guest
Posts: n/a
 
      12-18-2010
On Fri, 17 Dec 2010 17:26:08 +0000, Grant Edwards wrote:

> 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?



--
Steven
 
Reply With Quote
 
Francesco
Guest
Posts: n/a
 
      12-18-2010
On 17/12/2010 0.51, Steven D'Aprano wrote:
> 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
 
Reply With Quote
 
Steven D'Aprano
Guest
Posts: n/a
 
      12-19-2010
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:

>>> from timeit import Timer
>>> t1 = Timer('for i in range(20): x = func1(i)',

.... 'from __main__ import func1')
>>> t2 = Timer('for i in range(20): x = func2(i)',

.... 'from __main__ import func2')
>>> min(t1.repeat(number=10, repeat=5))

7.3219029903411865
>>> min(t2.repeat(number=10, repeat=5))

4.530779838562012

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



--
Steven
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
DataGrid header style inconsistent with sortable column style cedoucette@alum.rpi.edu ASP .Net 0 10-14-2005 12:13 AM
All style tags after the first 30 style tags on an HTML page are not applied in Internet Explorer Rob Nicholson ASP .Net 3 05-28-2005 03:11 PM
Need help with Style conversion from Style object to Style key/value collection. Ken Varn ASP .Net Building Controls 0 04-26-2004 07:06 PM
Javascript Style Switcher that remebers current site style in use Hardeep Rakhra HTML 8 01-15-2004 08:00 PM
Style sheets, include one style within another (not inheritance) foldface@yahoo.co.uk HTML 1 11-24-2003 01:37 PM



Advertisments