1

I'm trying to get some data from an JSON API. I don't want all the data that the API returns so I wrote a method that reads all the data and returns a dictionary with the relevant fields. Sometimes though, some data are missing and I would like to replace the fields that are missing with an underscore. A sample of the method is like that;

return {
    'foo': data['foo'],
    'bar': data['bar']
 }

If a field is missing from the data, this throughs a KeyError. Is it possible to catch programmatically which field produced the error, in a single try-except block and not write a try-except block for every field?

try:
    ret_dict =  {
        'foo': data['foo'],
        'bar': data['bar']
    }
 except KeyError:
    ret_dict[thefailurekey] = '_'

instead of

ret_dict = {}
try:
    ret_dict['foo'] = data['foo']
except KeyError:
    ret_dict['foo'] = '_'
try:
    ret_dict['bar'] = data['bar']
except:
    ret_dict['bar'] = '_'

Thank you

jzlas
  • 187
  • 2
  • 11

4 Answers4

3

You can probably get that information from the members of the KeyError exception object, but a simpler way would be to just use get() that will return a default value if the key is not there.

return {
    'foo': data.get('foo', '_'),
    'bar': data.get('bar', '_'),
 }

Another reason this is better than handling an exception is that you can only handle one exception. What happens if two keys are missing? And on top of that, ret_dict will not even be defined in your example because the code failed.

kichik
  • 33,220
  • 7
  • 94
  • 114
  • Thank you for your answer. Some fields are themselves dictionaries, which means that I need to do something like data['foo']['bar']. Is there a better solution from data.get('foo',{}).get('bar','_')? – jzlas Oct 02 '16 at 19:03
  • I haven't seen anything better than that. – kichik Oct 02 '16 at 19:07
2

Instead of using try block, you can use dict.get(key, default_val)

For example:

ret_dict = dict(
    foo=data.get('foo', '-'),
    bar=data.get('bar', '-')
)

return ret_dict
2

Use .get method of dict:

def get_data(data):
    return {
        # If you want to accept falsy values from API:
        'foo': data.get('foo', '_'),

        # If you want to override falsy values from API:
        'bar': data.get('bar') or '_',
    }

.get returns its second argument (None by default) if a dict doesn't have requested key, so it is always safe to use it in uncertain situations.

Example:

>>> data = {'foo': False, 'bar': False}
>>> get_data(data)
{'bar': '_', 'foo': False}
skovorodkin
  • 9,394
  • 1
  • 39
  • 30
  • Thank you for your answer. If the field is itself a dictionary, should I use get like that; data.get('foo',{}).get('bar','_') or is there a better-more pythonic way? – jzlas Oct 02 '16 at 19:05
  • Yes, that's a way to go. Though if you want a more generic method, look at this answer http://stackoverflow.com/a/36131992/847552. – skovorodkin Oct 02 '16 at 19:10
  • Thank you, that's great. Doesn't get more pythonic than that! – jzlas Oct 02 '16 at 19:11
1

If you want to avoid the repetitiveness of typing .get(attr, '_') for each key, you can use a defaultdict, setting it to return _ when a key is trying to be accessed but missing.

from collections import defaultdict

data = {
    'foo': 'foo_value',
}

ret_dict = defaultdict(lambda: '_')
ret_dict.update(data)

print(ret_dict['foo'])  # 'foo_value'
print(ret_dict['bar'])  # '_'
Karin
  • 8,404
  • 25
  • 34