In your example, it doesn't appear that the later tries really need to be nested inside the earlier ones. You're basically just making three separate attempts. They have an order, but they don't have to be nested. So you can just do something like
grokster = None
try: # is x referring to a snood?
s = makeSnood(x)
grokster = s.snoodidle()
except ValueError:
pass
if grokster is None:
try: # is x referring to a trund?
t = makeTrund(x)
grokster = t.trundle()
except ValueError:
pass
if grokster is None:
try: # is x referring to a trund?
d = makeTooDeep(x)
grokster = d.groan()
except ValueError:
pass
if grokster:
return grokster.grok()
else:
return None
If you cannot "recover" from the case where all attempts fail, then instead of return None
at the end you would do something like raise GroksterError("Don't know how to make grokster of x")
.
Of course, if you have a lot of these, you can probably factor out some of this logic, for instance by having a list of the "creators" (makeSnood
, makeTrund
, etc.) and their corresponding methods (snoodidle
, trundle
, etc.), so that you can loop over this list and run the same logic for each attempt. However, this may make the code more confusing for a simple case like this (with only three things to try). Also, in practice I find that if you're doing heterogenous attempts like this, often your handling of them is heterogenous too (e.g., you may be doing different things depending on which attempt succeeds); in that case, for a general solution, you have to split things up even more by pulling the "setup" for each attempt out into a separate function so you can call it.