2

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.

21
  • 3
    You could make it falsy, but valid objects like None would still be falsy and fail the test Commented Jul 29, 2022 at 19:10
  • 2
    You can, but then your if statement would apply to None, and you said you wanted that handled differently. Commented Jul 29, 2022 at 19:10
  • @Carson No that'd be ok, I'm just using the sentinel so I can be sure that either arg1 or arg2 is passed in. So no one just calls func(). Commented Jul 29, 2022 at 19:11
  • 3
    In that case, why wouldn't None work? Commented Jul 29, 2022 at 19:14
  • 1
    I'm sure there's an easier way but NO_VALUE = type('NO_VALUE', (), {'__bool__': lambda *_: False})() should work. Commented Jul 29, 2022 at 19:18

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

1 Comment

If you want to simplify the checks, than maybe you could just use None as your sentinel. <- this does not work for me as this is sometimes just called as func(arg1=might_return_none()), in which case I don't want to raise an error, and also don't want to do any processing (at least here in the start of the function). But thank you, the actual solution in the answer seems nice.
0

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.

4 Comments

The problem with this is that the signature doesn't document the function at all. I'm also in reality using type hints, and with **kwargs I couldn't use different types for the parameters.
@Carson the dict.keys() returns a view and should be compared with a set, not with a list.
I'd like to ask some type hints expert: could @typing.overload help to overcome the signature problem?
I'm not a typing expert but PEP 692 will be much more suitable, when/if it is released.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.