1. Terminology
Note, I use “singlenel” to differentiate and to not confuse this to PEP 661: Sentinel Values. The difference between these:
singlenel
- has its own type, supports custom attributes and methods. In other words, it is a specialisation of a singleton.- PEP661 - One type for instantiation of many different sentinels
2. Motivation
Currently, there are several singlenel
(singleton sentinel) implementations:
- builtins:
None
& friends functools.Placeholder
typing.NoDefault
- …?
Why unify?
- The repetition needed to implement such object is fairly large and can get quite complex
- Serialization issues. E.g.
None
& friends are special-cased inpickle
as theirtype
is not located anywhere. This is because it is often desirable to expose the instance, but not its type. This leads to:type(None).__module__ == 'builtins'
, butbuiltins.NoneType -> AttributeError
- I think
singletons
andsinglenels
would be used more if there was an easy way to implement these. I suspect that some extensions that could make use of these simply don’t happen because implementation ofsingleton
would be more than 50% of the work required. My personal most recent case: Add operator.getitemtuple & operator.itemtuplegetter · Issue #128000 · python/cpython · GitHub.
Why in C
?
- It could be a base class of
None
& friends. - It would resolve inconveniences that arise in cases where there are both
C
andPython
implementations. I had issues when testingcopy
,pickle
& similar due to nuances of multi-phase module initialisation. Ifsinglenels
lived in independent lower level module, bothC
andPython
versions could use the same.
Why singleton?
Because from implementation perspective None
is 99% singleton.
Thus, implementing singleton would also give general singletons and singlenel
would be just one specialisation.
3. How would it look like?
So I haven’t got the specifics yet, there is a bit of ground to cover.
But I thought it would be useful to put it out a bit earlier to see whether it is worth going further at all.
However, I had this in mind for a fair while now and have a rough idea.
Base class of builtin and other C-implemented
singletons:
type(None).__mro__ # (NoneType, Singleton, object)
Implementing new singleton in Python
:
from singleton import Singleton
class UniversalSetType(Singleton):
def __contains__(self, other):
return True
UniversalSet = UniversalSetType()
type(UniversalSet)() is UniversalSet# True
UniversalSet.__module__ # "this module"
Shorthand for implementing new singlenel
in Python
:
from singleton implement new_sentinel
# new_sentinel(name, namespace, /, **attrs)
FlagEmpty = new_sentinel('FlagEmpty', 'typing', a=1)
type(FlagEmpty)() is FlagEmpty # True
FooType.__module__ # 'typing'
Foo.a # 1
# Only 1 unique name per namespace
FlagEmpty = new_sentinel('FlagEmpty', namespace='typing')
# Error
Adding new singlenel
in standard library or extension with standard defaults:
// mymodule.c
#include "singletonobject.h"
MACRO_NEW_SINGLETON("StandardSentinel", ...)
...
from mymodule import StandardSentinel
type(StandardSentinel)() is StandardSentinel # True
StandardSentinel.__module__ # 'mymodule`
Adding new Singleton
in standard library or extension with custom attributes and methods:
This needs work, but something along the lines of:
// mymodule.c
#include "singletonobject.h"
typedef struct {
PyObject_HEAD
} customsingletonobject;
// Some macros for common methods
MACRO_SINGLETON_REPR(customsingleton_repr, "CustomSentinel")
static PyType_Slot customsingleton_type_slots[] = {
{Py_tp_repr, customsingleton_repr},
...
{0, 0}
};
static PyType_Spec customsingleton_type_spec = {
...
.slots = customsingleton_type_slots
};
...
4. Summary
I think this would:
- Simplify a fair amount of present and future code in CPython by addressing common issues in one localised place.
- Standardise things a bit by introducing a bare-bone protocol.
- Provide users with convenient
singlenel
(and more generalsingleton
) creation
Thoughts?