Create sentinel and singleton objects in Python
MIT License
sentinel — create sentinel and singleton objects
|Tests| |PyPI version|
.. |Tests| image:: https://github.com/eddieantonio/sentinel/workflows/Test%20and%20Lint/badge.svg :target: https://github.com/eddieantonio/sentinel/actions?query=workflow%3A%22Test+and+Lint%22 .. |PyPI version| image:: https://img.shields.io/pypi/v/sentinel :target: https://pypi.org/project/sentinel/
Creates simple sentinel objects.
Basic features::
pip install sentinel
with extra magic features powered by python-varname_::
pip install 'sentinel[varname]'
Sentinels_ are singleton_ objects that typically represent some
terminating (end) condition or have a special, symbolic meaning. Python's built-in
None
is a sentinel. Python also has other sentinels like NotImplemented
and
Ellipsis
.
If you want to create your own sentinels, use this library! Make your calls to
dict.get()
more meaningful! You can replace the object()
idiom with a sentinel:
.. code-block:: python
d = {"a": 1, "b": None}
missing = object() if d.get("c", missing) is missing: ... # do some stuff
Missing = sentinel.create() if d.get("c", Missing) is Missing: ... # do some stuff
is
comparisonspickle
copy.deepcopy
__repr__
!Create a sentinel:
import sentinel MySentinel = sentinel.create("MySentinel") MySentinel MySentinel
If you have python-varname_ installed, or installed this module using
pip install 'sentinel[varname]'
, sentinel.create()
can infer the name
from the assignment expression:
.. code-block:: python
import sentinel
MySentinel = sentinel.create()
print(MySentinel) # prints MySentinel
NOTE: this will not work in the interactive console!
import sentinel # Fails because varname can't find the source code for the interactive console! MySentinel = sentinel.create("MySentinel")
Sentinels are useful when other objects such as None
, False
,
0
, -1
, are valid values within some data structure. For example, setting
default values when all other values are valid with:
dict.setdefault()
:
.. code-block:: python
d = {"stdout": None, "stdin": 0, "EOF": -1}
MissingEntry = sentinel.create()
[d.setdefault(key, MissingEntry) for key in ("stdin", "stdout", "stderr")] [0, None, MissingEntry]
Alternatively, using dict.get()
when fetching values:
d = {"stdout": None, "stdin": 0, "EOF": -1} d.get("stdout", MissingEntry) None d.get("stdin", MissingEntry) 0 d.get("stderr", MissingEntry) MissingEntry
Since a new sentinel can never occur in the original dictionary, you can tell which entries are missing or unset in a dictionary in a self-documenting way:
.. code-block:: python
Unset = sentinel.create() if d.get("stdin", Unset) is Unset: stdin = 0 # some reasonable default
Sentinels may also inherit from base classes, or implement extra methods.
Consider a binary search tree with two kinds of nodes: interior nodes
(Node
) which contain some payload and leaves (Leaf
), which simply
terminate traversal.
To create singleton leaf which implements a search
method and an
is_leaf
property, you may provide any extra class attributes in the
cls_dict
keyword argument. The following is a full example of both
the singleton Leaf
and its Node
counterpart:
.. code-block:: python
def _search_leaf(self, key):
raise KeyError(key)
Leaf = sentinel.create('Leaf', cls_dict={
'search': _search_leaf,
'is_leaf': property(lambda self: True)
})
class Node(object):
def __init__(self, key, payload, left=Leaf, right=Leaf):
self.left = left
self.right = right
self.key = key
self.payload = payload
def search(self, key):
if key < self.key:
return self.left.search(key)
elif key > self.key:
return self.right.search(key)
else:
return self.payload
is_leaf = property(lambda: false)
Example usage:
tree = Node(2, 'bar', Node(1, 'foo'), Node(3, 'baz')) tree.search(1) 'foo' tree.search(4) Traceback (most recent call last): ... KeyError: 2
This project uses Poetry_. To contribute to the codebase, make sure to install poetry
_,
With Poetry installed, clone then repo, then within the repo directory, install the developer dependencies::
$ poetry install --extras varname
Next, I recommend you do all development tasks within the poetry shell
::
$ poetry shell
(sentinel-nUnrocCf-py3.9) $ black .
(sentinel-nUnrocCf-py3.9) $ pytest
.. _Sentinels: http://en.wikipedia.org/wiki/Sentinel_nodes .. _singleton: http://en.wikipedia.org/wiki/Singleton_pattern .. _Poetry: https://python-poetry.org/ .. _install poetry: https://python-poetry.org/docs/#installation .. _python-varname: https://github.com/pwwang/python-varname