I was also thinking that. I’ve got a reasonable working implementation of MISSING = sentinel("MISSING")
, but one which generates a class and an instance, rather than just using a class object as you suggest. AFAICT, the only drawbacks of this are using the same mechanism as Enum
, logging
and namedtuple
of inspecting stack frames to find the module where it is defined, and using a naming hack instead of trying to set the class as a module attribute. I also didn’t like that the type isn’t directly available and would potentially have an awkward name, but being able to use Literal[MISSING]
possibly makes that irrelevant.
I’m still not sure what would be preferable. (I originally intended to first think about it some more and get thoughts from a few initial reviewers.)
For those wondering, here is the (simplified) code:
import sys
if hasattr(sys, '_getframe'):
get_parent_frame = lambda: sys._getframe(2)
else:
def get_parent_frame():
"""Return the frame object for the caller's parent stack frame."""
try:
raise Exception
except Exception:
return sys.exc_info()[2].tb_frame.f_back.f_back
def sentinel(name):
"""Create a unique sentinel object."""
repr_ = f'<{name}>'
# This is a hack to get copying and unpickling to work without setting the
# class as a module attribute.
class_name = f'{name}.__class__'
class_namespace = {
'__repr__': lambda self: repr_,
}
cls = type(class_name, (), class_namespace)
# For pickling to work, the __module__ variable needs to be set to the
# name of the module where the sentinel is defined.
try:
module = get_parent_frame().f_globals.get('__name__', '__main__')
except (AttributeError, ValueError):
pass
else:
cls.__module__ = module
sentinel = cls()
cls.__new__ = lambda self: sentinel
return sentinel