Reputation: 2625
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
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
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
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
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