Mailing List Archive

1 2 3  View All
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/

1 2 3  View All