In 3.15, this is the way to define a sentinel within a class (example from the documentation):
class Cls:
PICKLABLE = sentinel("Cls.PICKLABLE")
I am personally averse to the common antipattern in Python of:
value = some_function('value', ...)
…where “value” must appear both as the assignment left-hand side and as an argument string. It feels repetitious and, while not especially error-prone, when there is an error it’s hard to diagnose. In this specific case things are complicated by needing to (remember to) give the constructor the qualified name.
What if sentinel were enhanced to provide this alternative?
class Cls:
PICKLABLE = sentinel()
The intended behaviour would be that the sentinel is initially constructed with __name__ is None, but sentinel implements __set_name__ (and a dummy __get__) so Cls later fills in the name via the descriptor protocol.
This feels considerably tidier to me, and would be backward-compatible because the name parameter to sentinel() is currently mandatory.
However, my understanding is that the posted idea is for class members only, correct? Obviously a __set_name__ mechanism wouldn’t work outside classes.
That means the first example in the documentation
MISSING = sentinel("MISSING")
remains requiring the name, leading to a split between class members vs. regular (local or global) values.
The latter split is also undesirable, so it’s choosing between two suboptimal situations.
For regular values that would require inspecting the stack etc., which I believe is being frowned upon (a.o. because not universally possible across other Python implementation than CPython).
If both could omit the name, that would be great. I hope someone smarter than me sees a practical approach to that end.
That’d be a bit of a pain, since modules frequently contain functions, and functions are descriptors. But maybe it’d be nice to have a standard idiom for making a class that becomes the module’s type.
CRJ: This idea might fit better under Help as you are asking for genii-like magic that is very unlikely to be added to the language but which you could implement as an add-on and which could be distributed on pypi. But see the end.
The requested magic is to actually or effectively replace sentinel("Cls.PICKLABLE") with PICKLABLE = sentinel("Cls.PICKLABLE") or replace PICKLABLE = sentinel(<optional-prefix>) with the same. The replacement could be done to the text with the re module or to the ast during compilation with an ast-transformer. In either case, the file extension should indicate the need for transformation before compiling. I am pretty sure devs want .py and similar to contain ‘real’ python.
I reject the idea of runtime manipulation because whether to replace and what to do are context dependent. Sentinels do not have to be bound to the passed in name; the first replacement form should not be invoked if preceeded by single = or :=. For regexes, context can be determined by a look-behind assertion; for ast context, look up the tree. Either case, the effect of the replacement is handled by compile. Both of these solutions can be easily generalized to include other functions with boilerplate duplication.
What might be a possible idea is to add a generic hook at least to CPython.
A new environmental PY_AST_HOOK = filename, where filename contains an ast transformer.
Recognition that a .pyx file needs transformation before or during compilation. There may have been ideas like this already rejected.
The requested magic is to actually or effectively replace sentinel("Cls.PICKLABLE") with PICKLABLE = sentinel("Cls.PICKLABLE") or replace PICKLABLE = sentinel(<optional-prefix>) with the same.
(Ignore the digression into how a similar effect might be achieved in a module. My original request was about classes.)
My specific suggestion was, within a class definition, that PICKLABLE = sentinel() should achieve the same effect as PICKLABLE = sentinel('Cls.PICKLABLE'). That absolutely does not require transforming source code or syntax tree: it can be achieved by having sentinel implement __set_name__ to populate its __name__ field.
To confirm it’s relatively low impact I took the reference implementation from PEP 661 and added it. The only tiny wrinkles were needing to handle __name__ is None in __repr__ and __reduce__ — hardly major.