superdee
superdee

Reputation: 697

Clean way to write IF statements in python to skip on None?

I have a function where sometimes a parameter can be none, and I would like to compare that with another object. However, if I am trying to call an object property, my script will throw an exception on None, even if both objects are None (see example below).

def do_animals_make_same_sound(first_animal, second_animal):
    if first_animal.sound = second_animal.sound:
        print('Yes they do!')

But if both animals are None, it throws an exception when instead I want it to print('Yes they do!'), but it seems I have to write a really ugly If statement:

def do_animals_make_same_sound(first_animal, second_animal):
    if (first_animal is None and second_animal is None) or (first_animal is not None and first_animal.sound == second_animal.sound):
        print('Yes they do!')

Is there a better way to do this?

Upvotes: 0

Views: 1615

Answers (4)

georgexsh
georgexsh

Reputation: 16624

following code is clearer IMHO:

def do_animals_make_same_sound(first_animal, second_animal):
    # early return if one of the two animals is missing, ensure both exist
    if not (first_animal and second_animal):
        return
    if first_animal.sound == second_animal.sound:
        print('Yes they do!')

ref: Avoid Else, Return Early

Upvotes: 2

Corley Brigman
Corley Brigman

Reputation: 12391

If it's a general enough pattern, I'd use a decorator to catch the None case specifically and process it. That keeps the logic out of the function. But you need to define exactly what None means here... it's a little odd that you can pass None for both, but it's not legal to just pass None for one of them. In any case, a decorator is a great way to abstract out some common logic in a clean way...

def NoNone(f):
    @functools.wraps(f)
    def _no_none_func(*args, **kwargs):
        if args[0] == None and args[1] == None:
            print('Both are None')
            return 
        return f(*args)
    return _no_none_func

@NoNone
def do_animals_make_same_sound(first_animal, second_animal):
    if first_animal.sound == second_animal.sound:
        print('Yes they do!')
    else:
        print("No they don't!")

Upvotes: 0

Martin Liu
Martin Liu

Reputation: 107

I think first you have to understand what's the meaning if one of the objects is None. There are basically three scenarios:

  1. One or both objects are None

  2. One or both objects does not have sound attribute

  3. Both have sound attribute

For #1, I'm assuming it should throw an error as there is really no comparison. What your code does is print "Yes they do" if both objects are None.

For #2, you can use what ShadowRanger suggests, If both objects have None as sound property, and your think it is a normal behavior, then use ShadowRanger's solution.

For #3, just do your normal comparison

def do_animals_make_same_sound(first_animal, second_animal):
    if not first_animal or not second_animal:
        print("One of the objects is None")
    elif getattr(first_animal, 'sound', None) == getattr(second_animal, 'sound', None):
        print("Yes, they do!")

Upvotes: 0

ShadowRanger
ShadowRanger

Reputation: 155418

It's not great, but one approach can be to use getattr with a default, so None (and anything else without the desired attribute) behaves as if it had the default as the value of its attribute. For example:

if first_animal.sound == second_animal.sound:

can become:

if getattr(first_animal, 'sound', None) == getattr(second_animal, 'sound', None):

I don't actually recommend this, as it silently ignores errors. In real code, I'd almost always let the AttributeError propagate; there is no reasonable scenario in which I'd consider None an acceptable stand-in for "something" where "something" has specific behaviors or attributes; if the caller is passing None, that's almost certainly an error that should not be silently ignored.

Upvotes: 0

Related Questions