Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Looking for idiomatic way to evaluate to False if argument is False in Python 3

I have a chain of functions, all defined elsewhere in the class:

fus(roh(dah(inp)))

where inp is either a dictionary, or bool(False).

The desired result is that if inp, or any of the functions evaluate to False, False is returned by the function stack.

I attempted to use ternary operators, but they don't evaluate correctly.

def func(inp):
    return int(inp['value']) + 1 if inp else False

throws a TypeError, bool not subscriptable, if i == False because inp['value'] is evaluated before the conditional.

I know I can do it explicitly:

def func(inp):
    if inp == False:
        return False
    else:
        return inp['value'] + 1

but there are a ton of functions, and this will nearly quadruple the length of my code. It's also rewriting the exact same lines of code again and again, which suggests to me that it is the wrong way to do things.

I suspect that a decorator with arguments is the answer, but the more I play around with it the less sure I am about that.

def validate_inp(inp):
    def decorator(func):
        def wrapper(*args):
             return func(inp) if inp else False
        return wrapper
    return decorator

@validate_inp(inp)
def func(inp):
    return int(inp['value']) + 1

Unfortunately the decorator call throws a NameError, 'inp' not defined. But I'm not sure if I'm using the decorator incorrectly, or the decorator is the wrong solution.

Looking for comment, criticism, suggestion, and/or sanity check.


If you found this trying to solve your own problem...

You probably want to be using empty dictionaries instead of boolean False. Props to @chepner.

In my application, using False was "okay" but offered no advantages and caused some chunky blocks of code.

I've found everything is simpler using an empty dictionary instead. I'm wrapping the functions that use the dict with a decorator that catches the KeyError thrown by referencing dict['value'] where dict is empty.

like image 549
Jeffrey Swan Avatar asked May 07 '15 17:05

Jeffrey Swan


People also ask

How do you check if a value is false in Python?

You can check if a value is either truthy or falsy with the built-in bool() function. According to the Python Documentation, this function: Returns a Boolean value, i.e. one of True or False .

How do you check if a variable is boolean in Python?

We can evaluate values and variables using the Python bool() function. This method is used to return or convert a value to a Boolean value i.e., True or False, using the standard truth testing procedure.


2 Answers

Decorator should look like:

def validate_inp(fun):
    def wrapper(inp):
        return fun(inp) if inp else False
    return wrapper


@validate_inp
def func(inp):
    return int(inp['value']) + 1

print(func(False))
print(func({'value': 1}))

If you want to use your decorator with a class member:

def validate_inp(fun):
    def wrapper(self, inp):
        return fun(self, inp) if inp else False
    return wrapper

class Foo(object):
    @validate_inp
    def func(self, inp):
        return int(inp['value']) + 1 if inp else False

foo = Foo()
print(foo.func(False))
print(foo.func({'value': 1}))
like image 133
KAdot Avatar answered Sep 29 '22 08:09

KAdot


I attempted to use ternary operators, but they don't evaluate correctly.

def func(inp):
    return int(inp['value']) + 1 if inp else False

throws a TypeError, bool not subscriptable, if i == False because inp['value'] is evaluated before the conditional.

This is not true - that code works. Further, you can just write

def func(inp):
    return inp and (int(inp['value']) + 1)

To automatically wrap functions like this, make a function that wraps a function:

def fallthrough_on_false(function):
    def inner(inp):
        return inp and function(inp)
    return inner

This should be improved by using functools.wraps to carry through decorators and names, and it should probably take a variadic number of arguments to allow for optional extensions:

from functools import wraps

def fallthrough_on_false(function):
    @wraps(function)
    def inner(inp, *args, **kwargs):
        return inp and function(inp, *args, **kwargs)
    return inner
like image 35
Veedrac Avatar answered Sep 29 '22 08:09

Veedrac