Search code examples
pythonerror-handlingconditional-operatortry-except

How to say in Pythonese - do something unless it causes an error (without resorting to multilevel try/execpt blocks)


This is a little difficult to explain, so let's hope I'm expressing the problem coherently:

Say I have this list:

my_list = ["a string", 45, 0.5]

The critical point to understand in order to see where the question comes from is that my_list is generated by another function; I don't know ahead of time anything about my_list, specifically its length and the datatype of any of its members.

Next, say that every time <my_list> is generated, there is a number of predetermined operations I want to perform on it. For example, I want to:

my_text = my_list[1]+"hello"
some_var = my_list[10]
mini_list = my_list[0].split('s')[1]
my_sum = my_list[7]+2

etc. The important point here is that it's a large number of operations.

Obviously, some of these operations would succeed with any given my_list and some would fail and, importantly, those which fail will do so with an unpredictable Error type; but I need to run all of them on every generation of my_list.

One obvious solution would be to use try/except on each of these operations:

try:
  my_text = my_list[1]+"hello"
except:    
  my_text = "None"

try:
  some_var = my_list[10]
except:
  some_var = "couldn't do it"

etc.

But with a large number of operations, this gets very cumbersome. I looked into the various questions about multiple try/excepts, but unless I'm missing something, they don't address this.

Based on someone's suggestion (sorry, lost the link), I tried to create a function with a built-in try/except, create another list of these operations, and send each operation to the function. Something along the lines of

def careful(op):
  try:
    return op
  else:
    return "None"

And use it with, for example, the first operation:

my_text = careful(my_list[1]+"hello")

The problem is python seems to evaluate the careful() argument before it's sent out to the function and the error is generated before it can be caught...

So I guess I'm looking for a form of a ternary operator that can do something like:

my text = my_list[1]+"hello" if (this doesn't cause any type of error) else "None"

But, if one exist, I couldn't find it...

Any ideas would be welcome and sorry for the long post.


Solution

  • If you must do this, consider keeping a collection of the operations as strings and calling exec on them in a loop

    actions = [
        'my_text   = my_list[1]+"hello"',
        'some_var  = my_list[10]',
        'mini_list = my_list[0].split("s")[1]',
        'my_sum    = my_list[7]+2',
    ]
    

    If you make this collection a dict, you may also assign a default

    Note that if an action default (or part of an action string) is meant to be a string, it must be quoted twice. Consider using block-quotes for this if you already have complex escaping, like returning a raw strings or a string representing a regular expression

    {
        "foo = bar": r"""r'[\w]+baz.*'"""
    }
    

    complete example:

    >>> actions_defaults = {
    ...     'my_text   = my_list[1]+"hello"':       '"None"',
    ...     'some_var  = my_list[10]':              '"couldn\'t do it"',
    ...     'mini_list = my_list[0].split("s")[1]': '"None"',
    ...     'my_sum    = my_list[7]+2':             '"None"',
    ... }
    >>>
    >>> for action, default in actions_defaults.items():
    ...     try:
    ...         exec(action)
    ...     except Exception:  # consider logging error
    ...         exec("{} = {}".format(action.split("=")[0], default))
    ...
    >>> my_text
    'None'
    >>> some_var
    "couldn't do it"
    

    Other notes

    • this is pretty evil
    • declaring your vars before running to be their default values is probably better/clearer (sufficient to pass in the except block, as the assignment will fail)
    • you may run into weird scoping and need to access some vars via locals()