Mailing List Archive

The repr of a sentinel
Following a recent change, we now have in traceback.py:

_sentinel = object()
def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None,
file=None, chain=True):

So now:

>>> import traceback
>>> help(traceback.print_exception)
Help on function print_exception in module traceback:

print_exception(exc, /, value=<object object at
0x000002825DF09650>, tb=<object object at 0x000002825DF09650>,
limit=None, file=None, chain=True)


Is there a convention on how such default sentinel values should appear in
docs?

https://bugs.python.org/issue43024
Re: The repr of a sentinel [ In reply to ]
On Thu, 13 May 2021 10:15:03 +0100
Irit Katriel via Python-Dev <python-dev@python.org> wrote:

> Following a recent change, we now have in traceback.py:
>
> _sentinel = object()
> def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None,
> file=None, chain=True):
>
> So now:
>
> >>> import traceback
> >>> help(traceback.print_exception)
> Help on function print_exception in module traceback:
>
> print_exception(exc, /, value=<object object at
> 0x000002825DF09650>, tb=<object object at 0x000002825DF09650>,
> limit=None, file=None, chain=True)
>
>
> Is there a convention on how such default sentinel values should appear in
> docs?

If this were a positional-only argument, you could use square brackets,
e.g.:

print_exception(exc[, value[, ...]])

Other than that, I can't think of any existing convention. I agree
that <optional> is a reasonable spelling.

Regards

Antoine.


_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/EJXKQJM7COQFIPPSQGH5O3IAFPUKYWGL/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Thu, May 13, 2021 at 10:28 AM Antoine Pitrou <antoine@python.org> wrote:

>
> I agree that <optional> is a reasonable spelling.
>
>
I initially suggested <optional>, but now I'm not sure because it doesn't
indicate what happens when you don't provide it (as in, what is the default
value). So now I'm with <derived> or <implicit>.

The arg is only there for backwards compatibility now.
Re: The repr of a sentinel [ In reply to ]
Le 13/05/2021 à 11:40, Irit Katriel a écrit :
>
>
> On Thu, May 13, 2021 at 10:28 AM Antoine Pitrou <antoine@python.org
> <mailto:antoine@python.org>> wrote:
>
>
>  I agree that <optional> is a reasonable spelling.
>
>
> I initially suggested <optional>, but now I'm not sure because it
> doesn't indicate what happens when you don't provide it (as in, what is
> the default value).  So now I'm with <derived> or <implicit>.

"<derived>" makes think of a derived class, and leaves me confused.
"<implicit>" is a bit better, but doesn't clearly say what the default
value is, either. So in all cases I have to read the docstring in
addition to the function signature.

Regards

Antoine.
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/EVACPXULASJ2GUTN7ZICKJ2LOT224MP4/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 13. 05. 21 11:45, Antoine Pitrou wrote:
>
> Le 13/05/2021 à 11:40, Irit Katriel a écrit :
>>
>>
>> On Thu, May 13, 2021 at 10:28 AM Antoine Pitrou <antoine@python.org
>> <mailto:antoine@python.org>> wrote:
>>
>>
>>       I agree that <optional> is a reasonable spelling.
>>
>>
>> I initially suggested <optional>, but now I'm not sure because it
>> doesn't indicate what happens when you don't provide it (as in, what
>> is the default value).  So now I'm with <derived> or <implicit>.
>
> "<derived>" makes think of a derived class, and leaves me confused.
> "<implicit>" is a bit better, but doesn't clearly say what the default
> value is, either.  So in all cases I have to read the docstring in
> addition to the function signature.
>

Is <default> the term you're looking for?
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/POF7BUF5EGU37DB5F34DOVT7E6LVERX4/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 5/13/2021 7:48 AM, Petr Viktorin wrote:
> On 13. 05. 21 11:45, Antoine Pitrou wrote:
>>
>> Le 13/05/2021 à 11:40, Irit Katriel a écrit :
>>>
>>>
>>> On Thu, May 13, 2021 at 10:28 AM Antoine Pitrou <antoine@python.org
>>> <mailto:antoine@python.org>> wrote:
>>>
>>>
>>>       I agree that <optional> is a reasonable spelling.
>>>
>>>
>>> I initially suggested <optional>, but now I'm not sure because it
>>> doesn't indicate what happens when you don't provide it (as in, what
>>> is the default value).  So now I'm with <derived> or <implicit>.
>>
>> "<derived>" makes think of a derived class, and leaves me confused.
>> "<implicit>" is a bit better, but doesn't clearly say what the
>> default value is, either.  So in all cases I have to read the
>> docstring in addition to the function signature.
>>
>
> Is <default> the term you're looking for?

In the dataclasses docs
https://docs.python.org/3/library/dataclasses.html I document the
module-level sentinel MISSING, then I document the function as:

dataclasses.field(*, default=MISSING, default_factory=MISSING,
repr=True, hash=None, init=True, compare=True, metadata=None)

And I explain what MISSING means.

The help looks like:

field(*, default=<dataclasses._MISSING_TYPE object at 0x6fffffe46610>,
default_factory=<dataclasses._MISSING_TYPE object at 0x6fffffe46610>,
init=True, repr=True, hash=None, compare=True, metadata=None)

None of this is particularly awesome, but no one has complained about it
yet.

I think it's important to state an actual value for the default value,
instead of just using something like "<default>". Unless you know the
actual default value, you can't write a wrapper.

Say I wanted to write something that calls dataclasses.field, but
doesn't allow the user to specify repr, hash, init, compare, and
metadata. I could write it as:

def myfield_func(*, default=MISSING, default_factory=MISSING):
    ...

If the real default values for "default" and "default_factory" weren't
documented, there wouldn't be any easy way to write this.

I do think a python-wide standard for this would be helpful, but I don't
see how to change existing code given backward compatibility constraints.

Eric

_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/ICKR43MC35QNBEWZLJ6NW2RG2M4O27FU/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 13May2021 1248, Petr Viktorin wrote:
> On 13. 05. 21 11:45, Antoine Pitrou wrote:
>>
>> Le 13/05/2021 à 11:40, Irit Katriel a écrit :
>>>
>>>
>>> On Thu, May 13, 2021 at 10:28 AM Antoine Pitrou <antoine@python.org
>>> <mailto:antoine@python.org>> wrote:
>>>
>>>
>>>       I agree that <optional> is a reasonable spelling.
>>>
>>>
>>> I initially suggested <optional>, but now I'm not sure because it
>>> doesn't indicate what happens when you don't provide it (as in, what
>>> is the default value).  So now I'm with <derived> or <implicit>.
>>
>> "<derived>" makes think of a derived class, and leaves me confused.
>> "<implicit>" is a bit better, but doesn't clearly say what the default
>> value is, either.  So in all cases I have to read the docstring in
>> addition to the function signature.
>>
>
> Is <default> the term you're looking for?

Perhaps <unspecified> or <missing>?

Cheers,
Steve
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/RSRBWH2UK2MKZN7O3PHSNVZFZEE7JIVJ/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Thu, May 13, 2021 at 4:31 PM Eric V. Smith <eric@trueblade.com> wrote:
>
> I do think a python-wide standard for this would be helpful, but I don't
> see how to change existing code given backward compatibility constraints.

While we're on the subject, these sentinels also don't compare
properly using `is` after pickling and unpickling.

I think it's worth considering making the sentinels in the stdlib all
have good reprs and support pickling+unpickling.

What would be the potential backwards-compatibility issues with
changing the implementation of these existing sentinel values?

- Tal
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/KKDZ5TW6WXSQDE2YPOU6X5JXEU264HBS/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 5/13/2021 10:02 AM, Tal Einat wrote:
> On Thu, May 13, 2021 at 4:31 PM Eric V. Smith <eric@trueblade.com> wrote:
>> I do think a python-wide standard for this would be helpful, but I don't
>> see how to change existing code given backward compatibility constraints.
> While we're on the subject, these sentinels also don't compare
> properly using `is` after pickling and unpickling.
>
> I think it's worth considering making the sentinels in the stdlib all
> have good reprs and support pickling+unpickling.
>
> What would be the potential backwards-compatibility issues with
> changing the implementation of these existing sentinel values?

I don't think there would be a problem changing the implementation. I
was commenting on changing the name of the sentinel objects so that we
could document the functions with the sentinel's real name. We couldn't
change them to all be some appropriate module-level value named
"MISSING", for example.

Eric

_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/SWGCTZFNGLFEQ5SL5GBVQFQAD2YAAZNI/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Thu, 13 May 2021 13:44:54 +0100
Steve Dower <steve.dower@python.org> wrote:
> On 13May2021 1248, Petr Viktorin wrote:
> > On 13. 05. 21 11:45, Antoine Pitrou wrote:
> >>
> >> Le 13/05/2021 à 11:40, Irit Katriel a écrit :
> >>>
> >>>
> >>> On Thu, May 13, 2021 at 10:28 AM Antoine Pitrou <antoine@python.org
> >>> <mailto:antoine@python.org>> wrote:
> >>>
> >>>
> >>>       I agree that <optional> is a reasonable spelling.
> >>>
> >>>
> >>> I initially suggested <optional>, but now I'm not sure because it
> >>> doesn't indicate what happens when you don't provide it (as in, what
> >>> is the default value).  So now I'm with <derived> or <implicit>.
> >>
> >> "<derived>" makes think of a derived class, and leaves me confused.
> >> "<implicit>" is a bit better, but doesn't clearly say what the default
> >> value is, either.  So in all cases I have to read the docstring in
> >> addition to the function signature.
> >>
> >
> > Is <default> the term you're looking for?
>
> Perhaps <unspecified> or <missing>?

Now that I read more about the specific use case, though, I think
"<implicit>" really describes it accurately. It's not that the
information is missing, it's that it's already implied in another
argument.

Quoting the documentation:

"""Since Python 3.10, instead of passing value and tb, an exception object
can be passed as the first argument."""

(meaning the traceback is implicitly gotten from the exception object
which is passed as first argument)

Regards

Antoine.


_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/BROL74WPPOLTJLSRER6NLWUJRU3JK4SE/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
>>>>
On 5/13/21 2:15 AM, Irit Katriel via Python-Dev wrote:
>
> >>> help(traceback.print_exception)
> Help on function print_exception in module traceback:
>
> print_exception(exc, /, value=<object object at 0x000002825DF09650>, tb=<object object
> at 0x000002825DF09650>, limit=None, file=None, chain=True)


On 5/13/21 5:37 AM, Eric V. Smith wrote:
>
> The help looks like:
>
> field(*, default=<dataclasses._MISSING_TYPE object at 0x6fffffe46610>,
> default_factory=<dataclasses._MISSING_TYPE object at 0x6fffffe46610>,
> init=True, repr=True, hash=None, compare=True, metadata=None)
>
> None of this is particularly awesome, but no one has complained about it yet.

Consider me complaining. ;-)

Looks to me like the default repr for the sentinels is making those helps much less helpful by showing totally
irrelevant information and cluttering up the screen making it harder to see the actually useful bits.

An actual Sentinel class would be helpful:

>>> class Sentinel:
... def __init__(self, repr):
... self.repr = repr
... def __repr__(self):
... return self.repr
...

>>> MISSING = Sentinel('MISSING')
>>> MISSING
MISSING

>>> implicit = Sentinel('<implicit>')
>>> implicit
<implicit>

Naturally, since sentinels are symbolic names, I think it should go into the enum module. ;-) Although I will concede
that we could just put those five lines into the modules that need it.

--
~Ethan~
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/7S5PU6334ZZXCA3EFV244YZWY27KA3FD/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Thu, May 13, 2021 at 7:44 PM Ethan Furman <ethan@stoneleaf.us> wrote:
>
> Consider me complaining. ;-)

+1

> An actual Sentinel class would be helpful:
>
> >>> class Sentinel:
> ... def __init__(self, repr):
> ... self.repr = repr
> ... def __repr__(self):
> ... return self.repr
> ...
>
> >>> MISSING = Sentinel('MISSING')
> >>> MISSING
> MISSING
>
> >>> implicit = Sentinel('<implicit>')
> >>> implicit
> <implicit>

Here is my suggestion (also posted on the related bpo-44123), which is
also simple, ensures a single instance is used, even considering
multi-threading and pickling, and has a better repr:

class Sentinel:
def __new__(cls, *args, **kwargs):
raise TypeError(f'{cls.__qualname__} cannot be instantiated')

class MISSING(Sentinel):
pass

- Tal
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/URFRF634732GRICGLRPGJEJON2BYQZM4/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 5/13/2021 12:41 PM, Ethan Furman wrote:
>>>>>
> On 5/13/21 2:15 AM, Irit Katriel via Python-Dev wrote:
>>
>>      >>> help(traceback.print_exception)
>>      Help on function print_exception in module traceback:
>>
>>      print_exception(exc, /, value=<object object at
>> 0x000002825DF09650>, tb=<object object
>>                      at 0x000002825DF09650>, limit=None, file=None,
>> chain=True)
>
>
> On 5/13/21 5:37 AM, Eric V. Smith wrote:
>>
>> The help looks like:
>>
>>      field(*, default=<dataclasses._MISSING_TYPE object at
>> 0x6fffffe46610>,
>>            default_factory=<dataclasses._MISSING_TYPE object at
>> 0x6fffffe46610>,
>>            init=True, repr=True, hash=None, compare=True, metadata=None)
>>
>> None of this is particularly awesome, but no one has complained about
>> it yet.
>
> Consider me complaining.  ;-)
>
Your complaint is hereby noted!

> Looks to me like the default repr for the sentinels is making those
> helps much less helpful by showing totally irrelevant information and
> cluttering up the screen making it harder to see the actually useful
> bits.
>
> An actual Sentinel class would be helpful:
>
>    >>> class Sentinel:
>    ...     def __init__(self, repr):
>    ...         self.repr = repr
>    ...     def __repr__(self):
>    ...         return self.repr
>    ...
>
>    >>> MISSING = Sentinel('MISSING')
>    >>> MISSING
>    MISSING
>
dataclasses.py actually has similar code, but for some reason I guess it
got missed for MISSING (ha!).
>
> Naturally, since sentinels are symbolic names, I think it should go
> into the enum module.  ;-)  Although I will concede that we could just
> put those five lines into the modules that need it.

Yeah, it's probably not worth dataclasses importing enum just to get
that functionality.

Eric

_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/45RGCWZJSS7WDWCYUEDL72NNHSYGGLCU/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 5/13/2021 1:39 PM, Tal Einat wrote:
> On Thu, May 13, 2021 at 7:44 PM Ethan Furman <ethan@stoneleaf.us> wrote:
>> Consider me complaining. ;-)
> +1
>
>> An actual Sentinel class would be helpful:
>>
>> >>> class Sentinel:
>> ... def __init__(self, repr):
>> ... self.repr = repr
>> ... def __repr__(self):
>> ... return self.repr
>> ...
>>
>> >>> MISSING = Sentinel('MISSING')
>> >>> MISSING
>> MISSING
>>
>> >>> implicit = Sentinel('<implicit>')
>> >>> implicit
>> <implicit>
> Here is my suggestion (also posted on the related bpo-44123), which is
> also simple, ensures a single instance is used, even considering
> multi-threading and pickling, and has a better repr:
>
> class Sentinel:
> def __new__(cls, *args, **kwargs):
> raise TypeError(f'{cls.__qualname__} cannot be instantiated')
>
> class MISSING(Sentinel):
> pass

>>> MISSING
<class '__main__.MISSING'>

I think a repr of just "MISSING", or maybe "dataclasses.MISSING" would
be better.

Eric

_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/EQZV3KZZQNBN6NQRIERUK5HOEAUW2DUJ/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 5/13/21 10:46 AM, Eric V. Smith wrote:
> >>> MISSING
> <class '__main__.MISSING'>
>
> I think a repr of just "MISSING", or maybe "dataclasses.MISSING" would
> be better.


I literally just went down this road--for a while there was a special
sentinel value for the eval_str parameter to inspect.get_annotations(). 
The repr I went with was "<id>", e.g "<MISSING>".  It depends on how
seriously you take the idea that eval(repr(x)) == x.  Certainly most
objects don't actually support that, e.g., uh, object(), a type which I
understand is available in most Python implementations.


Cheers,


//arry/
Re: The repr of a sentinel [ In reply to ]
On Thu, May 13, 2021 at 8:46 PM Eric V. Smith <eric@trueblade.com> wrote:
>
>
> On 5/13/2021 1:39 PM, Tal Einat wrote:
> > Here is my suggestion (also posted on the related bpo-44123), which is
> > also simple, ensures a single instance is used, even considering
> > multi-threading and pickling, and has a better repr:
> >
> > class Sentinel:
> > def __new__(cls, *args, **kwargs):
> > raise TypeError(f'{cls.__qualname__} cannot be instantiated')
> >
> > class MISSING(Sentinel):
> > pass
>
> >>> MISSING
> <class '__main__.MISSING'>
>
> I think a repr of just "MISSING", or maybe "dataclasses.MISSING" would
> be better.

The repr uses whatever module the class is defined in, so we'd get:

>>> from dataclasses import MISSING
>>> MISSING
<class 'dataclasses.MISSING'>

We could override that to something even cleaner with a meta-class. For example:

class Sentinel(type):
@classmethod
def __prepare__(cls, name, bases, **kwds):
d = super().__prepare__(name, bases, **kwds)
def __new__(cls_, *args, **kwargs):
raise TypeError(f'{cls_!r} is a sentinel and cannot be
instantiated')
d.update(__new__=__new__)
return d

def __repr__(cls):
return f'{cls.__module__}.{cls.__qualname__}'

Which results in:

>>> from dataclasses import MISSING
>>> MISSING
dataclasses.MISSING
>>> type(MISSING)
<class 'sentinels.Sentinel'>
>>> MISSING()
Traceback (most recent call last): ...
TypeError: dataclasses.MISSING is a sentinel and cannot be instantiated

- Tal


- Tal
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/ECYFQWPBQPRN4ZKDU6WNPPAG3Y5XZ2BD/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
There was a discussion a while back ( a year or so?? ) on Python-ideas that
introduced the idea of having more "sentinel-like" singletons in Python --
right now, we only have None.

I can't remember the context, but the consensus seemed to be that it
was easy to create a custom sentinel object, and it was not worth adding
more "official" ones to the language. But this conversation reminded me
about that, and while I do agree that we don't need more that are elevated
to the status of None, maybe it would be good to have a couple (or only
MISSING) in the standard library somewhere "central" for everyone to use.
"central" rather than in, say, dataclasses.

I'm not sure where that should be, the operator module, maybe??

Ayway, if someone were to put one of the nifty implementations being
discussed here in the stdlib --I'd use it :-)

-CHB




On Thu, May 13, 2021 at 1:14 PM Tal Einat <taleinat@gmail.com> wrote:

> On Thu, May 13, 2021 at 8:46 PM Eric V. Smith <eric@trueblade.com> wrote:
> >
> >
> > On 5/13/2021 1:39 PM, Tal Einat wrote:
> > > Here is my suggestion (also posted on the related bpo-44123), which is
> > > also simple, ensures a single instance is used, even considering
> > > multi-threading and pickling, and has a better repr:
> > >
> > > class Sentinel:
> > > def __new__(cls, *args, **kwargs):
> > > raise TypeError(f'{cls.__qualname__} cannot be instantiated')
> > >
> > > class MISSING(Sentinel):
> > > pass
> >
> > >>> MISSING
> > <class '__main__.MISSING'>
> >
> > I think a repr of just "MISSING", or maybe "dataclasses.MISSING" would
> > be better.
>
> The repr uses whatever module the class is defined in, so we'd get:
>
> >>> from dataclasses import MISSING
> >>> MISSING
> <class 'dataclasses.MISSING'>
>
> We could override that to something even cleaner with a meta-class. For
> example:
>
> class Sentinel(type):
> @classmethod
> def __prepare__(cls, name, bases, **kwds):
> d = super().__prepare__(name, bases, **kwds)
> def __new__(cls_, *args, **kwargs):
> raise TypeError(f'{cls_!r} is a sentinel and cannot be
> instantiated')
> d.update(__new__=__new__)
> return d
>
> def __repr__(cls):
> return f'{cls.__module__}.{cls.__qualname__}'
>
> Which results in:
>
> >>> from dataclasses import MISSING
> >>> MISSING
> dataclasses.MISSING
> >>> type(MISSING)
> <class 'sentinels.Sentinel'>
> >>> MISSING()
> Traceback (most recent call last): ...
> TypeError: dataclasses.MISSING is a sentinel and cannot be instantiated
>
> - Tal
>
>
> - Tal
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-dev@python.org/message/ECYFQWPBQPRN4ZKDU6WNPPAG3Y5XZ2BD/
> Code of Conduct: http://python.org/psf/codeofconduct/
>


--
Christopher Barker, PhD (Chris)

Python Language Consulting
- Teaching
- Scientific Software Development
- Desktop GUI and Web Development
- wxPython, numpy, scipy, Cython
Re: The repr of a sentinel [ In reply to ]
On Thu, May 13, 2021 at 10:31 PM Christopher Barker <pythonchb@gmail.com>
wrote:

> There was a discussion a while back ( a year or so?? ) on Python-ideas
> that introduced the idea of having more "sentinel-like" singletons in
> Python -- right now, we only have None.
>

As I remember, the year-ago conversation was basically wanting more ways of
saying "argument not specified" in function signatures. The thought was
that None is used pretty often to mean something somewhat different. The
rough consensus seemed to be that `my_sentinel = object()` was a low burden
per project.

But in what I recall, there was no talk there of custom behavior like a
nicer repr(). My own feeling is that ONLY a repr() isn't quite enough to
motivate an addition to stdlib. But if there were a few other useful
behaviors, maybe it would be (e.g. maybe default or specifiable inequality
operations). However, I wouldn't really want another two or three or four
singletons, but rather a slightly more templated factory than just
`object()`.

--
The dead increasingly dominate and strangle both the living and the
not-yet born. Vampiric capital and undead corporate persons abuse
the lives and control the thoughts of homo faber. Ideas, once born,
become abortifacients against new conceptions.
Re: The repr of a sentinel [ In reply to ]
> There was a discussion a while back ( a year or so?? ) on Python-ideas
> that introduced the idea of having more "sentinel-like" singletons in
> Python -- right now, we only have None.
>

Not quite true, we also have Ellipsis, which already has a nice repr that
both reads easily and still follows the convention of eval(repr(x)) == x.
It also is already safe from instantiation, survives pickle round-trip and
is multi-thread safe.
So long as you are not dealing with scientific projects, it seems a
quick (if dirty) solution to having a sentinel that is not None. There is
also some symmetrical wholeness when considered with the other builtin
sentinels: bool(None) is False; bool(Ellipsis) is True.
Re: The repr of a sentinel [ In reply to ]
Hi Tal,

Would it make sense to have an unique singleton for such sentinel, a
built-in singleton like None or Ellipsis? I propose the name
"Sentinel".

Sentinel would be similar to None, but the main property would be that
"Sentinel is None" is false :-)

The stdlib contains tons of sentinels:

* _collections_abc: __marker__
* cgitb.__UNDEF__
* configparser: _UNSET
* dataclasses: _HAS_DEFAULT_FACTORY, MISSING, KW_ONLY
* datetime.timezone._Omitted
* fnmatch.translate() STAR
* functools.lru_cache.sentinel (each @lru_cache creates its own sentinel object)
* functools._NOT_FOUND
* heapq: temporary sentinel in nsmallest() and nlargest()
* inspect._sentinel
* inspect._signature_fromstr() invalid
* plistlib._undefined
* runpy._ModifiedArgv0._sentinel
* sched: _sentinel
* traceback: _sentinel

There are different but similar use cases:

* Optional parameter: distinguish between func() and func(arg=value),
a sentinel is useful to distinguish func() from func(arg=None)
* Look into a data structure for a value and store the result in a
value, distinguish if 'result' variable was set ("result is not None"
doesn't work since None is a value). Quick example: "missing =
object(); tmsg = self._catalog.get(message, missing); if tmsg is
missing: ..."

Special cases:

* dataclases._EMPTY_METADATA = types.MappingProxyType({})
* string._sentinel_dict = {}
* enum: _auto_null = object()

Victor

On Thu, May 13, 2021 at 7:40 PM Tal Einat <taleinat@gmail.com> wrote:
>
> On Thu, May 13, 2021 at 7:44 PM Ethan Furman <ethan@stoneleaf.us> wrote:
> >
> > Consider me complaining. ;-)
>
> +1
>
> > An actual Sentinel class would be helpful:
> >
> > >>> class Sentinel:
> > ... def __init__(self, repr):
> > ... self.repr = repr
> > ... def __repr__(self):
> > ... return self.repr
> > ...
> >
> > >>> MISSING = Sentinel('MISSING')
> > >>> MISSING
> > MISSING
> >
> > >>> implicit = Sentinel('<implicit>')
> > >>> implicit
> > <implicit>
>
> Here is my suggestion (also posted on the related bpo-44123), which is
> also simple, ensures a single instance is used, even considering
> multi-threading and pickling, and has a better repr:
>
> class Sentinel:
> def __new__(cls, *args, **kwargs):
> raise TypeError(f'{cls.__qualname__} cannot be instantiated')
>
> class MISSING(Sentinel):
> pass
>
> - Tal
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/URFRF634732GRICGLRPGJEJON2BYQZM4/
> Code of Conduct: http://python.org/psf/codeofconduct/



--
Night gathers, and now my watch begins. It shall not end until my death.
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/JBYXQH3NV3YBF7P2HLHB5CD6V3GVTY55/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 14. 05. 21 10:55, Victor Stinner wrote:
> Hi Tal,
>
> Would it make sense to have an unique singleton for such sentinel, a
> built-in singleton like None or Ellipsis? I propose the name
> "Sentinel".
>
> Sentinel would be similar to None, but the main property would be that
> "Sentinel is None" is false :-)

If you need your Sentinel to be different from one particular sentinel
(None), you'll usually want it to be different from all the other ones
as well.

A sentinel for an optional parameter shouldn't really be used at all
outside of the function it's defined for. That's why it's usually
defined as a private module-level variable.

Perhaps it would be beneficial to provide a common base class or
factory, so we get a good repr. But I don't think another common value
like None and Ellipsis would do much good.


> The stdlib contains tons of sentinels:
>
> * _collections_abc: __marker__
> * cgitb.__UNDEF__
> * configparser: _UNSET
> * dataclasses: _HAS_DEFAULT_FACTORY, MISSING, KW_ONLY
> * datetime.timezone._Omitted
> * fnmatch.translate() STAR
> * functools.lru_cache.sentinel (each @lru_cache creates its own sentinel object)
> * functools._NOT_FOUND
> * heapq: temporary sentinel in nsmallest() and nlargest()
> * inspect._sentinel
> * inspect._signature_fromstr() invalid
> * plistlib._undefined
> * runpy._ModifiedArgv0._sentinel
> * sched: _sentinel
> * traceback: _sentinel
>
> There are different but similar use cases:
>
> * Optional parameter: distinguish between func() and func(arg=value),
> a sentinel is useful to distinguish func() from func(arg=None)
> * Look into a data structure for a value and store the result in a
> value, distinguish if 'result' variable was set ("result is not None"
> doesn't work since None is a value). Quick example: "missing =
> object(); tmsg = self._catalog.get(message, missing); if tmsg is
> missing: ..."
>
> Special cases:
>
> * dataclases._EMPTY_METADATA = types.MappingProxyType({})
> * string._sentinel_dict = {}
> * enum: _auto_null = object()
>
> Victor
>
> On Thu, May 13, 2021 at 7:40 PM Tal Einat <taleinat@gmail.com> wrote:
>>
>> On Thu, May 13, 2021 at 7:44 PM Ethan Furman <ethan@stoneleaf.us> wrote:
>>>
>>> Consider me complaining. ;-)
>>
>> +1
>>
>>> An actual Sentinel class would be helpful:
>>>
>>> >>> class Sentinel:
>>> ... def __init__(self, repr):
>>> ... self.repr = repr
>>> ... def __repr__(self):
>>> ... return self.repr
>>> ...
>>>
>>> >>> MISSING = Sentinel('MISSING')
>>> >>> MISSING
>>> MISSING
>>>
>>> >>> implicit = Sentinel('<implicit>')
>>> >>> implicit
>>> <implicit>
>>
>> Here is my suggestion (also posted on the related bpo-44123), which is
>> also simple, ensures a single instance is used, even considering
>> multi-threading and pickling, and has a better repr:
>>
>> class Sentinel:
>> def __new__(cls, *args, **kwargs):
>> raise TypeError(f'{cls.__qualname__} cannot be instantiated')
>>
>> class MISSING(Sentinel):
>> pass
>>
>> - Tal
>> _______________________________________________
>> Python-Dev mailing list -- python-dev@python.org
>> To unsubscribe send an email to python-dev-leave@python.org
>> https://mail.python.org/mailman3/lists/python-dev.python.org/
>> Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/URFRF634732GRICGLRPGJEJON2BYQZM4/
>> Code of Conduct: http://python.org/psf/codeofconduct/
>
>
>
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/SJ45ED57TNPLFCWXAREUGRKSPTPPJYJI/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Fri, May 14, 2021 at 7:31 PM Petr Viktorin <encukou@gmail.com> wrote:
> Perhaps it would be beneficial to provide a common base class or
> factory, so we get a good repr. But I don't think another common value
> like None and Ellipsis would do much good.
>

Agreed - I think Sentinel would make a great class, from which you
instantiate purpose-specific sentinels.

But maybe there really needs to be a way to NOT specify an argument,
and to find out that an argument wasn't specified? Defaulting is only
one way to handle it. Consider:

def truly_optional_arg(x, y, *z):
"""Two mandatory args, and then an optional one"""
if z: print("Got an extra arg")

pass_arg = input("Pass the arg? ") == "y"
truly_optional_arg(10, 20, *([30] if pass_arg else []))

It's horrifically ugly, but it really and truly does/doesn't pass that
argument, and it really and truly detects whether one was passed. To
make that sort of thing actually viable, there'd need to be some sort
of language support; maybe something where the local name would start
out unbound, but testing for a local's boundness is a clunky
try/except, so that might also need some support.

Do we ever really need the ability to pass a specific sentinel to a
function, or are we actually looking for a way to say "and don't pass
this argument"?

ChrisA
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/VSKX4PV4URDMNAJ6AYW3H3XKI4XWPJNS/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 14May2021 0622, micro codery wrote:
>
> There was a discussion a while back ( a year or so?? ) on
> Python-ideas that introduced the idea of having more "sentinel-like"
> singletons in Python -- right now, we only have None.
>
> Not quite true, we also have Ellipsis, which already has a nice repr
> that both reads easily and still follows the convention of eval(repr(x))
> == x. It also is already safe from instantiation, survives pickle
> round-trip and is multi-thread safe.
> So long as you are not dealing with scientific projects, it seems a
> quick (if dirty) solution to having a sentinel that is not None.

I don't think using "..." to indicate "some currently unknown or
unspecified value" is dirty at all, it seems perfectly consistent with
how we use it in English (and indexing in scientific projects, for that
matter, where it tends to imply "figure out the rest for me").

All that's really missing is some kind of endorsement (from python-dev,
presumably in the docs) that it's okay to use it as a default parameter
value. I can't think of any reason you'd need to accept Ellipsis as a
*specified* value that wouldn't also apply to any other kind of shared
sentinel.

Cheers,
Steve
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/VCKTZF45OAFYNIOL5IVNI5HG34BGJBEA/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On May 14, 2021, at 02:38, Chris Angelico <rosuav@gmail.com> wrote:
>
> Do we ever really need the ability to pass a specific sentinel to a
> function, or are we actually looking for a way to say "and don't pass
> this argument”?

Very often, that’s the case. Such a “it’s okay to not pass this argument” construct would have to work with the Optional type too.

The other use case I have for a special case single use singleton is for dict.get(), i.e.

missing = object()
value = somedict.get(‘key’, missing)
if value is missing:
# It ain’t there.

-Barry
Re: The repr of a sentinel [ In reply to ]
On Sat, May 15, 2021 at 2:04 AM Barry Warsaw <barry@python.org> wrote:
>
> On May 14, 2021, at 02:38, Chris Angelico <rosuav@gmail.com> wrote:
> >
> > Do we ever really need the ability to pass a specific sentinel to a
> > function, or are we actually looking for a way to say "and don't pass
> > this argument”?
>
> Very often, that’s the case. Such a “it’s okay to not pass this argument” construct would have to work with the Optional type too.

What I mean is: how often do you actually need to pass the specific
sentinel, as opposed to the normal construct of asking if the argument
was or wasn't passed? Eg if you have a function like this:

_sentinel = object()
def func(x, y=_sentinel):
if y is _sentinel: ...
else: ...

Would you ever call it like this:

func(42, _sentinel)

? Because if there's no reason to ever pass the sentinel, then it's
nothing more than an implementation detail for the concept of "was
this argument passed?", and that's exactly what I'm asking about.

> The other use case I have for a special case single use singleton is for dict.get(), i.e.
>
> missing = object()
> value = somedict.get(‘key’, missing)
> if value is missing:
> # It ain’t there.
>

I'd write that one with a try/except instead. The whole point of get()
is to provide the missing value.

try:
value = somedict['key']
except KeyError:
# It ain't there.

No sentinel needed. Same number of lines.

ChrisA
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/TMZWXDXJSVXTEKJNXYDMQNHTFC6ZXIOH/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Fri, May 14, 2021 at 12:45 PM Steve Dower <steve.dower@python.org> wrote:
>
> On 14May2021 0622, micro codery wrote:
> >
> > There was a discussion a while back ( a year or so?? ) on
> > Python-ideas that introduced the idea of having more "sentinel-like"
> > singletons in Python -- right now, we only have None.
> >
> > Not quite true, we also have Ellipsis, which already has a nice repr
> > that both reads easily and still follows the convention of eval(repr(x))
> > == x. It also is already safe from instantiation, survives pickle
> > round-trip and is multi-thread safe.
> > So long as you are not dealing with scientific projects, it seems a
> > quick (if dirty) solution to having a sentinel that is not None.
>
> I don't think using "..." to indicate "some currently unknown or
> unspecified value" is dirty at all, it seems perfectly consistent with
> how we use it in English (and indexing in scientific projects, for that
> matter, where it tends to imply "figure out the rest for me").
>
> All that's really missing is some kind of endorsement (from python-dev,
> presumably in the docs) that it's okay to use it as a default parameter
> value. I can't think of any reason you'd need to accept Ellipsis as a
> *specified* value that wouldn't also apply to any other kind of shared
> sentinel.

I'll try to organize my thoughts a bit here. This is a bit long,
welcome to skip to the final sentence for the "tl;dr".

Features one may want for a sentinel:
1. Unique from other objects
2. Globally unique, i.e. unique from other such sentinels (no consensus here)
3. Clear repr (the original subject of this thread) - significant
since these often appear in function signatures
4. Survives pickling round-trip (I brought this up since I was bitten
by this once, but others have mentioned that this is usually
irrelevant)
5. Can be given a clear type signature (this was brought up on
twitter[1]) - significant since without this nobody can add full type
signatures even if they want to

The common `SENTINEL = object()` idiom fails #3, #4 and #5. This is
what I've been using for years, and I now think that it isn't good
enough. This not having a nice repr is what started this thread.

I'd also personally prefer something simple, ideally without a new
class or module.

There are several simple idioms using existing features that seem like
good options, so let's review those:

1. Ellipsis, a.k.a. `...`. This has all of the features outlined above
except #2. My main issue with using Ellipsis for this is that it could
be surprising and confusing for devs first encountering such use, and
could be relatively awkward to figure out.

2. An instance of a one-off class. dataclasses.MISSING is an example
of this. It is defined thus:

class _MISSING:
pass
MISSING = _MISSING()

Besides failing #4 (surviving pickle round-trips), its repr isn't
great: <dataclasses._MISSING object at 0x7fe14b1e2e80>. That is easily
overcome by implementing __repr__.

3. A one-off class:

class MISSING: pass

This has all of the above features except #5: having a clear type
signature (since its type is type). Using a class as a value this way
could be surprising, though. It's repr also isn't great: <class
'dataclasses._MISSING'>.

4. A value of an single-valued enum, for example (from [1]):

class _UNSET(enum.Enum):
token = enum.auto()

This has all of the features above and is simple, just requiring a
comment to explain what it is. It's repr is a bit awkward though:

>>> repr(_UNSET.token)
'<_UNSET.token: 1>'


All of these are in use by some developers, though not necessarily in
the stdlib. None is perfect, though all are probably good enough.
Since pickling is likely not relevant in most cases, I'm currently in
favor of #2 making sure to implement a nice __repr__.

- Tal

[1] https://twitter.com/nolar/status/1392962447166877697
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/BYLTDD72RZUUKVRJL6TTSWF35JD3FL47/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
If we drop the requirement for pickle round-tripping then I would add a
requirement that sentinel is unpicklable, to prevent accidents.

Irit

On Fri, May 14, 2021 at 8:38 PM Tal Einat <taleinat@gmail.com> wrote:

>
> I'll try to organize my thoughts a bit here. This is a bit long,
> welcome to skip to the final sentence for the "tl;dr".
>
> Features one may want for a sentinel:
> 1. Unique from other objects
> 2. Globally unique, i.e. unique from other such sentinels (no consensus
> here)
> 3. Clear repr (the original subject of this thread) - significant
> since these often appear in function signatures
> 4. Survives pickling round-trip (I brought this up since I was bitten
> by this once, but others have mentioned that this is usually
> irrelevant)
> 5. Can be given a clear type signature (this was brought up on
> twitter[1]) - significant since without this nobody can add full type
> signatures even if they want to
>
> The common `SENTINEL = object()` idiom fails #3, #4 and #5. This is
> what I've been using for years, and I now think that it isn't good
> enough. This not having a nice repr is what started this thread.
>
> I'd also personally prefer something simple, ideally without a new
> class or module.
>
> There are several simple idioms using existing features that seem like
> good options, so let's review those:
>
> 1. Ellipsis, a.k.a. `...`. This has all of the features outlined above
> except #2. My main issue with using Ellipsis for this is that it could
> be surprising and confusing for devs first encountering such use, and
> could be relatively awkward to figure out.
>
> 2. An instance of a one-off class. dataclasses.MISSING is an example
> of this. It is defined thus:
>
> class _MISSING:
> pass
> MISSING = _MISSING()
>
> Besides failing #4 (surviving pickle round-trips), its repr isn't
> great: <dataclasses._MISSING object at 0x7fe14b1e2e80>. That is easily
> overcome by implementing __repr__.
>
> 3. A one-off class:
>
> class MISSING: pass
>
> This has all of the above features except #5: having a clear type
> signature (since its type is type). Using a class as a value this way
> could be surprising, though. It's repr also isn't great: <class
> 'dataclasses._MISSING'>.
>
> 4. A value of an single-valued enum, for example (from [1]):
>
> class _UNSET(enum.Enum):
> token = enum.auto()
>
> This has all of the features above and is simple, just requiring a
> comment to explain what it is. It's repr is a bit awkward though:
>
> >>> repr(_UNSET.token)
> '<_UNSET.token: 1>'
>
>
> All of these are in use by some developers, though not necessarily in
> the stdlib. None is perfect, though all are probably good enough.
> Since pickling is likely not relevant in most cases, I'm currently in
> favor of #2 making sure to implement a nice __repr__.
>
> - Tal
>
> [1] https://twitter.com/nolar/status/1392962447166877697
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-dev@python.org/message/BYLTDD72RZUUKVRJL6TTSWF35JD3FL47/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
Re: The repr of a sentinel [ In reply to ]
I think it's more future-looking to allow pickle round-tripping. Just add a
._uuid attribute and have object equality follow equality of that
attribute. There's no reason to expose that in the .__repr__, but it would
be inspectable in concept.

On Fri, May 14, 2021, 7:01 PM Irit Katriel via Python-Dev <
python-dev@python.org> wrote:

>
> If we drop the requirement for pickle round-tripping then I would add a
> requirement that sentinel is unpicklable, to prevent accidents.
>
> Irit
>
> On Fri, May 14, 2021 at 8:38 PM Tal Einat <taleinat@gmail.com> wrote:
>
>>
>> I'll try to organize my thoughts a bit here. This is a bit long,
>> welcome to skip to the final sentence for the "tl;dr".
>>
>> Features one may want for a sentinel:
>> 1. Unique from other objects
>> 2. Globally unique, i.e. unique from other such sentinels (no consensus
>> here)
>> 3. Clear repr (the original subject of this thread) - significant
>> since these often appear in function signatures
>> 4. Survives pickling round-trip (I brought this up since I was bitten
>> by this once, but others have mentioned that this is usually
>> irrelevant)
>> 5. Can be given a clear type signature (this was brought up on
>> twitter[1]) - significant since without this nobody can add full type
>> signatures even if they want to
>>
>> The common `SENTINEL = object()` idiom fails #3, #4 and #5. This is
>> what I've been using for years, and I now think that it isn't good
>> enough. This not having a nice repr is what started this thread.
>>
>> I'd also personally prefer something simple, ideally without a new
>> class or module.
>>
>> There are several simple idioms using existing features that seem like
>> good options, so let's review those:
>>
>> 1. Ellipsis, a.k.a. `...`. This has all of the features outlined above
>> except #2. My main issue with using Ellipsis for this is that it could
>> be surprising and confusing for devs first encountering such use, and
>> could be relatively awkward to figure out.
>>
>> 2. An instance of a one-off class. dataclasses.MISSING is an example
>> of this. It is defined thus:
>>
>> class _MISSING:
>> pass
>> MISSING = _MISSING()
>>
>> Besides failing #4 (surviving pickle round-trips), its repr isn't
>> great: <dataclasses._MISSING object at 0x7fe14b1e2e80>. That is easily
>> overcome by implementing __repr__.
>>
>> 3. A one-off class:
>>
>> class MISSING: pass
>>
>> This has all of the above features except #5: having a clear type
>> signature (since its type is type). Using a class as a value this way
>> could be surprising, though. It's repr also isn't great: <class
>> 'dataclasses._MISSING'>.
>>
>> 4. A value of an single-valued enum, for example (from [1]):
>>
>> class _UNSET(enum.Enum):
>> token = enum.auto()
>>
>> This has all of the features above and is simple, just requiring a
>> comment to explain what it is. It's repr is a bit awkward though:
>>
>> >>> repr(_UNSET.token)
>> '<_UNSET.token: 1>'
>>
>>
>> All of these are in use by some developers, though not necessarily in
>> the stdlib. None is perfect, though all are probably good enough.
>> Since pickling is likely not relevant in most cases, I'm currently in
>> favor of #2 making sure to implement a nice __repr__.
>>
>> - Tal
>>
>> [1] https://twitter.com/nolar/status/1392962447166877697
>> _______________________________________________
>> Python-Dev mailing list -- python-dev@python.org
>> To unsubscribe send an email to python-dev-leave@python.org
>> https://mail.python.org/mailman3/lists/python-dev.python.org/
>> Message archived at
>> https://mail.python.org/archives/list/python-dev@python.org/message/BYLTDD72RZUUKVRJL6TTSWF35JD3FL47/
>> Code of Conduct: http://python.org/psf/codeofconduct/
>>
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-dev@python.org/message/7I7S22KEOZY2MDDIYCUPQWG6WXLFZZR5/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
Re: The repr of a sentinel [ In reply to ]
On Sat, 15 May 2021, 5:39 am Tal Einat, <taleinat@gmail.com> wrote:

(snip useful feature summary)

The common `SENTINEL = object()` idiom fails #3, #4 and #5. This is
> what I've been using for years, and I now think that it isn't good
> enough. This not having a nice repr is what started this thread.
>
> I'd also personally prefer something simple, ideally without a new
> class or module.
>

The key advantage of a new base class is that it offers a way to
communicate a shift in the recommended development idiom. "sentinel =
object()" is entrenched enough that I would expect that only a
"types.Sentinel" base class would stand a good chance of displacing it.

Why the "types" module? I don't think this is important enough to be a
builtin type, and the types module is cheap to import, doesn't bring in
many transitive dependencies, and the purpose of the new base class would
be defining custom sentinel types with various desirable properties.

Cheers,
Nick.

>


>
Re: The repr of a sentinel [ In reply to ]
Since the subject is this,
I will note that past week, I resorted twice to create an Enum with a
single element,
and then alias the element on the module namespace, so that it would work
as a "polished" sentinel.

So:

import enum

class Whatever(enum.Enum):
EMPTY = "EMPTY"

EMPTY = Whatever.EMPTY

A "repeat yourself three times" - certainly not that nice.
But its use was a bit off the ones listed here in a sense these
are meant to be explicitly passed to some methods (rather,
placed inside data structures) to indicate a desired behavior.



On Fri, 14 May 2021 at 21:03, Nick Coghlan <ncoghlan@gmail.com> wrote:

> On Sat, 15 May 2021, 5:39 am Tal Einat, <taleinat@gmail.com> wrote:
>
> (snip useful feature summary)
>
> The common `SENTINEL = object()` idiom fails #3, #4 and #5. This is
>> what I've been using for years, and I now think that it isn't good
>> enough. This not having a nice repr is what started this thread.
>>
>> I'd also personally prefer something simple, ideally without a new
>> class or module.
>>
>
> The key advantage of a new base class is that it offers a way to
> communicate a shift in the recommended development idiom. "sentinel =
> object()" is entrenched enough that I would expect that only a
> "types.Sentinel" base class would stand a good chance of displacing it.
>
> Why the "types" module? I don't think this is important enough to be a
> builtin type, and the types module is cheap to import, doesn't bring in
> many transitive dependencies, and the purpose of the new base class would
> be defining custom sentinel types with various desirable properties.
>
> Cheers,
> Nick.
>
>>
>
>
>> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-dev@python.org/message/V67I4A6WX6INEAGGSJWYFX4WKQHA73NW/
> Code of Conduct: http://python.org/psf/codeofconduct/
>
Re: The repr of a sentinel [ In reply to ]
I think that would be the primary motivating factor behind recommending
Ellipsis, it’s already a builtin and we are not likely it to get another
builtin singleton. Ever? But besides that “...” in a function signature,
although maybe looking magical, does immediately call out to the reader
that something special is happening here. And for beginners who might not
know that three dots are an Ellipsis or what an Ellipsis even is, it would
be fairly easy to internet search “three dots in a function signature” or
even “python def ...” would probably be enough to surface an explanation if
this became somewhat common.
Re: The repr of a sentinel [ In reply to ]
To add to the suggestions already given in this thread I dug into code I wrote some time ago.
Offered as an inspiration.


=== missing.py ===

from typing import Any


def MISSING(klass: Any) -> Any:
"""
create a sentinel to indicate a missing instance of a class
:param klass: the class of which an instance is missing
:return: missing class object
"""
g = globals()
missing_klass_name = f"_MISSING_{klass.__module__}_{klass.__name__}_MISSING_"
if missing_klass_name not in g:
g[missing_klass_name] = type(
missing_klass_name,
(klass,),
{
"__repr__": lambda x: f"MISSING({klass.__name__})",
}
)
return g[missing_klass_name]()

===


and as a demo:

=== demo_missing.py ===

import pickle


from missing import MISSING


x = MISSING(str)
y = "bar"
print(f"{x!r} == {y!r}: {x == y}")
print(f"{x!r} is {y!r}: {x is y}")
# MISSING(str) == 'bar': False
# MISSING(str) is 'bar': False

with open("object.pickled", "wb") as f:
pickle.dump(x, f)
with open("object.pickled", "rb") as f:
y = pickle.load(f)
print(f"{x!r} == {y!r}: {x == y}")
print(f"{x!r} is {y!r}: {x is y}")
# MISSING(str) == MISSING(str): True
# MISSING(str) is MISSING(str): False


def foo(a: int = MISSING(int), b: int = MISSING(int)):
print(f"{a=} {isinstance(a, int)}")
print(f"{b=} {isinstance(b, int)}")
print(f"{a!r} == {b!r}: {a == b}")
print(f"{a!r} is {b!r}: {a is b}")


foo()
# a=MISSING(int) True
# b=MISSING(int) True
# MISSING(int) == MISSING(int): True
# MISSING(int) is MISSING(int): False

foo(1)
# a=1 True
# b=MISSING(int) True
# 1 == MISSING(int): False
# 1 is MISSING(int): False


class Test:
...


t = MISSING(Test)
print(f"{t=}")
# t=MISSING(Test)

===
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/42O33BFRJLDFVKX4XSKMZ6VLR7H7GXKP/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
I've created a poll on this subject:
https://discuss.python.org/t/sentinel-values-in-the-stdlib/8810

On Fri, May 14, 2021 at 10:33 PM Tal Einat <taleinat@gmail.com> wrote:
>
> On Fri, May 14, 2021 at 12:45 PM Steve Dower <steve.dower@python.org> wrote:
> >
> > On 14May2021 0622, micro codery wrote:
> > >
> > > There was a discussion a while back ( a year or so?? ) on
> > > Python-ideas that introduced the idea of having more "sentinel-like"
> > > singletons in Python -- right now, we only have None.
> > >
> > > Not quite true, we also have Ellipsis, which already has a nice repr
> > > that both reads easily and still follows the convention of eval(repr(x))
> > > == x. It also is already safe from instantiation, survives pickle
> > > round-trip and is multi-thread safe.
> > > So long as you are not dealing with scientific projects, it seems a
> > > quick (if dirty) solution to having a sentinel that is not None.
> >
> > I don't think using "..." to indicate "some currently unknown or
> > unspecified value" is dirty at all, it seems perfectly consistent with
> > how we use it in English (and indexing in scientific projects, for that
> > matter, where it tends to imply "figure out the rest for me").
> >
> > All that's really missing is some kind of endorsement (from python-dev,
> > presumably in the docs) that it's okay to use it as a default parameter
> > value. I can't think of any reason you'd need to accept Ellipsis as a
> > *specified* value that wouldn't also apply to any other kind of shared
> > sentinel.
>
> I'll try to organize my thoughts a bit here. This is a bit long,
> welcome to skip to the final sentence for the "tl;dr".
>
> Features one may want for a sentinel:
> 1. Unique from other objects
> 2. Globally unique, i.e. unique from other such sentinels (no consensus here)
> 3. Clear repr (the original subject of this thread) - significant
> since these often appear in function signatures
> 4. Survives pickling round-trip (I brought this up since I was bitten
> by this once, but others have mentioned that this is usually
> irrelevant)
> 5. Can be given a clear type signature (this was brought up on
> twitter[1]) - significant since without this nobody can add full type
> signatures even if they want to
>
> The common `SENTINEL = object()` idiom fails #3, #4 and #5. This is
> what I've been using for years, and I now think that it isn't good
> enough. This not having a nice repr is what started this thread.
>
> I'd also personally prefer something simple, ideally without a new
> class or module.
>
> There are several simple idioms using existing features that seem like
> good options, so let's review those:
>
> 1. Ellipsis, a.k.a. `...`. This has all of the features outlined above
> except #2. My main issue with using Ellipsis for this is that it could
> be surprising and confusing for devs first encountering such use, and
> could be relatively awkward to figure out.
>
> 2. An instance of a one-off class. dataclasses.MISSING is an example
> of this. It is defined thus:
>
> class _MISSING:
> pass
> MISSING = _MISSING()
>
> Besides failing #4 (surviving pickle round-trips), its repr isn't
> great: <dataclasses._MISSING object at 0x7fe14b1e2e80>. That is easily
> overcome by implementing __repr__.
>
> 3. A one-off class:
>
> class MISSING: pass
>
> This has all of the above features except #5: having a clear type
> signature (since its type is type). Using a class as a value this way
> could be surprising, though. It's repr also isn't great: <class
> 'dataclasses._MISSING'>.
>
> 4. A value of an single-valued enum, for example (from [1]):
>
> class _UNSET(enum.Enum):
> token = enum.auto()
>
> This has all of the features above and is simple, just requiring a
> comment to explain what it is. It's repr is a bit awkward though:
>
> >>> repr(_UNSET.token)
> '<_UNSET.token: 1>'
>
>
> All of these are in use by some developers, though not necessarily in
> the stdlib. None is perfect, though all are probably good enough.
> Since pickling is likely not relevant in most cases, I'm currently in
> favor of #2 making sure to implement a nice __repr__.
>
> - Tal
>
> [1] https://twitter.com/nolar/status/1392962447166877697
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/77ZBKCJQTG2OFY2WUL33OSJ6H3J57AEP/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Sat, May 15, 2021 at 2:09 AM David Mertz <mertz@gnosis.cx> wrote:
>
> I think it's more future-looking to allow pickle round-tripping.

I tend to agree.

> Just add a ._uuid attribute and have object equality follow equality of that attribute. There's no reason to expose that in the .__repr__, but it would be inspectable in concept.

I think it's worth preserving the idiom of comparing sentinels using
`is`, as we do for `None` and other existing sentinel values. It's
relatively easy to do, such as by using a single-value Enum or by
using a class with a custom __new__.

- Tal
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/XNYLOHY46BWLRXVLPL5RNTLBE6JG5OEX/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
IMO you should consider writing a PEP to enhance sentinels in Python,
and maybe even provide a public API for sentinels in general.

Victor
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/EY6B6PRQ2B54FVG5JK42GR6ZM2VQ7VL2/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
I'd like to learn about use cases where `...` (a.k.a. `Ellipsis`) is
not a good sentinel. It's a pickable singleton testable with `is`,
readily available, and extremely unlikely to appear in a data stream.
Its repr is "Ellipsis".

If you don't like the name for this purpose, you can always define a
constant (that won't fix the `repr`, obviously, but helps with source
code readability).

SENTINEL = ...

I can't think of any case where I'd rather have my own custom
sentinel, or need a special API for sentinels. Probably my fault, of
course. Please enlighten me!

Cheers,

Luciano

On Thu, May 20, 2021 at 8:35 AM Victor Stinner <vstinner@python.org> wrote:
>
> IMO you should consider writing a PEP to enhance sentinels in Python,
> and maybe even provide a public API for sentinels in general.
>
> Victor
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/EY6B6PRQ2B54FVG5JK42GR6ZM2VQ7VL2/
> Code of Conduct: http://python.org/psf/codeofconduct/



--
Luciano Ramalho
| Author of Fluent Python (O'Reilly, 2015)
| http://shop.oreilly.com/product/0636920032519.do
| Technical Principal at ThoughtWorks
| Twitter: @ramalhoorg
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/WNCFFIJHLS5NCA5QZG5JA45U2DKEETGI/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Thu, May 20, 2021 at 6:21 AM Tal Einat <taleinat@gmail.com> wrote:

> On Sat, May 15, 2021 at 2:09 AM David Mertz <mertz@gnosis.cx> wrote:
> > Just add a ._uuid attribute and have object equality follow equality of
> that attribute. There's no reason to expose that in the .__repr__, but it
> would be inspectable in concept.
>
> I think it's worth preserving the idiom of comparing sentinels using
> `is`, as we do for `None` and other existing sentinel values. It's
> relatively easy to do, such as by using a single-value Enum or by
> using a class with a custom __new__.
>

This only works if:

a) Unpickling is within a single interpreter session
b) Sentinels are explicitly created in imported modules, not as a runtime,
user-level creation

Maybe there's a way to do it, but how would you handle this situation:

if some_runtime_condition:
my_sentinal = Sentinel(desc="Gosh, I need a sentinel")
# ... code ...
pickle.dump(thing_using_sentinel, fh)

Equality is certainly a lot easier to get than identity here.

--
The dead increasingly dominate and strangle both the living and the
not-yet born. Vampiric capital and undead corporate persons abuse
the lives and control the thoughts of homo faber. Ideas, once born,
become abortifacients against new conceptions.
Re: The repr of a sentinel [ In reply to ]
On Thu, 20 May 2021 at 18:13, Luciano Ramalho <luciano@ramalho.org> wrote:
>
> I'd like to learn about use cases where `...` (a.k.a. `Ellipsis`) is
> not a good sentinel. It's a pickable singleton testable with `is`,
> readily available, and extremely unlikely to appear in a data stream.
> Its repr is "Ellipsis".

Personally, I'm quite tempted by the idea of using ellipsis. It just
sort of feels reasonable (and in the context `def f(x,
optional_arg=...)` it even looks pretty natural).

But it nevertheless feels like a bit of an abuse - the original point
of ellipsis was for indexing, and in particular complex slices like
a[1:20:2, ..., 3:5]. That usage is common in numpy, as I understand
it, even if it's relatively rare in everyday Python. So while I like
the idea in principle, I'm mildly worried that it's not "the right
thing to do".

I can't put my ambivalence about the idea any more precisely than
this, unfortunately.

Paul
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/ZGMZRGHFXQQZZLKBBZKXXAO65TRB6VYX/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Fri, May 21, 2021 at 3:51 AM David Mertz <mertz@gnosis.cx> wrote:
>
> On Thu, May 20, 2021 at 6:21 AM Tal Einat <taleinat@gmail.com> wrote:
>>
>> On Sat, May 15, 2021 at 2:09 AM David Mertz <mertz@gnosis.cx> wrote:
>> > Just add a ._uuid attribute and have object equality follow equality of that attribute. There's no reason to expose that in the .__repr__, but it would be inspectable in concept.
>>
>> I think it's worth preserving the idiom of comparing sentinels using
>> `is`, as we do for `None` and other existing sentinel values. It's
>> relatively easy to do, such as by using a single-value Enum or by
>> using a class with a custom __new__.
>
>
> This only works if:
>
> a) Unpickling is within a single interpreter session
> b) Sentinels are explicitly created in imported modules, not as a runtime, user-level creation
>
> Maybe there's a way to do it, but how would you handle this situation:
>
> if some_runtime_condition:
> my_sentinal = Sentinel(desc="Gosh, I need a sentinel")
> # ... code ...
> pickle.dump(thing_using_sentinel, fh)
>
> Equality is certainly a lot easier to get than identity here.
>

Probably the easiest way would be to have some kind of unique
identifier (a fully-qualified name) that can be pickled, and then any
time you attempt to construct a Sentinel with that identifier, it's
guaranteed to return the same object. That way, in your example,
unpickling it in another session where that runtime condition is false
would simply create a brand new sentinel, which would be the same as
any others that also get unpickled; and if, subsequently, you
reconstruct my_sentinal, it would be the same one as got unpickled.

The hardest part would be figuring out a reliable way to define the
identifier, without massively duplicating or requiring that something
be registered somewhere.

ChrisA
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/3WKMD7Q3JMLDWI3L2WOGIJB767PYJO2W/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Thu, May 20, 2021, 2:11 PM Chris Angelico

> Probably the easiest way would be to have some kind of unique
> identifier (a fully-qualified name) that can be pickled, and then any
> time you attempt to construct a Sentinel with that identifier, it's
> guaranteed to return the same object.


Gosh, almost like a UUID :-).

Actually, there's no reason my ._uuid attribute couldn't simply be the
object id(). That's probably an improvement.
Re: The repr of a sentinel [ In reply to ]
On 5/20/21 11:00 AM, Paul Moore wrote:

> But it nevertheless feels like a bit of an abuse - the original point
> of ellipsis was for indexing, and in particular complex slices like
> a[1:20:2, ..., 3:5]. That usage is common in numpy, as I understand
> it,

Interesting -- do you know what ... means in that context?

--
~Ethan~
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/EKKZP27QJZJD7UA7BXV6TB5EG7NOI2H3/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 5/20/21 10:47 AM, David Mertz wrote:
> On Thu, May 20, 2021 at 6:21 AM Tal Einat wrote:

>> I think it's worth preserving the idiom of comparing sentinels using
>> `is`, as we do for `None` and other existing sentinel values. It's
>> relatively easy to do, such as by using a single-value Enum or by
>> using a class with a custom __new__.
>
>
> This only works if:
>
> a) Unpickling is within a single interpreter session

I don't understand. If I pickle a sentinel in session A, then unpickle it in session B, why wouldn't I get the "same"
sentinel?

> b) Sentinels are explicitly created in imported modules, not as a runtime, user-level creation

Why would you ever have a sentinel not always created?

Hoping-to-learn-something-new'ly yrs,

--
~Ethan~
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/OLTV44EVHJL3C4AIFPGUINZAZNI3YNNK/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Thu, May 20, 2021 at 3:03 PM Ethan Furman <ethan@stoneleaf.us> wrote:

> > But it nevertheless feels like a bit of an abuse - the original point
> > of ellipsis was for indexing, and in particular complex slices like
> > a[1:20:2, ..., 3:5]. That usage is common in numpy, as I understand
> > it,
> Interesting -- do you know what ... means in that context?
>

In NumPy, the ellipsis means "fill in as many dimensions as needed (with
full range)".

So e.g., if I have a 5-D array, and I want just a portion from the first
and last dimension (but everything from the middle ones), I can type:

a[1:20:2, :, :, :, 3:5]

But as a simplification, I can use the example given:

a[1:20:2, ..., 3:5]

This is particularly useful since in NumPy it is not uncommon to expand or
contract the number of dimensions (often with some dimensions having only a
span of 1). If you don't want to think about which version of the
high-dimensional array you are working with (that might have been
.flatten()'d, .squeeze()'d, or .expand_dims()'d), this is sometimes more
expressive.


--
The dead increasingly dominate and strangle both the living and the
not-yet born. Vampiric capital and undead corporate persons abuse
the lives and control the thoughts of homo faber. Ideas, once born,
become abortifacients against new conceptions.
Re: The repr of a sentinel [ In reply to ]
On Thu, 20 May 2021 at 20:06, Ethan Furman <ethan@stoneleaf.us> wrote:
>
> On 5/20/21 11:00 AM, Paul Moore wrote:
>
> > But it nevertheless feels like a bit of an abuse - the original point
> > of ellipsis was for indexing, and in particular complex slices like
> > a[1:20:2, ..., 3:5]. That usage is common in numpy, as I understand
> > it,
>
> Interesting -- do you know what ... means in that context?

In general, it just means a.getitem((slice(1,20,2), Ellipsis,
slice(3,5))), which has no specifically-defined meaning. In numpy, it
means something along the lines of "broadcast along this axis" (I
don't know the numpy terminology very well).

Paul
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/FFKSIHKW2CAQUMBWA6YUDKTGDKNSEQBB/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
> On 20 May 2021, at 19:10, Luciano Ramalho <luciano@ramalho.org> wrote:
>
> I'd like to learn about use cases where `...` (a.k.a. `Ellipsis`) is
> not a good sentinel. It's a pickable singleton testable with `is`,
> readily available, and extremely unlikely to appear in a data stream.
> Its repr is "Ellipsis".
>
> If you don't like the name for this purpose, you can always define a
> constant (that won't fix the `repr`, obviously, but helps with source
> code readability).
>
> SENTINEL = ...
>
> I can't think of any case where I'd rather have my own custom
> sentinel, or need a special API for sentinels. Probably my fault, of
> course. Please enlighten me!

One use case for a sentinel that is not a predefined (builtin) singleton is APIs where an arbitrary user specified value can be used.

One example of this is the definition of dataclasses.field:

dataclasses.field(*, default=MISSING, default_factory=MISSING, repr=True, hash=None, init=True, compare=True, metadata=None)

Here the “default” and “default_factory” can be an arbitrary value, and any builtin singleton could be used. Hence the use of a custom module-private sentinel that cannot clash with values used by users of the module (unless those users poke at private details of the module, but then all bets are off anyway).

That’s why I don’t particularly like the proposal of using Ellipsis as the sanctioned sentinel value. It would be weird at best that the default for a dataclass field can be any value, except for the builtin Ellipsis value.

Ronald

>
> Cheers,
>
> Luciano
>
> On Thu, May 20, 2021 at 8:35 AM Victor Stinner <vstinner@python.org> wrote:
>>
>> IMO you should consider writing a PEP to enhance sentinels in Python,
>> and maybe even provide a public API for sentinels in general.
>>
>> Victor
>> _______________________________________________
>> Python-Dev mailing list -- python-dev@python.org
>> To unsubscribe send an email to python-dev-leave@python.org
>> https://mail.python.org/mailman3/lists/python-dev.python.org/
>> Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/EY6B6PRQ2B54FVG5JK42GR6ZM2VQ7VL2/
>> Code of Conduct: http://python.org/psf/codeofconduct/
>
>
>
> --
> Luciano Ramalho
> | Author of Fluent Python (O'Reilly, 2015)
> | http://shop.oreilly.com/product/0636920032519.do
> | Technical Principal at ThoughtWorks
> | Twitter: @ramalhoorg
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/WNCFFIJHLS5NCA5QZG5JA45U2DKEETGI/
> Code of Conduct: http://python.org/psf/codeofconduct/



Twitter / micro.blog: @ronaldoussoren
Blog: https://blog.ronaldoussoren.net/
Re: The repr of a sentinel [ In reply to ]
On Thu, 2021-05-20 at 19:00 +0100, Paul Moore wrote:
> On Thu, 20 May 2021 at 18:13, Luciano Ramalho <luciano@ramalho.org>
> wrote:
> >
> > I'd like to learn about use cases where `...` (a.k.a. `Ellipsis`)
> > is
> > not a good sentinel. It's a pickable singleton testable with `is`,
> > readily available, and extremely unlikely to appear in a data
> > stream.
> > Its repr is "Ellipsis".
>
> Personally, I'm quite tempted by the idea of using ellipsis. It just
> sort of feels reasonable (and in the context `def f(x,
> optional_arg=...)` it even looks pretty natural).
>
> But it nevertheless feels like a bit of an abuse - the original point
> of ellipsis was for indexing, and in particular complex slices like
> a[1:20:2, ..., 3:5]. That usage is common in numpy, as I understand
> it, even if it's relatively rare in everyday Python. So while I like
> the idea in principle, I'm mildly worried that it's not "the right
> thing to do".
>
> I can't put my ambivalence about the idea any more precisely than
> this, unfortunately.


In NumPy we use a "missing argument" sentinel currently. Mainly for
things roughly like:


def mean(arr, *, axis=np._NoValue):
if not hasattr(arr, "mean"):
# Not a duck that defines `mean`, coerce to ndarray:
arr = np.asarray(arr)

if axis is np._NoValue:
return arr.mean()
return arr.mean(axis=axis)


This allows us to add new keyword arguments without breaking backward
compatibility. I do not remember if we had particularly important
reasons for not wanting to drop the default `None`, or it was just
erring on the safe side.


In any case, I tend to agree that `Ellipsis` should be considered
"user-facing" value. And in the above code, we do not expect anyone to
ever call `np.mean(something, axis=np._NoValue)` – its not even
accessible – but if the value was `...` then I would expect users to be
encouraged to write `np.mean(arr, axis=...)` in normal code.

More importantly, I can think of a reasonable "meaning" for `axis=...`!
In NumPy `axis=None` (default) returns a scalar, `axis=...` could
return a 0-D array.
This would borrow meanings that `Ellipsis` carries in indexing. [1]

Cheers,

Sebastian



[1] In such a mental model, it would mean the same as
`axis=range(arr.ndim)`. To be clear, NumPy doesn't do this, its just a
plausible meaning if it has to continue to juggle scalars and 0-D
arrays and wants to be "clearer" about it.



>
> Paul
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-dev@python.org/message/ZGMZRGHFXQQZZLKBBZKXXAO65TRB6VYX/
> Code of Conduct: http://python.org/psf/codeofconduct/
>


_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/M2X6VMEDFNZ4GA3CLXKXMA56SNCEPX4O/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
The scenario I'm thinking about is like this:

(base) 84-tmp % python
Python 3.9.1 (default, Dec 11 2020, 14:32:07)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> cheap_sentinel = object()
>>> id(cheap_sentinel)
140469343429632
>>> pickle.dump(cheap_sentinel, open('sentinel.pkl', 'wb'))

(base) 85-tmp % python
Python 3.9.1 (default, Dec 11 2020, 14:32:07)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pickle
>>> cheap_sentinel = object()
>>> id(cheap_sentinel)
139852505814016
>>> id(pickle.load(open('sentinel.pkl', 'rb')))
139852505813968

It would be pleasant if there were a way to make "cheap_sentinel" be the
same thing—either by equality or by identity—betweeen those two runs of the
interpreter.

None or Ellipsis have that property, of course. So do, for example,
integers, at least by equality if not identity (yes, of course, we might
get identity by interning, but it's not guaranteed).

On Thu, May 20, 2021 at 3:10 PM Ethan Furman <ethan@stoneleaf.us> wrote:

> On 5/20/21 10:47 AM, David Mertz wrote:
> > On Thu, May 20, 2021 at 6:21 AM Tal Einat wrote:
>
> >> I think it's worth preserving the idiom of comparing sentinels using
> >> `is`, as we do for `None` and other existing sentinel values. It's
> >> relatively easy to do, such as by using a single-value Enum or by
> >> using a class with a custom __new__.
> >
> >
> > This only works if:
> >
> > a) Unpickling is within a single interpreter session
>
> I don't understand. If I pickle a sentinel in session A, then unpickle it
> in session B, why wouldn't I get the "same"
> sentinel?
>
> > b) Sentinels are explicitly created in imported modules, not as a
> runtime, user-level creation
>
> Why would you ever have a sentinel not always created?
>
> Hoping-to-learn-something-new'ly yrs,
>
> --
> ~Ethan~
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at
> https://mail.python.org/archives/list/python-dev@python.org/message/OLTV44EVHJL3C4AIFPGUINZAZNI3YNNK/
> Code of Conduct: http://python.org/psf/codeofconduct/
>


--
The dead increasingly dominate and strangle both the living and the
not-yet born. Vampiric capital and undead corporate persons abuse
the lives and control the thoughts of homo faber. Ideas, once born,
become abortifacients against new conceptions.
Re: The repr of a sentinel [ In reply to ]
On Thu, May 20, 2021 at 10:37 PM David Mertz <mertz@gnosis.cx> wrote:
>
> The scenario I'm thinking about is like this:
>
> (base) 84-tmp % python
> Python 3.9.1 (default, Dec 11 2020, 14:32:07)
> [GCC 7.3.0] :: Anaconda, Inc. on linux
> Type "help", "copyright", "credits" or "license" for more information.
> >>> import pickle
> >>> cheap_sentinel = object()
> >>> id(cheap_sentinel)
> 140469343429632
> >>> pickle.dump(cheap_sentinel, open('sentinel.pkl', 'wb'))
>
> (base) 85-tmp % python
> Python 3.9.1 (default, Dec 11 2020, 14:32:07)
> [GCC 7.3.0] :: Anaconda, Inc. on linux
> Type "help", "copyright", "credits" or "license" for more information.
> >>> import pickle
> >>> cheap_sentinel = object()
> >>> id(cheap_sentinel)
> 139852505814016
> >>> id(pickle.load(open('sentinel.pkl', 'rb')))
> 139852505813968
>
> It would be pleasant if there were a way to make "cheap_sentinel" be the same thing—either by equality or by identity—betweeen those two runs of the interpreter.
>
> None or Ellipsis have that property, of course. So do, for example, integers, at least by equality if not identity (yes, of course, we might get identity by interning, but it's not guaranteed).

There are several ways of achieving this for other sentinel values,
some of which have been mentioned earlier in this thread. Some
examples are:

1. a value from a single-valued enum
2. a class object (not an instance)
3. a singleton class with a carefully implemented __new__ which always
returns the same instance

- Tal Einat
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/RJXU7F6LYVJ4C4N4EPVA3A5F3QRGK253/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
> > But it nevertheless feels like a bit of an abuse - the original point
>> > of ellipsis was for indexing, and in particular complex slices like
>> > a[1:20:2, ..., 3:5]. That usage is common in numpy, as I understand
>> > it,
>> Interesting -- do you know what ... means in that context?
>>
>
> In NumPy, the ellipsis means "fill in as many dimensions as needed (with
> full range)".
>

I am in the same boat here in that until now I saw Ellipsis as a thing used
by numpy and adjacent libraries in slicing. I don't think I have ever
written a slice with "..." in it so please forgive any ignorance but wasn't
the reason for having Ellipsis originally that None already had a special
meaning to the slice object and a different sentinel was needed? In other
words the normal case of a missing argument being None caused specific side
effects that needed to be skipped, so a sentinel was created to distinguish
these cases, just like we are discussing for other objects? It seems to me
the current situation has more in common than not. Numpy could have created
its own Sentinel object for its own use in these sliceses, just as it now
creates its own Sentinel for NoValue, but somewhere along the way it was
determined to be in the better interest of the community to have this
object defined in the standard library so it could be shared by multiple
scientific libraries without additional dependency burden, even though it
had no use to CPython.

I think there was perhaps a time when Ellipsis was so closely tied to the
scientific libraries that recommending its use anywhere else would have
been a poor idea, probably mostly because of the body of existing teaching
around its use; but I also think that association is no longer as strong.
"..." is being adopted across the typing landscape with several PEPs, such
as 483, outlining their special meaning in annotations. In addition, stub
files have since the beginning normalized on putting "..." as the
function body despite already having the working conventions of `pass`
`NotImplemented` or just a good old empty docstring """""" filling the same
purpose in standard python files.
Re: The repr of a sentinel [ In reply to ]
On 5/20/2021 3:24 PM, Ronald Oussoren via Python-Dev wrote:
>
>
>> On 20 May 2021, at 19:10, Luciano Ramalho <luciano@ramalho.org
>> <mailto:luciano@ramalho.org>> wrote:
>>
>> I'd like to learn about use cases where `...` (a.k.a. `Ellipsis`) is
>> not a good sentinel. It's a pickable singleton testable with `is`,
>> readily available, and extremely unlikely to appear in a data stream.
>> Its repr is "Ellipsis".
>>
>> If you don't like the name for this purpose, you can always define a
>> constant (that won't fix the `repr`, obviously, but helps with source
>> code readability).
>>
>> SENTINEL = ...
>>
>> I can't think of any case where I'd rather have my own custom
>> sentinel, or need a special API for sentinels. Probably my fault, of
>> course. Please enlighten me!
>
> One use case for a sentinel that is not a predefined (builtin)
> singleton is APIs where an arbitrary user specified value can be used.
>
> One example of this is the definition of dataclasses.field:
>
> |dataclasses.||field|(/*/, /default=MISSING/,
> /default_factory=MISSING/, /repr=True/, /hash=None/, /init=True/,
> /compare=True/, /metadata=None/)
>
> Here the “default” and “default_factory” can be an arbitrary value,
> and any builtin singleton could be used. Hence the use of a custom
> module-private sentinel that cannot clash with values used by users of
> the module (unless those users poke at private details of the module,
> but then all bets are off anyway).
>
> That’s why I don’t particularly like the proposal of using Ellipsis as
> the sanctioned sentinel value. It would be weird at best that the
> default for a dataclass field can be any value, except for the builtin
> Ellipsis value.

Completely agree. I'm opposed to Ellipsis as a sentinel for this reason,
at least for dataclasses. I can easily see wanting to store an Ellipsis
in a field of a dataclass that's describing a function's parameters. And
I can even see it being the default= value. Not so much
default_factory=, but they may as well be the same.

Eric
Re: The repr of a sentinel [ In reply to ]
On 21. 05. 21 3:23, Eric V. Smith wrote:
> On 5/20/2021 3:24 PM, Ronald Oussoren via Python-Dev wrote:
>>
>>
>>> On 20 May 2021, at 19:10, Luciano Ramalho <luciano@ramalho.org
>>> <mailto:luciano@ramalho.org>> wrote:
>>>
>>> I'd like to learn about use cases where `...` (a.k.a. `Ellipsis`) is
>>> not a good sentinel. It's a pickable singleton testable with `is`,
>>> readily available, and extremely unlikely to appear in a data stream.
>>> Its repr is "Ellipsis".
>>>
>>> If you don't like the name for this purpose, you can always define a
>>> constant (that won't fix the `repr`, obviously, but helps with source
>>> code readability).
>>>
>>> SENTINEL = ...
>>>
>>> I can't think of any case where I'd rather have my own custom
>>> sentinel, or need a special API for sentinels. Probably my fault, of
>>> course. Please enlighten me!
>>
>> One use case for a sentinel that is not a predefined (builtin)
>> singleton is APIs where an arbitrary user specified value can be used.
>>
>> One example of this is the definition of dataclasses.field:
>>
>> |dataclasses.||field|(/*/, /default=MISSING/,
>> /default_factory=MISSING/, /repr=True/, /hash=None/, /init=True/,
>> /compare=True/, /metadata=None/)
>>
>> Here the “default” and “default_factory” can be an arbitrary value,
>> and any builtin singleton could be used. Hence the use of a custom
>> module-private sentinel that cannot clash with values used by users of
>> the module (unless those users poke at private details of the module,
>> but then all bets are off anyway).
>>
>> That’s why I don’t particularly like the proposal of using Ellipsis as
>> the sanctioned sentinel value. It would be weird at best that the
>> default for a dataclass field can be any value, except for the builtin
>> Ellipsis value.
>
> Completely agree. I'm opposed to Ellipsis as a sentinel for this reason,
> at least for dataclasses. I can easily see wanting to store an Ellipsis
> in a field of a dataclass that's describing a function's parameters. And
> I can even see it being the default= value. Not so much
> default_factory=, but they may as well be the same.


And this argument also works for any other single value.
Including the original None.

(It just might not be obvious at first, before that single value starts
being used in lots of different contexts.)
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/IA4BEPTEJXVK5UO2L7ZDQJG2Z3OYQ3VX/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
I was attracted to Python in 1998 because it seemed designed to make
the simple cases simple, and the hard cases possible.

My personal takeaway from this discussion: I will continue to advocate
for the use of Ellipsis as a sentinel in the *many* cases where it is
suitable.

For the hard cases, I will read the upcoming 46-page "PEP 973:
Parameterized Sentinel Factory Factory API" ;-)

Cheers,

Luciano


--
Luciano Ramalho
| Author of Fluent Python (O'Reilly, 2015)
| http://shop.oreilly.com/product/0636920032519.do
| Technical Principal at ThoughtWorks
| Twitter: @ramalhoorg
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/3RXJFB4B5KWBG3F226KUE7ZM53URDLXP/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 5/21/2021 9:36 AM, Petr Viktorin wrote:
> On 21. 05. 21 3:23, Eric V. Smith wrote:
>> On 5/20/2021 3:24 PM, Ronald Oussoren via Python-Dev wrote:
>>> One example of this is the definition of dataclasses.field:
>>>
>>> |dataclasses.||field|(/*/, /default=MISSING/,
>>> /default_factory=MISSING/, /repr=True/, /hash=None/, /init=True/,
>>> /compare=True/, /metadata=None/)
>>
>> Completely agree. I'm opposed to Ellipsis as a sentinel for this
>> reason, at least for dataclasses. I can easily see wanting to store an
>> Ellipsis in a field of a dataclass that's describing a function's
>> parameters. And I can even see it being the default= value. Not so
>> much default_factory=, but they may as well be the same.
>
> And this argument also works for any other single value.
> Including the original None.
>
> (It just might not be obvious at first, before that single value starts
> being used in lots of different contexts.)

I think it's a fairly obvious case, and a legitimate one to acknowledge
(the array one less so - we're talking about a "missing parameter"
sentinel, so you ought to be testing for it immediately and replacing
with your preferred/secret default value, not using it for indexing
without even checking it).

In the example above, it's fairly easy to pass "lambda: ..." as the
default_factory to work around it, and besides, the existence of rare
edge cases doesn't mean you have to force everyone into acting like
they're a rare edge case.

All the other situations where we want arguments with unspecified
default values can use ..., and the few cases where ... is a valid value
(semantically, for the API, not syntactically) can spend the time
figuring out a different API design.

Cheers,
Steve
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/EJYSP7KBD2XA26QTE2FVQP4SS2FDLPB2/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Fri, May 21, 2021 at 5:33 PM Luciano Ramalho <luciano@ramalho.org> wrote:
>
> For the hard cases, I will read the upcoming 46-page "PEP 973:
> Parameterized Sentinel Factory Factory API" ;-)

I put up an early draft of a PEP on a branch in the PEPs repo:
https://github.com/python/peps/blob/sentinels/pep-9999.rst

(Note: This link will break once the temporary branch is deleted.)

I wrote it to summarize the discussions, organize my thoughts and
explore the options. I stopped working on it late at night and sent a
link to a few people to get some opinions. I didn’t intend to make it
public yet, but it was noticed and replied to on the
discuss.python.org thread where I put up the poll [1], so the cat is
out of the proverbial bag now…

Luciano, your wish is granted! ;)
- Tal Einat

[1] https://discuss.python.org/t/sentinel-values-in-the-stdlib/8810/
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/U3N7XG5WJASMGKLIBXIE4SS6TSWLQQU2/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Sun, May 23, 2021 at 3:37 AM Tal Einat <taleinat@gmail.com> wrote:
> I put up an early draft of a PEP on a branch in the PEPs repo:
> https://github.com/python/peps/blob/sentinels/pep-9999.rst

Thanks for that PEP, Tal. Good ideas and recap there.

I think repr= should have a default: the name of the class within <>:
<NotGiven>.

Sentinels don't have state or any other data besides a name, so I
would prefer not to force users to create a class just so they can
instantiate it.

Why not just this?

NotGiven = sentinel('<NotGiven>')

In this case it's harder to provide a good default repr.

On the other hand, if the user must create a class, the class itself
should be the sentinel. Class objects are already singletons, so that
makes sense.

Here is a possible class-based API:

class NotGiven(Sentinel):
pass

That's it. Now I can use NotGiven as the sentinel, and its default
repr is <NotGiven>.

Behind the scenes we can have a SentinelMeta metaclass with all the
magic that could be required--including the default __repr__ method.

What do you think?

Cheers,

Luciano


>
> (Note: This link will break once the temporary branch is deleted.)
>
> I wrote it to summarize the discussions, organize my thoughts and
> explore the options. I stopped working on it late at night and sent a
> link to a few people to get some opinions. I didn’t intend to make it
> public yet, but it was noticed and replied to on the
> discuss.python.org thread where I put up the poll [1], so the cat is
> out of the proverbial bag now…
>
> Luciano, your wish is granted! ;)
> - Tal Einat
>
> [1] https://discuss.python.org/t/sentinel-values-in-the-stdlib/8810/



--
Luciano Ramalho
| Author of Fluent Python (O'Reilly, 2015)
| http://shop.oreilly.com/product/0636920032519.do
| Technical Principal at ThoughtWorks
| Twitter: @ramalhoorg
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/32FKEQ4GBXHGVNSX5NYPW5AKCOFNBI5C/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
Sorry about my detour into the rejected idea of a factory function.

But how about this class-based API?

class NotGiven(Sentinel):
pass


Now I can use NotGiven as the sentinel, and its default repr is <NotGiven>.

Behind the scenes we can have a SentinelMeta metaclass with all the
magic that could be required--including the default __repr__ method.

Cheers,

Luciano



> >
> > (Note: This link will break once the temporary branch is deleted.)
> >
> > I wrote it to summarize the discussions, organize my thoughts and
> > explore the options. I stopped working on it late at night and sent a
> > link to a few people to get some opinions. I didn’t intend to make it
> > public yet, but it was noticed and replied to on the
> > discuss.python.org thread where I put up the poll [1], so the cat is
> > out of the proverbial bag now…
> >
> > Luciano, your wish is granted! ;)
> > - Tal Einat
> >
> > [1] https://discuss.python.org/t/sentinel-values-in-the-stdlib/8810/
>
>
>
> --
> Luciano Ramalho
> | Author of Fluent Python (O'Reilly, 2015)
> | http://shop.oreilly.com/product/0636920032519.do
> | Technical Principal at ThoughtWorks
> | Twitter: @ramalhoorg



--
Luciano Ramalho
| Author of Fluent Python (O'Reilly, 2015)
| http://shop.oreilly.com/product/0636920032519.do
| Technical Principal at ThoughtWorks
| Twitter: @ramalhoorg
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/2B6VXRYS7UHJRX762DI2LSOGGGRJHXIV/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On 2021-05-24 01:37, Luciano Ramalho wrote:
> Sorry about my detour into the rejected idea of a factory function.
>
> But how about this class-based API?
>
> class NotGiven(Sentinel):
> pass
>
>
> Now I can use NotGiven as the sentinel, and its default repr is <NotGiven>.
>
The repr of other singletons are the names of those singletons, eg.
"None", so why "<NotGiven>" instead of "NotGiven"?

> Behind the scenes we can have a SentinelMeta metaclass with all the
> magic that could be required--including the default __repr__ method.
>
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/C672ZR7E6AD6KD5UVLUWD3GDQT66VACA/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
Here is a simple implementation of that Sentinel class:

https://github.com/fluentpython/example-code-2e/blob/master/25-class-metaprog/sentinel/sentinel.py

Tests in the same directory. That's not a real package yet, just a
couple of super simple examples I may use in Fluent Python 2e.

Cheers,

Luciano

On Sun, May 23, 2021 at 9:37 PM Luciano Ramalho <luciano@ramalho.org> wrote:
>
> Sorry about my detour into the rejected idea of a factory function.
>
> But how about this class-based API?
>
> class NotGiven(Sentinel):
> pass
>
>
> Now I can use NotGiven as the sentinel, and its default repr is <NotGiven>.
>
> Behind the scenes we can have a SentinelMeta metaclass with all the
> magic that could be required--including the default __repr__ method.
>
> Cheers,
>
> Luciano
>
>
>
> > >
> > > (Note: This link will break once the temporary branch is deleted.)
> > >
> > > I wrote it to summarize the discussions, organize my thoughts and
> > > explore the options. I stopped working on it late at night and sent a
> > > link to a few people to get some opinions. I didn’t intend to make it
> > > public yet, but it was noticed and replied to on the
> > > discuss.python.org thread where I put up the poll [1], so the cat is
> > > out of the proverbial bag now…
> > >
> > > Luciano, your wish is granted! ;)
> > > - Tal Einat
> > >
> > > [1] https://discuss.python.org/t/sentinel-values-in-the-stdlib/8810/
> >
> >
> >
> > --
> > Luciano Ramalho
> > | Author of Fluent Python (O'Reilly, 2015)
> > | http://shop.oreilly.com/product/0636920032519.do
> > | Technical Principal at ThoughtWorks
> > | Twitter: @ramalhoorg
>
>
>
> --
> Luciano Ramalho
> | Author of Fluent Python (O'Reilly, 2015)
> | http://shop.oreilly.com/product/0636920032519.do
> | Technical Principal at ThoughtWorks
> | Twitter: @ramalhoorg



--
Luciano Ramalho
| Author of Fluent Python (O'Reilly, 2015)
| http://shop.oreilly.com/product/0636920032519.do
| Technical Principal at ThoughtWorks
| Twitter: @ramalhoorg
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/DAUQOKAJG7FZYY24JBG7ZIGA3TFZA4OY/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
Thanks Tal for writing this up.

A couple comments:

1) “Add a single new sentinel value, e.g. MISSING or Sentinel” (under
rejected)

I was one of the proponent of that -- but not as an alternative to having a
stadardars way to create unique sentinels, but as an addition. That's kind
of orthogonal to the PEP, but it would still be nice to have it at the same
time.

Why? There was a fair bit of discussion as to why you would not want to
require everyone to use MISSING (dataclasses, in particular), but I still
think better for the readability of everyone's code for the common case(s?)
to use the same singleton, if they can.

2) couldn't a factory function simply return a sentinel subclass? Maybe I'm
missing something, but I can't see why that would require stack frame
inspection.

But frankly Luciano's idea of a base class that can be subclassed seems the
most startightford to me.

-CHB
Re: The repr of a sentinel [ In reply to ]
On Mon, May 24, 2021 at 3:30 AM Luciano Ramalho <luciano@ramalho.org> wrote:
>
> On Sun, May 23, 2021 at 3:37 AM Tal Einat <taleinat@gmail.com> wrote:
> > I put up an early draft of a PEP on a branch in the PEPs repo:
> > https://github.com/python/peps/blob/sentinels/pep-9999.rst
>
> Thanks for that PEP, Tal. Good ideas and recap there.
>
> I think repr= should have a default: the name of the class within <>:
> <NotGiven>.
>
> Sentinels don't have state or any other data besides a name, so I
> would prefer not to force users to create a class just so they can
> instantiate it.
>
> Why not just this?
>
> NotGiven = sentinel('<NotGiven>')

I'm seriously considering that now. The issues I ran into with this
approach are perhaps not actually problematic.

> On the other hand, if the user must create a class, the class itself
> should be the sentinel. Class objects are already singletons, so that
> makes sense.
>
> Here is a possible class-based API:
>
> class NotGiven(Sentinel):
> pass
>
> That's it. Now I can use NotGiven as the sentinel, and its default
> repr is <NotGiven>.
>
> Behind the scenes we can have a SentinelMeta metaclass with all the
> magic that could be required--including the default __repr__ method.
>
> What do you think?

One issue with that is that such sentinels don't have their own class,
so you can't write a strict type signature, such as `Union[str,
NotGivenType]`.

Another issue is that having these objects be classes, rather than
normal instances of classes, could be surprising and confusing.

For those two reasons, for now, I think generating a unique object
with its own unique class is preferable.

> Sorry about my detour into the rejected idea of a factory function.

Please don't apologize! I put those ideas in the "Rejected Ideas"
section mostly to have them written down with a summary of the
considerations related to them. They shouldn't be considered finally
rejected unless and until the PEP is finished and accepted.

- Tal
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/HL74JC3OF7Y3F5RDYVACAFODL4E3CBI6/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Mon, May 24, 2021 at 4:10 AM MRAB <python@mrabarnett.plus.com> wrote:
>
> On 2021-05-24 01:37, Luciano Ramalho wrote:
> > Now I can use NotGiven as the sentinel, and its default repr is <NotGiven>.
> >
> The repr of other singletons are the names of those singletons, eg.
> "None", so why "<NotGiven>" instead of "NotGiven"?

Yea, that's up in the air. The common suggestions are either
"NotGiven", "<NotGiven>" or "mymodule.NotGiven".

The first makes sense for builtins like None and Ellipses, but I'm not
sure a function signature like foo(bar=NotGiven) is very clear.

With the factory function pattern there's no need for a default, so
this may become a non-issue, and I may remove the recommendation for
which form to use.

- Tal
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/WMS3CLQ765HAN3WCQKCL2XSSJUNP45LY/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Mon, May 24, 2021 at 6:04 AM Christopher Barker <pythonchb@gmail.com> wrote:
>
> 1) “Add a single new sentinel value, e.g. MISSING or Sentinel” (under rejected)
>
> I was one of the proponent of that -- but not as an alternative to having a stadardars way to create unique sentinels, but as an addition. That's kind of orthogonal to the PEP, but it would still be nice to have it at the same time.
>
> Why? There was a fair bit of discussion as to why you would not want to require everyone to use MISSING (dataclasses, in particular), but I still think better for the readability of everyone's code for the common case(s?) to use the same singleton, if they can.

The way I see it, there are several clear motivations to implement one
of these and make that the recommended way of defining sentinels. The
further benefit of having both is less clear to me.

Also, there's this in the Zen of Python: "There should be one-- and
preferably only one --obvious way to do it." This strengthens my
feeling that having two recommended ways of defining sentinel values,
in addition to the existing None, would be undesirable.

> 2) couldn't a factory function simply return a sentinel subclass? Maybe I'm missing something, but I can't see why that would require stack frame inspection.

It seems better for each sentinel to have its own class, which makes
it possible to write strict type signatures and allows better static
code analysis. It also makes handling copying and unpickling simple.

> But frankly Luciano's idea of a base class that can be subclassed seems the most startightford to me.

Yes, and it's what I originally suggested near the beginning of this thread :)

- Tal
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/WP5HKBQGW255SEV3O5FULRKEHJOT2CM6/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Mon, May 24, 2021 at 11:44 AM Tal Einat <taleinat@gmail.com> wrote:
> > But frankly Luciano's idea of a base class that can be subclassed seems the most startightford to me.
>
> Yes, and it's what I originally suggested near the beginning of this thread :)

I am sorry to have missed your previous e-mail with a base class for
sentinels, @Tal. I support that idea.

In fact, if we have a SentinelMeta metaclass, and a Sentinel base
class built from SentinelMeta, the Sentinel class can be used directly
as a sentinel without the need for subclassing—if the application does
not require a custom sentinel. If it does, then the user can subclass
Sentinel.

The SentinelMeta could be private, to discourage misuse.

This is my implementation, after learning from @Tal's code:

https://github.com/fluentpython/example-code-2e/blob/master/25-class-metaprog/sentinel/sentinel.py

Since having a Sentinel base class is desirable for ease of use, I
think it is simpler to code the __new__ method in it, instead of
coding metaclass logic to inject __new__ in the class namespace.

Best,

Luciano



--
Luciano Ramalho
| Author of Fluent Python (O'Reilly, 2015)
| http://shop.oreilly.com/product/0636920032519.do
| Technical Principal at ThoughtWorks
| Twitter: @ramalhoorg
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/UUXUKG3UZLTWKXPV6IZAE5KOMEJHMUS5/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
Hello, and thanks for the PEP,

I feel like the 3-lines declaration of a new sentinel would discourage a
bit its adoption compared to just "sentinel = object()"
From what I understand from the PEP, if new classes are defined inside
the closure of a factory function, some Python implementations would
have trouble copying/pickling them?

Would it be doable to have a single Sentinel class, whose instances
store their representation and some autogenerated UUID, and which
automatically return internally stored singletons (depending on this
UUID) when called multiple times or unpickled ?
This would require some __new__() and unpickling magic, but nothing too
CPython-specific (or am I missing something?).

regards,

Pascal


Le 24/05/2021 à 16:28, Tal Einat a écrit :
> On Mon, May 24, 2021 at 3:30 AM Luciano Ramalho <luciano@ramalho.org> wrote:
>> On Sun, May 23, 2021 at 3:37 AM Tal Einat <taleinat@gmail.com> wrote:
>>> I put up an early draft of a PEP on a branch in the PEPs repo:
>>> https://github.com/python/peps/blob/sentinels/pep-9999.rst
>> Thanks for that PEP, Tal. Good ideas and recap there.
>>
>> I think repr= should have a default: the name of the class within <>:
>> <NotGiven>.
>>
>> Sentinels don't have state or any other data besides a name, so I
>> would prefer not to force users to create a class just so they can
>> instantiate it.
>>
>> Why not just this?
>>
>> NotGiven = sentinel('<NotGiven>')
> I'm seriously considering that now. The issues I ran into with this
> approach are perhaps not actually problematic.
>
>> On the other hand, if the user must create a class, the class itself
>> should be the sentinel. Class objects are already singletons, so that
>> makes sense.
>>
>> Here is a possible class-based API:
>>
>> class NotGiven(Sentinel):
>> pass
>>
>> That's it. Now I can use NotGiven as the sentinel, and its default
>> repr is <NotGiven>.
>>
>> Behind the scenes we can have a SentinelMeta metaclass with all the
>> magic that could be required--including the default __repr__ method.
>>
>> What do you think?
> One issue with that is that such sentinels don't have their own class,
> so you can't write a strict type signature, such as `Union[str,
> NotGivenType]`.
>
> Another issue is that having these objects be classes, rather than
> normal instances of classes, could be surprising and confusing.
>
> For those two reasons, for now, I think generating a unique object
> with its own unique class is preferable.
>
>> Sorry about my detour into the rejected idea of a factory function.
> Please don't apologize! I put those ideas in the "Rejected Ideas"
> section mostly to have them written down with a summary of the
> considerations related to them. They shouldn't be considered finally
> rejected unless and until the PEP is finished and accepted.
>
> - Tal
> _______________________________________________
> Python-Dev mailing list -- python-dev@python.org
> To unsubscribe send an email to python-dev-leave@python.org
> https://mail.python.org/mailman3/lists/python-dev.python.org/
> Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/HL74JC3OF7Y3F5RDYVACAFODL4E3CBI6/
> Code of Conduct: http://python.org/psf/codeofconduct/
> .
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/JZEPSN4ZSAZ6QWXN75GFWQRJMJLPN37M/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Tue, May 25, 2021 at 1:08 PM Pascal Chambon <pythoniks@gmail.com> wrote:
> I feel like the 3-lines declaration of a new sentinel would discourage a
> bit its adoption compared to just "sentinel = object()"

How about one line?

class Missing(Sentinel): pass

And avoiding a lot of complications?

https://github.com/fluentpython/example-code-2e/blob/master/25-class-metaprog/sentinel/sentinel.py

Cheers,

Luciano



--
Luciano Ramalho
| Author of Fluent Python (O'Reilly, 2015)
| http://shop.oreilly.com/product/0636920032519.do
| Technical Principal at ThoughtWorks
| Twitter: @ramalhoorg
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/ZUKHRZWLQK45TV74NKG2FX4ILWI4YHXK/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
To add to the suggestions already given in this thread I dug into code I wrote some time ago.
Offered as an inspiration.

=== missing.py ===

from typing import Any

def MISSING(klass: Any) -> Any:
"""
create a sentinel to indicate a missing instance of a class
:param klass: the class of which an instance is missing
:return: missing class object
"""
g = globals()
missing_klass_name = f"_MISSING_{klass.__module__}_{klass.__name__}_MISSING_"
if missing_klass_name not in g:
g[missing_klass_name] = type(
missing_klass_name,
(klass,),
{
"__repr__": lambda x: f"MISSING({klass.__name__})",
}
)
return g[missing_klass_name]()

===

and as a demo:

=== demo_missing.py ===

import pickle

from missing import MISSING

x = MISSING(str)
y = "bar"
print(f"{x!r} == {y!r}: {x == y}")
print(f"{x!r} is {y!r}: {x is y}")
# MISSING(str) == 'bar': False
# MISSING(str) is 'bar': False

with open("object.pickled", "wb") as f:
pickle.dump(x, f)
with open("object.pickled", "rb") as f:
y = pickle.load(f)
print(f"{x!r} == {y!r}: {x == y}")
print(f"{x!r} is {y!r}: {x is y}")
# MISSING(str) == MISSING(str): True
# MISSING(str) is MISSING(str): False

def foo(a: int = MISSING(int), b: int = MISSING(int)):
print(f"{a=} {isinstance(a, int)}")
print(f"{b=} {isinstance(b, int)}")
print(f"{a!r} == {b!r}: {a == b}")
print(f"{a!r} is {b!r}: {a is b}")

foo()
# a=MISSING(int) True
# b=MISSING(int) True
# MISSING(int) == MISSING(int): True
# MISSING(int) is MISSING(int): False

foo(1)
# a=1 True
# b=MISSING(int) True
# 1 == MISSING(int): False
# 1 is MISSING(int): False

class Test:
...

t = MISSING(Test)
print(f"{t=}")
# t=MISSING(Test)

===
Re: The repr of a sentinel [ In reply to ]
Following the continued discussion, I'm currently in favor of a simple
interface using a factory function, i.e. NotGiven = sentinel('NotGiven').

I've created a new GitHub repo with a reference implementation. It also
includes a second version of the draft PEP, addresses some of the
additional points brought up in the latest parts of this discussion.

https://github.com/taleinat/python-stdlib-sentinels

- Tal

On Mon, May 24, 2021 at 5:28 PM Tal Einat <taleinat@gmail.com> wrote:

> On Mon, May 24, 2021 at 3:30 AM Luciano Ramalho <luciano@ramalho.org>
> wrote:
> >
> > On Sun, May 23, 2021 at 3:37 AM Tal Einat <taleinat@gmail.com> wrote:
> > > I put up an early draft of a PEP on a branch in the PEPs repo:
> > > https://github.com/python/peps/blob/sentinels/pep-9999.rst
> >
> > Thanks for that PEP, Tal. Good ideas and recap there.
> >
> > I think repr= should have a default: the name of the class within <>:
> > <NotGiven>.
> >
> > Sentinels don't have state or any other data besides a name, so I
> > would prefer not to force users to create a class just so they can
> > instantiate it.
> >
> > Why not just this?
> >
> > NotGiven = sentinel('<NotGiven>')
>
> I'm seriously considering that now. The issues I ran into with this
> approach are perhaps not actually problematic.
>
> > On the other hand, if the user must create a class, the class itself
> > should be the sentinel. Class objects are already singletons, so that
> > makes sense.
> >
> > Here is a possible class-based API:
> >
> > class NotGiven(Sentinel):
> > pass
> >
> > That's it. Now I can use NotGiven as the sentinel, and its default
> > repr is <NotGiven>.
> >
> > Behind the scenes we can have a SentinelMeta metaclass with all the
> > magic that could be required--including the default __repr__ method.
> >
> > What do you think?
>
> One issue with that is that such sentinels don't have their own class,
> so you can't write a strict type signature, such as `Union[str,
> NotGivenType]`.
>
> Another issue is that having these objects be classes, rather than
> normal instances of classes, could be surprising and confusing.
>
> For those two reasons, for now, I think generating a unique object
> with its own unique class is preferable.
>
> > Sorry about my detour into the rejected idea of a factory function.
>
> Please don't apologize! I put those ideas in the "Rejected Ideas"
> section mostly to have them written down with a summary of the
> considerations related to them. They shouldn't be considered finally
> rejected unless and until the PEP is finished and accepted.
>
> - Tal
>
Re: The repr of a sentinel [ In reply to ]
Thanks for this, Luciano!

My main issue with using class objects is that such use of them would be
unusual and potentially confusing. I think that is important in general,
and doubly so for something I suggest adding to the stdlib.

Additionally, I very much would like for each sentinel object to have its
own dedicated class, to allow writing strict function type signatures. Once
one adds that to a meta-class or class decorator, the complexity of the
implementation approaches what I currently have for a sentinel() function.
At that point, I can't see a clear advantage for such an approach.

- Tal

On Mon, May 24, 2021 at 7:31 PM Luciano Ramalho <luciano@ramalho.org> wrote:

> On Mon, May 24, 2021 at 11:44 AM Tal Einat <taleinat@gmail.com> wrote:
> > > But frankly Luciano's idea of a base class that can be subclassed
> seems the most startightford to me.
> >
> > Yes, and it's what I originally suggested near the beginning of this
> thread :)
>
> I am sorry to have missed your previous e-mail with a base class for
> sentinels, @Tal. I support that idea.
>
> In fact, if we have a SentinelMeta metaclass, and a Sentinel base
> class built from SentinelMeta, the Sentinel class can be used directly
> as a sentinel without the need for subclassing—if the application does
> not require a custom sentinel. If it does, then the user can subclass
> Sentinel.
>
> The SentinelMeta could be private, to discourage misuse.
>
> This is my implementation, after learning from @Tal's code:
>
>
> https://github.com/fluentpython/example-code-2e/blob/master/25-class-metaprog/sentinel/sentinel.py
>
> Since having a Sentinel base class is desirable for ease of use, I
> think it is simpler to code the __new__ method in it, instead of
> coding metaclass logic to inject __new__ in the class namespace.
>
> Best,
>
> Luciano
>
>
>
> --
> Luciano Ramalho
> | Author of Fluent Python (O'Reilly, 2015)
> | http://shop.oreilly.com/product/0636920032519.do
> | Technical Principal at ThoughtWorks
> | Twitter: @ramalhoorg
>
Re: The repr of a sentinel [ In reply to ]
On Tue, May 25, 2021 at 11:25 AM Pascal Chambon <pythoniks@gmail.com> wrote:
>
> Hello, and thanks for the PEP,
>
> I feel like the 3-lines declaration of a new sentinel would discourage a
> bit its adoption compared to just "sentinel = object()"

I now tend to agree. The new version of the draft PEP proposes a
simpler interface: NotGiven = sentinel('NotGiven')

> From what I understand from the PEP, if new classes are defined inside
> the closure of a factory function, some Python implementations would
> have trouble copying/pickling them?

The reference implementations I propose take care of this. I'll make
sure that everything works in popular alternate implementations such
as PyPy.

> Would it be doable to have a single Sentinel class, whose instances
> store their representation and some autogenerated UUID, and which
> automatically return internally stored singletons (depending on this
> UUID) when called multiple times or unpickled ?
> This would require some __new__() and unpickling magic, but nothing too
> CPython-specific (or am I missing something?).

That's certainly doable, and I believe that there are existing
implementations of sentinels that use this method. But since classes
are singletons, and it's simple to make a class that always returns
the same object, there's no need for setting a UUID and implementing
custom pickling logic as you suggest.

Another drawback of this approach is that each sentinel value wouldn't
have its own dedicated type.

- Tal
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/SPIN5YYO2R6SU4FTKW2IEQY23KBTGYHR/
Code of Conduct: http://python.org/psf/codeofconduct/
Re: The repr of a sentinel [ In reply to ]
On Tue, May 25, 2021 at 10:09 PM Eric Nieuwland
<eric.nieuwland@gmail.com> wrote:
>
>
> To add to the suggestions already given in this thread I dug into code I wrote some time ago. Offered as an inspiration.
>
> === missing.py ===
>
> from typing import Any
>
> def MISSING(klass: Any) -> Any: """ create a sentinel to indicate a missing instance of a class :param klass: the class of which an instance is missing :return: missing class object """ g = globals() missing_klass_name = f"_MISSING_{klass.__module__}_{klass.__name__}_MISSING_" if missing_klass_name not in g: g[missing_klass_name] = type( missing_klass_name, (klass,), { "__repr__": lambda x: f"MISSING({klass.__name__})", } ) return g[missing_klass_name]()
>
> ===
>
> and as a demo:
>
> === demo_missing.py ===
>
> import pickle
>
> from missing import MISSING
>
> x = MISSING(str) y = "bar" print(f"{x!r} == {y!r}: {x == y}") print(f"{x!r} is {y!r}: {x is y}") # MISSING(str) == 'bar': False # MISSING(str) is 'bar': False
>
> with open("object.pickled", "wb") as f: pickle.dump(x, f) with open("object.pickled", "rb") as f: y = pickle.load(f) print(f"{x!r} == {y!r}: {x == y}") print(f"{x!r} is {y!r}: {x is y}") # MISSING(str) == MISSING(str): True # MISSING(str) is MISSING(str): False
>
> def foo(a: int = MISSING(int), b: int = MISSING(int)): print(f"{a=} {isinstance(a, int)}") print(f"{b=} {isinstance(b, int)}") print(f"{a!r} == {b!r}: {a == b}") print(f"{a!r} is {b!r}: {a is b}")
>
> foo() # a=MISSING(int) True # b=MISSING(int) True # MISSING(int) == MISSING(int): True # MISSING(int) is MISSING(int): False
>
> foo(1) # a=1 True # b=MISSING(int) True # 1 == MISSING(int): False # 1 is MISSING(int): False
>
> class Test: ...
>
> t = MISSING(Test) print(f"{t=}") # t=MISSING(Test)
>
> ===

Wow, that's... terribly clever. Thanks for sharing this!

- Tal
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-leave@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at https://mail.python.org/archives/list/python-dev@python.org/message/AEUHYDXJF3PVGGZ5WET5AMLKNO3STQJP/
Code of Conduct: http://python.org/psf/codeofconduct/