Lord_JABA
Lord_JABA

Reputation: 2625

How to handle python exception inside if statement

For example I got this if statement:

if user.address.streetname == 'Target':
    pass
elif:
    (...)
else:
    (...)

But not all users have enough money to have an address so it could raise an exception In my case a django DoesNotExist exception. In this case it should assume false.

How to handle exception in this place without breaking if elif else flow?

Upvotes: 0

Views: 326

Answers (4)

Corley Brigman
Corley Brigman

Reputation: 12371

try:
    streetname = user.address.streetname
except DoesNotExist:
    streetname = None
    # or:
    # streetname = NonexistenceSentinel()

if streetname == 'Target':
    pass
elif:
    ...
else:
    ...

But probably what you are really looking for is some syntactic sugar to allow you to not put this in everywhere. Here's a recipe that lets you do that:

# 'Stupid' object that just returns itself.
# Any attribute will just return itself.
class SentinelObject(object):
    __slots__ = []
    def __init__(self):
        pass

    def __getattr__(self, key):
        return self

    def __nonzero__(self):
        return False


def delegate_specials(specials):
    specialnames = ['__%s__'%s for s in specials.split()]
    def wrapit(cls, method):
        return lambda self, *args, **kwargs: getattr(self._original_obj, method)(*args, **kwargs) 
    def dowrap(cls):
        for n in specialnames:
            setattr(cls, n,
                wrapit(cls, n))
        return cls
    return dowrap

@delegate_specials('getitem setitem iter add sub mul div repr str len')
class SafeLookupObject(object):
    __slots__ = ['_original_obj', '_sentinel_default']
    __original_names__ = ['_original_obj', '_sentinel_default']
    def __init__(self, original_obj, sentinel_default=SentinelObject()):
        self._original_obj = original_obj
        self._sentinel_default = sentinel_default

    def __getattr__(self, key):
        if key in self.__original_names__:
            return object.__getattr__(self, key)
        else:
            try:
                val = getattr(self._original_obj, key)
                if callable(val):
                    return val
                else:
                    return SafeLookupObject(val, self._sentinel_default)
            except AttributeError:
                return self._sentinel_default

    def __setattr__(self, key, value):
        if key in self.__original_names__:
            return object.__setattr__(self, key, value)
        else:
            return setattr(self._original, key, value)

May not be perfect, looks OK at a first pass.

What this does: You pass in an original object, and a default val. The default val is a special SentinelObject (more on that in a minute). Only for getattr, if it doesn't exist, it returns the sentinel value. If it does exist, it checks to see if it's callable or not. If it's callable (i.e. a function), it returns it directly. If not, it wraps it in a SafeLookupObject and returns it.

The intention is that if you want to lookup x.y.z, you can just wrap x in the SafeLookupObject, and then x.y will automatically be wrapped as well, all the way down, so if it fails anywhere in the list, that value will be replaced with the sentinel object.

Therefore the special SentinelObject, which returns itself for whatever attribute you pass in. This with the above makes it fully recursive.

I.e. let's say you look up a.b.c.d with a as safe. a.b is OK, but then c does not exist in b. With a default of None, a.b.c returns None, but then a.b.c.d raises an exception.

If you use the SentinelObject as the default, then a.b.c instead returns a SentinelObject which both is boolean False, and can be matched against to determine a non-existent attribute. a.b.c.d also returns the same SentinelObject, and so it's now completely safe.

Upvotes: 0

chepner
chepner

Reputation: 530833

Use duck typing and create a sentinel object that is guaranteed to have a non-matching streetname attribute to use in place of an unavailable user.address.

poor_user_address = type('', (), {'streetname': None})()
if getattr(user, 'address', poor_user_address).streetname == "Target":
    ...

The call to type creates a minimal class with a class variable streetname; the rest of the details of the class are irrelevant. With duck typing, it doesn't matter that poor_user_address is an instance of a different class, as long as it exhibits the same behavior. In this case, the only expected behavior is to have a streetname attribute that can be compared to "Target".

Upvotes: 1

Joel
Joel

Reputation: 2257

Here's another stackoverflow question that answers this

The relevant part would be: hasattr(user, 'address')

If you add that to your if before accessing the property you can maintain the if/else flow.

Upvotes: 0

karthikr
karthikr

Reputation: 99620

If user.address is a model instance, you can do

if user and user.address and user.address.streetname and user.address.streetname == 'Target':
    #Do something/

Or, you can also do:

address = getattr(user, 'address', None) if user else None
if address and getattr(address, 'streetname', '') == 'Target':
    #do something 

Upvotes: 3

Related Questions