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.
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.
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.
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.
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.
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.