If/then style question

Discussion in 'Python' started by John Gordon, Dec 16, 2010.

  1. John Gordon

    John Gordon Guest

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

    --
    John Gordon A is for Amy, who fell down the stairs
    B is for Basil, assaulted by bears
    -- Edward Gorey, "The Gashlycrumb Tinies"
    John Gordon, Dec 16, 2010
    #1
    1. Advertising

  2. John Gordon

    Tim Harig Guest

    On 2010-12-16, John Gordon <> wrote:
    > 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.
    Tim Harig, Dec 16, 2010
    #2
    1. Advertising

  3. John Gordon

    Ethan Furman Guest

    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.


    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~
    Ethan Furman, Dec 16, 2010
    #3
  4. On 2010-12-16, 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.


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

    --
    Grant Edwards grant.b.edwards Yow! I want a WESSON OIL
    at lease!!
    gmail.com
    Grant Edwards, Dec 16, 2010
    #4
  5. 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 :-(
    Stefan Sonnenberg-Carstens, Dec 16, 2010
    #5
  6. John Gordon

    Ryan Kelly Guest

    On Thu, 2010-12-16 at 21:49 +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
    >
    >
    > 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
    | 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-----
    Ryan Kelly, Dec 16, 2010
    #6
  7. John Gordon

    Ian Kelly Guest

    On Thu, Dec 16, 2010 at 3:41 PM, Stefan Sonnenberg-Carstens
    <> wrote:
    > 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
    Ian Kelly, Dec 16, 2010
    #7
  8. 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 :(

    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.



    --
    Steven
    Steven D'Aprano, Dec 16, 2010
    #8
  9. John Gordon

    alex23 Guest

    John Gordon <> wrote:
    > 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
    alex23, Dec 17, 2010
    #9
  10. John Gordon

    Carl Banks Guest

    On Dec 16, 2:56 pm, Ryan Kelly <> wrote:
    > On Thu, 2010-12-16 at 21:49 +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

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


    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
    Carl Banks, Dec 17, 2010
    #10
  11. 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
    Steven D'Aprano, Dec 17, 2010
    #11
  12. 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
    Jean-Michel Pichavant, Dec 17, 2010
    #12
  13. On Thu, Dec 16, 2010 at 6:51 PM, Steven D'Aprano
    <> 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.
    David Robinow, Dec 17, 2010
    #13
  14. John Gordon

    Paul Rubin Guest

    Jean-Michel Pichavant <> 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)()
    Paul Rubin, Dec 17, 2010
    #14
  15. On 2010-12-16, Stefan Sonnenberg-Carstens <> 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?
    Grant Edwards, Dec 17, 2010
    #15
  16. On 2010-12-16, Steven D'Aprano <> 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
    Grant Edwards, Dec 17, 2010
    #16
  17. John Gordon

    Kev Dwyer Guest

    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
    Kev Dwyer, Dec 17, 2010
    #17
  18. 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
    Steven D'Aprano, Dec 18, 2010
    #18
  19. John Gordon

    Francesco Guest

    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
    Francesco, Dec 18, 2010
    #19
  20. 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
    Steven D'Aprano, Dec 19, 2010
    #20
    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. =?Utf-8?B?TWlrZQ==?=

    greater then / less then

    =?Utf-8?B?TWlrZQ==?=, Nov 4, 2004, in forum: ASP .Net
    Replies:
    2
    Views:
    1,559
    Kevin Spencer
    Nov 4, 2004
  2. JosephByrns

    Slow, then quick then slow

    JosephByrns, Jul 10, 2006, in forum: ASP .Net
    Replies:
    4
    Views:
    2,466
    codezilla94
    Nov 13, 2007
  3. Mufasa
    Replies:
    7
    Views:
    400
    Alexey Smirnov
    Sep 4, 2007
  4. Ken Varn
    Replies:
    0
    Views:
    413
    Ken Varn
    Apr 26, 2004
  5. Replies:
    0
    Views:
    252
Loading...

Share This Page