How to create a falsy sentinel object in Python?

Question:

I have a function which is designed to be called by passing in one of to keyword arguments. I’m using a sentinel object as default value, so that I can make sure no one just calls func() without any arguments, which is a clear logical error. It is ok to call the function by passing None as a value for one of the arguments, in those cases it just doesn’t do any processing.

NO_VALUE = object()


def func(*, arg1 = NO_VALUE, arg2 = NO_VALUE):
    if arg1 is NO_VALUE and arg2 is NO_VALUE:
        raise ValueError("Pass in one of `arg1` or arg2`.")

    if arg1 is not NO_VALUE and arg1:
        # Do something with a truthy `arg1` value.
    if arg2 is not NO_VALUE and arg2:
        # Do something with a truthy `arg2` value.

Could I somehow easily make NO_VALUE be falsy, so that I could simplify the if arg1 is not NO_VALUE and arg1 and if arg2 is not NO_VALUE and arg2 to just if arg1 and if arg2 respectively?

I tried making NO_VALUE an empty tuple () but it seems that the id() of an empty tuple is always(?) same as the id() of any other empty tuple. I also don’t want to make NO_VALUE e.g. an empty list object, since then I’d get linter warnings about using a mutable default value.

Asked By: ruohola

||

Answers:

In any case: for a Python object to be "Falsy" it can be an instance of a class which implements __bool__ and returns False when that is called:

class FalsySentinel:
    def  __bool__(self):
        return False


NO_VALUE = FalsySentinel()


def func(*, arg1 = NO_VALUE, arg2 = NO_VALUE):
    if arg1 is NO_VALUE and arg2 is NO_VALUE:
        raise ValueError("Pass in one of `arg1` or arg2`.")

    if arg1:
        # Do something with a truthy `arg1` value.
    if arg2:
        # Do something with a truthy `arg2` value.

There are other ways to produce falsy objects, like objects that implement __len__ and return 0 on it – but this is the most explicit and straightforward way.

Answered By: jsbueno

To get the behavior you describe, I think you should use kwargs instead of keyword args. Consider the following function:


def func(**kwargs):
    if kwargs.keys() == set(['arg1']):
        print(kwargs['arg1'])
    elif kwargs.keys() == set(['arg2']):
        print(kwargs['arg2'])
    else:
        raise ValueError("Pass in one of `arg1` or `arg2`")

Because you’re ultimately trying to achieve more complicated logic on your function arguments, it makes sense to handle this with code, as opposed to letting the compiler try to sort it out with weird sentinels.

Answered By: Carson
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.