cascading python executions only if return code is 0

F

Frank Cui

hey guys,
I have a requirement where I need to sequentially execute a bunch of executions, each execution has a return code. the followed executions should only be executed if the return code is 0. is there a cleaner or more pythonicway to do this other than the following ?
if a() == 0: if b() == 0: c()
Thanks for your input.
frank
 
R

Roy Smith

Frank Cui said:
hey guys,
I have a requirement where I need to sequentially execute a bunch of
executions, each execution has a return code. the followed executions should
only be executed if the return code is 0. is there a cleaner or more pythonic
way to do this other than the following ?
if a() == 0: if b() == 0: c()
Thanks for your input.
frank

Yup! Just do:

a() or b() or c()

The "or" operation has what's known as "short-circuit" semantics. That
means, if the first operand is true, it doesn't evaluate the second
operand. Just make sure that a(), b(), and c() all return something
which is true if they succeed and false otherwise.
 
M

Mark Lawrence

Yup! Just do:

a() or b() or c()

The "or" operation has what's known as "short-circuit" semantics. That
means, if the first operand is true, it doesn't evaluate the second
operand. Just make sure that a(), b(), and c() all return something
which is true if they succeed and false otherwise.

Really? :)
 
F

Frank Cui

To: (e-mail address removed)
From: (e-mail address removed)
Subject: Re: cascading python executions only if return code is 0
Date: Sun, 22 Dec 2013 14:49:43 -0500



Frank, welcome to the group. Common convention is to put your response
below the exiting message, so that the conversation continues down the page.

(See my answer below... :)


The most Python-natural way to deal with your problem would be to have
these functions not return status codes at all. Instead, have them
raise an exception if something goes wrong. Then you can invoke them
most naturally:

a()
b()
c()

Execution will continue as long as no exceptions are raised. If you
need to deal with the failure case also, then:

try:
a()
b()
c()
except Exception as e:
# do something here

Depending on how you want to deal with failures, you'd probably use your
own subclass of Exception, but this is the general idea.

Return codes can be awkward, especially in Python which has exception
integrated so fully into the language, library, and culture.

Thanks for informing the rules.
 
N

Ned Batchelder

sorry, but what if I need to have different parameters in these functions ?

Frank, welcome to the group. Common convention is to put your response
below the exiting message, so that the conversation continues down the page.

(See my answer below... :)

The most Python-natural way to deal with your problem would be to have
these functions not return status codes at all. Instead, have them
raise an exception if something goes wrong. Then you can invoke them
most naturally:

a()
b()
c()

Execution will continue as long as no exceptions are raised. If you
need to deal with the failure case also, then:

try:
a()
b()
c()
except Exception as e:
# do something here

Depending on how you want to deal with failures, you'd probably use your
own subclass of Exception, but this is the general idea.

Return codes can be awkward, especially in Python which has exception
integrated so fully into the language, library, and culture.
 
R

Roy Smith

Mark Lawrence said:

I believe what Mark is so elegantly trying to say is, "Roy is a dufus
and got that backwards". You need to return something which is false to
make the next one in the chain get executed.

$ cat or.py
def a():
print "a"
return 0

def b():
print "b"
return 1

def c():
print "c"
return 0

a() or b() or c()


$ python or.py
a
b
 
C

Cameron Simpson

I believe what Mark is so elegantly trying to say is, "Roy is a dufus
and got that backwards". You need to return something which is false to
make the next one in the chain get executed.

$ cat or.py
def a():
print "a"
return 0

def b():
print "b"
return 1

def c():
print "c"
return 0

a() or b() or c()


$ python or.py
a
b

Yeah.

Roy's code _depends_ upon the return value being equivalent to False.

A better approach would be:

a() == 0 and b() == 1 and c() == 0

i.e. to explicitly check each return code against the expected/desired
value instead of relying on some flukey coincidental property of
the (arbitrary) numeric value returned.

Better still is the suggestion elsewhere in the thread to make the functions
raise exceptions on error instead of returning a number.

Cheers,
 
R

Roy Smith

Cameron Simpson said:
Roy's code _depends_ upon the return value being equivalent to False.

Yes. You view this as a flaw. I view it as a feature :)
A better approach would be:

a() == 0 and b() == 1 and c() == 0

i.e. to explicitly check each return code against the expected/desired
value instead of relying on some flukey coincidental property of
the (arbitrary) numeric value returned.

You're assuming it's arbitrary. I'm saying do it that way by design.
Better still is the suggestion elsewhere in the thread to make the functions
raise exceptions on error instead of returning a number.

Possibly. But, I think of exceptions as indicating that something went
wrong. There's two possible things the OP was trying to do here:

1) He intends that all of the functions get run, but each one can only
get run if all the ones before it succeed. In that case, I agree that
the exception pattern makes sense.

2) He intends that each of the functions gets tried, and the first one
that can return a value wins. If that's the case, the "or" chaining
seems more natural.
 
C

Cameron Simpson

Yes. You view this as a flaw. I view it as a feature :)

When I write functions which return a boolean indicating success/failure,
I try to make that boolean be "true" for success.

Now, I do take the point that these functions seem to take the
unix-exit-code convention that zero is success (leaving the many
values of "non-zero" to indicate flavours of failure as desired,
as we have many types of exceptions).

Or, possibly, that a non-zero return indicates the number of errors
encountered; I do that myself for things like option or file parsing,
where I explicitly want to parse of much of a command line (or
whatever) as possible before rejecting things; few things annoy me
as much as a command that barfs about the first usage error and
aborts instead of barfing multiple times and aborting. Unless it
is a command that does the same and then fails to recite a usage
message after the barf. (Yes, almost every GNU command on the planet:
I'm looking at you!)

However, in this count-of-errors scenario I tend to try to return
a list of errors, not a count.

But regardless, I consider code that goes:

a() or b() or c()

as a test for _success_ of a(), b() and c() in succession to be
misleading at best. When I write that above incantation it is a
test for failure: try this, or try that, or finally try this other
thing.
You're assuming it's arbitrary. I'm saying do it that way by design.

The counter example the above is based upon deliberately returned
1 for success from b(), IIRC. Different design.

The OP was unclear about his/her design rationale.
Possibly. But, I think of exceptions as indicating that something went
wrong.

I think of failure as "something went wrong".
Yes, I'll grant there are shades of intent here.
There's two possible things the OP was trying to do here:

1) He intends that all of the functions get run, but each one can only
get run if all the ones before it succeed. In that case, I agree that
the exception pattern makes sense.

His cascading if-statement in the OP suggested this to me.
2) He intends that each of the functions gets tried, and the first one
that can return a value wins. If that's the case, the "or" chaining
seems more natural.

I'm pretty sure that wasn't his intent, again based on my recollection
of the OP. But I still dislike "a() or b() or c()" as a test for
chained success; I think it is a bad idiom.

Cheers,
 
F

Frank Cui

Date: Sun, 22 Dec 2013 14:27:35 -0800
Subject: Re: cascading python executions only if return code is 0
From: (e-mail address removed)
To: (e-mail address removed)



Hello Frank.

I kindly request that you be more specific when asking
questions. Both your question and your example code contain
too many ambiguities.

I'm still not sure what exact outcome you wish to achieve,
the only certainty is that you wish to perform a linear
execution of "N" members with later executions being affected
by earlier executions.

Whether you want executions to proceed on failure or proceed
on success is unclear. Here are a few explicit pseudo code
examples that would have removed all ambiguities:

if fails(a()):
if fails(b()):
c()

if succeeds(a()):
if succeeds(b()):
c()

Or if you prefer a purely OOP approach:

a.foo()
b.foo()
if a.failed:
if b.failed:
c.foo()

a.foo()
b.foo()
if a.succeeded:
if b.succeeded:
c.foo()

or you could simplify using a logical one liner:

if !a() and !b() then c()
if a() and b() then c()

Of course you could use the "all" function

if all(a(), b()):
c()
if not any(a(), b()):
c()

But this "all" depends whether you're testing for success or
testing for failure, and that point is a distant third from
my desperate need of understanding your semantics of "what"
values are *true* and "what" values are *false*.

I think (sadly) more time is spent attempting to interpret
what an OP is asking rather than attempting to provide a
solution to the problem the OP is suffering, and whilst any
problem solving adventure is likely to improve our
intelligence, fumbling about attempting to decode
ambiguities is indeed time that could have been better spent
IF ONLY the speaker (or writer) had put a small bit more
effort into the question.

Look Frank, nobody is perfect, we all need to improve our
skills here or there. So don't be offended that my
statements are, well,... "frank".

Hi Rick,
Thanks for pointing out. I accept your advice and will try to make the questions clearer and more straightforward to interpretate . I already took thesuggestion of using exception-based handling over the return code.
As to testing whether the previous function fails or succeeds, this doesn't really matter in the sense that I already mentioned a return code of 0.
ThanksFrank
 
R

Rick Johnson

I have a requirement where I need to sequentially execute
a bunch of executions, each execution has a return code.
the followed executions should only be executed if the
return code is 0. is there a cleaner or more pythonic way
to do this other than the following ?

if a() == 0:
if b() == 0:
c()

Hello Frank.

I kindly request that you be more specific when asking
questions. Both your question and your example code contain
too many ambiguities.

I'm still not sure what exact outcome you wish to achieve,
the only certainty is that you wish to perform a linear
execution of "N" members with later executions being affected
by earlier executions.

Whether you want executions to proceed on failure or proceed
on success is unclear. Here are a few explicit pseudo code
examples that would have removed all ambiguities:

if fails(a()):
if fails(b()):
c()

if succeeds(a()):
if succeeds(b()):
c()

Or if you prefer a purely OOP approach:

a.foo()
b.foo()
if a.failed:
if b.failed:
c.foo()

a.foo()
b.foo()
if a.succeeded:
if b.succeeded:
c.foo()

or you could simplify using a logical one liner:

if !a() and !b() then c()
if a() and b() then c()

Of course you could use the "all" function

if all(a(), b()):
c()
if not any(a(), b()):
c()

But this "all" depends whether you're testing for success or
testing for failure, and that point is a distant third from
my desperate need of understanding your semantics of "what"
values are *true* and "what" values are *false*.

I think (sadly) more time is spent attempting to interpret
what an OP is asking rather than attempting to provide a
solution to the problem the OP is suffering, and whilst any
problem solving adventure is likely to improve our
intelligence, fumbling about attempting to decode
ambiguities is indeed time that could have been better spent
IF ONLY the speaker (or writer) had put a small bit more
effort into the question.

Look Frank, nobody is perfect, we all need to improve our
skills here or there. So don't be offended that my
statements are, well,... "frank".
 
C

Chris Angelico

Thanks for pointing out. I accept your advice and will try to make the
questions clearer and more straightforward to interpretate . I already took
the suggestion of using exception-based handling over the return code.

As to testing whether the previous function fails or succeeds, this doesn't
really matter in the sense that I already mentioned a return code of 0.

Ranting Rick is one of the list's resident trolls. Don't take it amiss
that he turned his flamethrowers on you. :)

ChrisA
 
M

Mark Lawrence

Ranting Rick is one of the list's resident trolls. Don't take it amiss
that he turned his flamethrowers on you. :)

ChrisA

Given that Frank originally stated this

"
I have a requirement where I need to sequentially execute a bunch of
executions, each execution has a return code. the followed executions
should only be executed if the return code is 0. is there a cleaner or
more pythonic way to do this other than the following ?

if a() == 0:
if b() == 0:
c()
"

I can only see one way that you can possibly intepret it. Perhaps rr
was dashing off a reply on a mobile device while doing a home run, hence
wasn't concentrating on what Frank had actually said?
 
R

Rick Johnson

On 22/12/2013 22:51, Chris Angelico wrote:
if a() == 0:

if b() == 0:

c()

I can only see one way that you can possibly intepret it.

Hmm, I guess i should not assume color vision to be ubiquitous.

The level of abstraction of that code sample is so high that
no one should assume anything from reading it. The only fact
we CAN be sure of is that IF a() equals 0 and b() equals 0
then c will execute. But that fact gives us no insight into
Frank's *real* source code, not to mention his mind.

Sure. We always want folks to trim example code to it's most
relevant parts before presenting the code on this list, but
just as ten thousands lines of code is useless, so too is 4
lines. It's not the number of lines that matter so much as
the context those line provide to the problem at hand.

If you take the small code example literally, then why does
Frank even need to ask a question about what will happen
when he could simply run the code and observe the results.

Here is the most simplified example of Franks code:

if 0 == 0:
if 0 == 0:
do_something()

This code is so *confined* as to be useless for
generalities. In fact, the only reason to ask a question
about such simplified code is to understand the execution
rules OR syntax of Python source code. Here are some
concrete facts about those three lines:

1. test one will ALWAYS eval true, and proceed to test two

2. test two will ALWAYS eval true, then execute "do_something"

3. what happens after that is undefined

These are fundamental behaviors of Python conditionals,
operators, callables, etc... which we *already* understand
quite completely. But these fundamentals will not help us
understand Frank's problem.

What we need is help Frank is *CONTEXT*.

Now, even though Frank's code offers a small increment in
complexity over my simplified form, it offers *zero*
context of that complexity:

1. test one will evaluate true or false, then proceed (or
not, depending on the return value of "a") to test two

2. test two will evaluate true or false, then proceed (or
not, depending on the return value of "b") to execute the
body

3. What happens in a, b, and c is undefined.

The problem is we have no idea what happens in a,b,and c...
but those facts may not matter, what does matter is what a
return value of zero *means* to Frank, and without an
understanding of these "Frank semantics", how could we
possibly extrapolate a solution to such ambiguous questions?

You see, there exist no rules in Python for what a return
value of 0 should mean. Should it mean "success"? Should it
mean "failure"? Should it mean "eat pasta quickly"? Well it
could mean all those things to different people.

Since function return values are defined by programmers,
*ONLY* the programmer himself-- or those familiar with the
entire code base --can resolve the ambiguities with any
degree of precision.

Even IF you are presented with a small snippet of code that
include the identifier for the return value, you still
cannot be certain that extrapolating meaning from an
identifier *alone* will give you the insight to remove all
ambiguity.

For example, let's say the return value is "proceed" and
attached to name spelled "flag". Do you really think you can
extrapolate the purpose of that value being returned?

"proceed" with what?

Proceed counting unicorns?
Proceed raising exceptions?
Proceed extrapolating to infinity and beyond?!

You can guess, but that is all. Which brings us full circle
to the problem at hand.
 
S

Steven D'Aprano

Frank, welcome to the group. Common convention is to put your response
below the exiting message, so that the conversation continues down the
page.

Ideally responses should be *interleaved* between parts of the quoted
message, rather than just dumped at the end. The idea is that it's a
conversation:

Fred said:
-> How do I exfoliate my monkey?

Get a friend to hold it down while you rub it all over
with Extra Strength Monkey Exfoliating Cream. When you
are done, give it a banana as a reward.

-> Once I have my exfoliated monkey, how do I convince
-> others that it is my child?

Dress it in children's clothes, and make sure you tell
people that he or she has a very sensitive artistic
personality, and that is why you allow it to climb the
walls.


sort of thing. This also gives the writer the opportunity to trim out
parts of the irrelevant text which is no longer relevant to the
conversation, although it is considered polite to give some indication,
usually [snip] or [...], that you have done so.

See Wikipedia's article on posting styles for more information:

http://en.wikipedia.org/wiki/Posting_styles
 
S

Steven D'Aprano

hey guys,
I have a requirement where I need to sequentially execute a bunch of
executions, each execution has a return code. the followed executions
should only be executed if the return code is 0. is there a cleaner or
more pythonic way to do this other than the following ?
if a() == 0:
if b() == 0:
c()

I don't believe there is a clean way to deal with error return codes in
*any* language, but certainly not Python.

If you only have a few such functions, you can mis-use boolean operators
to get the result you want, at the cost of misleading code:

a() == 0 and b() == 0 and c()

But that's pretty horrible code, because it looks like you're testing a
condition when you're really trying to run a, b, c etc. for their side-
effects. Code that relies on side-effects is usually a sign of poor
design.

A better alternative is to modify the functions so that instead of
returning 0 on failure and (I'm guessing here) None on success, they
raise an exception instead. Instead of:

def a():
do_this()
do_that()
if condition:
return 0
do_something_else()


you re-write it as:

def a():
do_this()
do_that()
if condition:
raise SomeError("something broke")
do_something_else()


Then you can do this:


try:
a()
b()
c()
except SomeError:
handle_error()


What if you can't edit all the various a, b, c functions because other
parts of your code rely on them returning an error result? That's easy,
you just need an adaptor:

import functools

def adapt(func):
@functools.wraps(func)
def inner(*args, **kwargs):
result = func(*args, **kwargs)
if result == 0:
raise SomeError("some message")
return inner


try:
adapt(a)()
adapt(b)()
adapt(c)()
except SomeError:
handle_error()


Another option really only applies if all the functions use the same set
of arguments.

def chain(list_of_functions, *args, **kwargs):
for func in list_of_functions:
result = func(*args, **kwargs)
if result == 0:
break
 
G

Gregory Ewing

Frank said:
the page.

Thanks for informing the rules.

He forgot to mention the most important rule,
which is:

DON'T quote the entire message you're replying to!

Only quote small pieces of it, like I did above,
just enough to establish context. Then put your
reply to that point under the quoted text.
 
R

Roy Smith

Steven D'Aprano said:
Code that relies on side-effects is usually a sign of poor
design.

I don't understand what you're trying to say there. A bit later in your
post, you wrote:

try:
a()
b()
c()
except SomeError:
handle_error()

Clearly, since the return values of a(), b(), and c() aren't saved, the
only reason they're getting called is for their side effects. And I
don't see anything wrong with that.

BTW, there's a pattern we use a bunch in the Songza server code, which
is sort of this, but in reverse. We'll have a bunch of possible ways to
do something (strategies, to use the pattern vernacular), and want to
try them all in order until we find one which works. So, for example:

classes = [ClientDebugPicker,
StatefulSongPicker,
SWS_SequentialSongPicker,
StandardSongPicker]
for cls in classes:
picker = cls.create(radio_session, station, artist)
if picker:
return picker
else:
assert 0, "can't create picker (classes = %s)" % classes

Each SongPicker subclass encapsulates some logic for how to pick the
next song. It can also decide if the strategy it implements is
appropriate for the particular request; create() either returns an
instance of the class, or None. Returning None means, "I'm not the
right picker for this request; try the next one and see what he says".
 
C

Chris Angelico

Each SongPicker subclass encapsulates some logic for how to pick the
next song. It can also decide if the strategy it implements is
appropriate for the particular request; create() either returns an
instance of the class, or None. Returning None means, "I'm not the
right picker for this request; try the next one and see what he says".

But in that instance, the picker has done nothing. The main effect is
to return a value, and since it returned None, you go on to do
something else.

This looks fine:

foo = a() or b() or c()

And it also looks like it would be safe to drop one of the calls if
you know it won't succeed:

if no_way_that_b_will_work:
foo = a() or c()

The point about side effects is that b() still has to be called, here,
and the original statement doesn't make that clear. When you call a
function and ignore its return value, you're clearly doing it for its
side effects. Imagine this:

width_required = max(len(foo),len(bar),len(quux))

Any sane reader is going to assume that the length checks aren't going
to have side effects... but they could! Imagine if testing the length
of something forced it to be loaded into memory, thus triggering any
exception that would otherwise not be triggered until the thing got
loaded much later. Vaguely plausible, but bad design because it's
extremely unclear.

ChrisA
 

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

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,061
Latest member
KetonaraKeto

Latest Threads

Top