Better repr() for sentinel objects
A common pattern, both in the stdlib and in user code, is the dedicated sentinel object to recognize an omitted argument: no_timeout = object() def get_data(source, timeout=no_timeout): if timeout is not no_timeout: source.set_alarm(timeout) return source.pop() This is a bit unclear in the function signature, as seen in help(): """ Help on function get_data in module __main__: get_data(source, timeout=<object object at 0x7f579fe53070>) """ The stdlib does this: """ Help on function create_connection in module socket: create_connection(address, timeout=<object object at 0x7fe52900b080>, source_address=None) """ # chomp docstring """ Help on function urlopen in module urllib.request: urlopen(url, data=None, timeout=<object object at 0x7fe52900b080>, *, cafile=None, capath=None, cadefault=False) """ # no docstring to chomp, actually It's not particularly useful to get the object's address. Proposal: A self-documenting Sentinel class which serves the exact same purpose. class Sentinel: def __init__(self, desc): self.desc = desc def __repr__(self): return "Sentinel(" + repr(self.desc) + ")" This can then be used just like object(), only it retains something for the benefit of its repr: no_timeout = Sentinel("No timeout") # Same function definition """ Help on function get_data in module __main__: get_data(source, timeout=Sentinel('No timeout')) """ I don't know how this interacts with Argument Clinic and C-written functions. If there's something that's being done for those that would make sentinels come out a particular way in their help() info, ideally this should be displayed the same way (or at least similarly). Thoughts? ChrisA
On 15 February 2014 11:56, Chris Angelico <rosuav@gmail.com> wrote:
A common pattern, both in the stdlib and in user code, is the dedicated sentinel object to recognize an omitted argument:
no_timeout = object() def get_data(source, timeout=no_timeout): if timeout is not no_timeout: source.set_alarm(timeout) return source.pop()
This is a bit unclear in the function signature, as seen in help():
""" Help on function get_data in module __main__:
get_data(source, timeout=<object object at 0x7f579fe53070>) """
The stdlib does this: """ Help on function create_connection in module socket:
create_connection(address, timeout=<object object at 0x7fe52900b080>, source_address=None) """ # chomp docstring
""" Help on function urlopen in module urllib.request:
urlopen(url, data=None, timeout=<object object at 0x7fe52900b080>, *, cafile=None, capath=None, cadefault=False) """ # no docstring to chomp, actually
It's not particularly useful to get the object's address. Proposal: A self-documenting Sentinel class which serves the exact same purpose.
class Sentinel: def __init__(self, desc): self.desc = desc def __repr__(self): return "Sentinel(" + repr(self.desc) + ")"
This can then be used just like object(), only it retains something for the benefit of its repr:
no_timeout = Sentinel("No timeout") # Same function definition """ Help on function get_data in module __main__:
get_data(source, timeout=Sentinel('No timeout')) """
I don't know how this interacts with Argument Clinic and C-written functions. If there's something that's being done for those that would make sentinels come out a particular way in their help() info, ideally this should be displayed the same way (or at least similarly).
There's always this: Python 3.3.4 (v3.3.4:7ff62415e426, Feb 9 2014, 00:29:34) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin Type "help", "copyright", "credits" or "license" for more information.
from unittest.mock import sentinel
sentinel.FOO
sentinel.FOO
sentinel.FOO is sentinel.FOO
True
sentinel.BarBamBaz
sentinel.BarBamBaz Michael
Thoughts?
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- http://www.voidspace.org.uk/ May you do good and not evil May you find forgiveness for yourself and forgive others May you share freely, never taking more than you give. -- the sqlite blessing http://www.sqlite.org/different.html
On Sat, Feb 15, 2014 at 11:37 PM, Michael Foord <fuzzyman@gmail.com> wrote:
There's always this:
from unittest.mock import sentinel sentinel.FOO sentinel.FOO
Looks reasonable. The implementation is pretty much the same as I had, with the additional feature of the object with __getattr__ that generates and caches them. I wouldn't like to depend on unittest, but if that could be lifted out and put into a more plausible place (and then unittest.mock could import it from there, which would mean that nothing visible would change), it'd work. Downside: The names must be unique, so they can't be as descriptive. (If two functions want to use a "No timeout" sentinel, they'd either have to go for unique names, or be sharing a sentinel, which is probably not a good thing.) I suppose the question is: Is it a good thing for callers to be able to invoke the default? Currently, urllib.request.urlopen consciously calls up the default-sentinel for socket.create_connection; if that were done as "sentinel.NO_TIMEOUT", then by definition everyone who types that *will* get the effect of omitting the argument. This is a significantly bigger change than simply "hey wouldn't it be nice if the repr() were more readable", though. ChrisA
A class like this is useful although I've called it NamedObject (by analogy to NamedTuple). It's useful in contexts other than as a sentinel, for example to represent deleted values, but the fundamental aspects are that it's a unique object that has a name. I've done two different things in my case: (1) add __setattr__ so you can't set values on this object (to prevent someone that gets the object from operating on it as if it's some other object). (2) printing str(descr) not repr(descr). Since the standard use case is passing a string to the constructor, it's not valuable for it to print quotes around it. I also use <> instead of NamedObject() but I don't care about that distinction.
a = NamedObject('DELETED'), NamedObject('DELETED') a (<DELETED>, <DELETED>) a[0] == a[1] False
Note the last line: this is different from how unittest.mock.sentinel works. I find both cases useful (and particularly find the unittest.mock.sentinel version more useful in unittests). Writing this up I suppose UniqueObject might be a better name for this than NamedObject. --- Bruce Learn how hackers think: http://j.mp/gruyere-security On Sat, Feb 15, 2014 at 3:56 AM, Chris Angelico <rosuav@gmail.com> wrote:
A common pattern, both in the stdlib and in user code, is the dedicated sentinel object to recognize an omitted argument:
no_timeout = object() def get_data(source, timeout=no_timeout): if timeout is not no_timeout: source.set_alarm(timeout) return source.pop()
This is a bit unclear in the function signature, as seen in help():
""" Help on function get_data in module __main__:
get_data(source, timeout=<object object at 0x7f579fe53070>) """
The stdlib does this: """ Help on function create_connection in module socket:
create_connection(address, timeout=<object object at 0x7fe52900b080>, source_address=None) """ # chomp docstring
""" Help on function urlopen in module urllib.request:
urlopen(url, data=None, timeout=<object object at 0x7fe52900b080>, *, cafile=None, capath=None, cadefault=False) """ # no docstring to chomp, actually
It's not particularly useful to get the object's address. Proposal: A self-documenting Sentinel class which serves the exact same purpose.
class Sentinel: def __init__(self, desc): self.desc = desc def __repr__(self): return "Sentinel(" + repr(self.desc) + ")"
This can then be used just like object(), only it retains something for the benefit of its repr:
no_timeout = Sentinel("No timeout") # Same function definition """ Help on function get_data in module __main__:
get_data(source, timeout=Sentinel('No timeout')) """
I don't know how this interacts with Argument Clinic and C-written functions. If there's something that's being done for those that would make sentinels come out a particular way in their help() info, ideally this should be displayed the same way (or at least similarly).
Thoughts?
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
participants (3)
-
Bruce Leban
-
Chris Angelico
-
Michael Foord