views:

150

answers:

3

I have a fairly complex object (deserialized json, so I don't have too much control over it) that I need to check for the existence of and iterate over a fairly deep elements, so right now I have something like this:

if a.get("key") and a["key"][0] and a["key"][0][0] :
    for b in a["key"][0][0] :
        #Do something

which works, but is pretty ugly. It seems there has to be a better way to do this, so what's a more elegant solution?

+14  A: 
try:
  bs = a["key"][0][0]
# Note: the syntax for catching exceptions is different in old versions
# of Python. Use whichever one of these lines is appropriate to your version.
except KeyError, IndexError, TypeError:   # Python 3
except (KeyError, IndexError, TypeError): # Python 2
  bs = []
for b in bs:

And you can package it up into a function, if you don't mind longer lines:

def maybe_list(f):
  try:
    return f()
  except KeyError, IndexError, TypeError:
    return []

for b in maybe_list(lambda: a["key"][0][0]):
John Millikin
Good call, forgot about the Python philosophy of it's better to ask forgiveness than permission. Coming from .NET where you try to avoid having exceptions thrown as much as possible, it'll take some time getting used to.
Davy8
There is an error in your code: please change `except KeyError, IndexError:` to `except (KeyError, IndexError):`. Otherwise it will catch `KeyError` only and store it in `IndexError` variable.
Denis Otkidach
Also, you'll want to catch `TypeError`, which gets thrown if `bs['key']` contains something that isn't a sequence.
Robert Rossney
@Denis: That depends on your version of Python. I'll add both versions. @Robert: Thanks
John Millikin
+2  A: 

What about this:

try:
    for b in a['key'][0][0]:
        # do stuff.
except KeyError, TypeError, IndexError:
    # respond appropriately.
Peter
This'll catch exceptions from within "do stuff" also, not just from finding the list.
John Millikin
they should be handled within the `do stuff` block with their own `try/except` statements.
Peter
Unless you don't want to handle those.
recursive
+1 since it's better than what I was using, but accepting John's answer cause it's more easily abstracted.
Davy8
`except KeyError, TypeError, IndexError:` -> `except (KeyError, TypeError, IndexError)`
Denis Otkidach
+3  A: 

I would write a custom indexer function like this:

def safe_indexer(obj, *indices):
    for idx in indices:
        if not obj: break

        if hasattr(obj, "get"):
            obj = obj.get(idx)
        else:
            obj = obj[idx]

    return obj

Usage:

a = {"key": {0: {0: "foo"} } };
print safe_indexer(a, "key", 0, 0)
print safe_indexer(a, "bad", 0, 0)

Output:

foo
None
recursive
Hmm, I like this. If I find I come across this pattern more often I'll probably use this. For now I just need a one-off thing so John's is simpler.
Davy8
Quick question, new to Python, what's the * in front of indices mean?
Davy8
It means it's a parameter list. In other words, all of the subsequent arguments are consolidated into a list called "indices". That way you don't have to pass a list explicitly. Instead, just use the indices directly as arguments to safe_indexer.
recursive
@Davy8 the * [in front of indices] defines indices as a sequence-type argument which contains all the "extra positional arguments" at the time the function is called. For ex, in the case of safe_indexer(a, "key", 0, 1), it the following tuple ("key", 0, 0) since a is the "obj" argument, and there are 3 extra positional args. A similar notation, with two stars is used for keyword arguments, passed in a dictionary. See http://docs.python.org/reference/compound_stmts.html#function-definitions This is a very useful feature of Python.
mjv
It's worth noting that the above doesn't work with your original problem (list() doesn't have a .get()). Saying that I'd be much more likely to write something like this than the try/except version.
James Antill
@James: If the object doesn't have a get, then subscription would be used. It does work.
recursive