Sometimes it is necessary to differentiate between an argument that has not
been provided, and an argument provided with the value None
. For that purpose, we create what's called a 'sentinel value'.
For example, let's assume you want to define a Field class. Field instances must have a value or declare a default,
and None
could be a perfectly valid value to have:
class Field:
def __init__(self, default=sentinel):
self.value = default
def set(self, value):
self.value = value
def get(self):
if self.value is sentinel:
raise ValueError("this field has no value!")
eula_accepted = Field()
eula_accepted.get() # raises `ValueError`
eula_accepted = Field(default=None)
eula_accepted.get() # doesn't raise. `None` means the EULA hasn't been neither accepted or reject yet.
The most common approach is to declare the sentinel value with object()
:
sentinel = object()
This approach is the quickest and most common, but it has some issues. To quote Joseph Jevnik:
One is that they don't repr well so they make debugging harder. Another issue is that they cannot be pickled or copied. You also cannot take a weak reference to a sentinel which can break some caching code and makes them harder to use.
For example:
sentinel = object()
repr(sentinel)
# '<object object at 0x10823e8d0>'
To work around this issue, some people create their own Sentinel
class. But I've found a quicker way in the unittest.mock
module.
from unittest.mock import sentinel
NotSet = sentinel.NotSet
repr(NotSet)
# 'sentinel.NotSet'
If you don't feel like importing from unittest
in your application code, you could install the mock
package, or "hide it under the rug" by aliasing somewhere in your code base and importing it from there:
# in `myproject.types`
from unittest.mock import sentinel
# somewhere else in your project:
from myproject.types import sentinel
An alternative to unittest.mock.sentinel
is to declare your own sentinel class and use it as a value:
class NotSet:
pass
# PS: Remember to use the class _itself_.
def fn(default=NotSet):
pass
This will give you a not really pretty, but useful enough repr
:
repr(NotSet)
# "<class '__main__.NotSet'>"
Of course, you could go one step further and declare your own repr
:
class NotSet:
def __repr__(self):
return 'NotSet'
repr(NotSet)
# 'NotSet'
Of all the options, I think using unittest.mock.Sentinel
is my favorite. Importing from unittest
in my application code is a compromise that I'm willing to make in exchange for having something ready to use.