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,
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
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.
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
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(NotSet) # "<class '__main__.NotSet'>"
Of course, you could go one step further and declare your own
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.