From Ruby to Python - Is there an equivalent of "try"?

Viewed 1368

In Ruby (on Rails, actually), I have the try function:

[].try(:[], 1) # => nil
[10].try(:[], 1) # => nil
[10, 20].try(:[], 1) # => 20
[10, 20, 30].try(:[], 1) # => 20

{}.try(:foo) # => nil
{ foo: 'bar' }.try(:foo) # => 'bar'

The function basically is a shortcut to avoid ifs to check if the position is present.
Is there something similar in Python?

7 Answers

Dictionaries: dict.get

You can use dict.get:

d = {'foo' : 'bar'}

print(d.get('foo'))
'bar'

print(d.get('xxxxx'))
None

You can also pass a default parameter to get:

print(d.get('xxxxx', 'Invalid Key!'))
Invalid Key!

The default value is printed out when the key does not exist in the dictionary.


Lists: custom try-except block

Unfortunately, lists do not have a dict.get equivalent in their API, so you'll need to implement one yourself. Thankfully, you can extend the list class and override __getitem__ to do this cleanly.

class MyList(list):
    def __getitem__(self, idx, default='oops'):
        try:
            return super().__getitem__(idx)
        except IndexError:
            return default

l = MyList([10, 20])

l[1]
# 20

l[3]
# 'oops'

      

It is not possible to have a method like try in Python, since try crucially relies on monkey patching Object and NilClass, and their Python equivalents (object and NoneType) cannot be monkey patched:

def try_object(self, f, *args, **kwargs):
    return getattr(self, f)(*args, **kwargs)

def try_none(self, f, *args, **kwargs):
    return None

object.try   = try_object
object.try   = try_object
#            ^
# SyntaxError: invalid syntax

NoneType.try = try_none
#            ^
# SyntaxError: invalid syntax

However, you can write a global try function that behaves similar:

def tryy(obj, f, *args, **kwargs):
    if obj is None:
        return None
    else:
        return getattr(obj, f)(*args, **kwargs)

tryy([10, 20], '__getitem__', 1)
# >>> 20

tryy(None, '__getitem__', 1)
# >>> None

python list object has no safe .get like dictionary has.

you will need to catch that with your own try and except:

try:
    return your_list[index]
except IndexError:
    return default # could be -1 for example

With dict's you can use d.get('foo') which returns nothing if the key doesn't exist. You also can specify a defualt value, d.get('foo', 'bar') in which case bar is returned if the key foo isn't in the dict.

With lists, there isn't such obvious way. You can define a simple function,

def check_item(list_obj, item):
    try:
        return list_obj[item]
    except IndexError:
        pass

Which will return the value if it exists, and nothing if not. Then simply call it value = check_item(list_item, item_to_look)


More advanced option would be to subclass the list class, and override the __getitem__ method a function with similar logic to the above. (It requires some additional modifications to handle slice objects, etc.)

In python, you must live by this mantra "It's easier to ask forgiveness than get permission".

For a list however, you can write a check like this to make the list into a dict with having its indexes as keys.

>>>en = enumerate([10, 20])
>>>dct = dict(en)
>>>dct.get(2)
None
>>>dct.get(1)
20

combining solutions

def safe_get(data, key):
    if isinstance(data, (list, set, tuple)):
        return dict(enumerate(data)).get(key)
    elif isinstance(data, dict):
        return (data).get(key)
    else:
        return None

print safe_get([], 1)
None
print safe_get([10], 1)
None
print safe_get([10, 20], 1)
20
print safe_get([10, 20, 30], 1)
20
print safe_get({}, 'foo')
None
print safe_get({ 'foo': 'bar' }, 'foo')
bar

you can use getattr which will do what try would do for properties/attributes but it won't work for methods which need to be called.

eg

getattr(getattr(foo, 'prop1', None), 'prop2', None)

this is the same as in ruby

foo.try(:prop1).try(:prop2)
Related