[Python-Dev] Surely "nullable" is a reasonable name?

Tal Einat taleinat at gmail.com
Tue Apr 21 13:50:25 CEST 2015


On Sun, Apr 19, 2015 at 11:19 AM, Larry Hastings <larry at hastings.org> wrote:
>
>
> On 08/07/2014 09:41 PM, Larry Hastings wrote:
>
> Well!  It's rare that the core dev community is so consistent in its
> opinion.  I still think "nullable" is totally appropriate, but I'll change
> it to "allow_none".
>
>
> (reviving eight-month-old thread)
>
> In case anybody here is still interested in arguing about this: the Clinic
> API may be shifting a bit here.  What follows is a quick refresher course on
> Argument Clinic, followed by a discussion of the proposed new API.
>
> Here's an Argument Clinic declaration of a parameter:
>     s: str()
> The parameter is called "s", and it's specifying a converter function called
> "str" which handles converting string parameters.  The str() converter
> itself accepts parameters; since the parameters all have default values,
> they're all optional.  By default, str() maps directly to the "s" format
> unit for PyArg_ParseTuple(), as it does here.
>
> Currently str() (and a couple other converter functions) accepts a parameter
> called "types".  "types" is specified as a string, and contains an unordered
> set of whitespace-separated strings representing the Python types of the
> values this (Clinic) parameter should accept.  The default value of "types"
> for str() is "str"; the following declaration is equivalent to the
> declaration above:
>     s: str(types="str")
> Other legal values for the "types" parameter for the str converter include
> "bytes bytearray str" and "robuffer str".  Internally the types parameter is
> converted into a set of strings; passing it in as a string is a nicety for
> the caller's benefit.  (It also means that the strings "robuffer str" and
> "str robuffer" are considered equivalent.)
>
> There's a second parameter, currently called "nullable", but I was supposed
> to rename it "allow_none", so I'll use that name here.  If you pass in
> "allow_none=True" to a converter, it means "this (Clinic) parameter should
> accept the Python value None".  So, to map to the format unit "z", you would
> specify:
>   s: str(allow_none=True)
>
> And to map to the format unit "z#", you would specify:
>   s: str(types="robuffer str", allow_none=True, length=True)
>
>
> In hindsight this is all a bit silly.  I propose what I think is a much
> better API below.
>
> We should rename "types" to "accept".  "accept" should takes a set of types;
> these types specify the types of Python objects the Clinic parameter should
> accept.  For the funny pseudo-types needed in some Clinic declarations
> ("buffer", "robuffer", and "rwbuffer"), Clinic provides empty class
> declarations so these behave like types too.
>
> accept={str} is the default for the str() converter.  If you want to map to
> format unit "z", you would write this:
>     s: str(accept={str, NoneType})
> (In case you haven't seen it before: NoneType = type(None).  I don't think
> the name is registered anywhere officially in the standard library... but
> that's the name.)
>
> The upside of this approach:
>
> Way, way more obvious to the casual reader.  "types" was always meant as an
> unordered collection of types, but I felt specifying it with strings was
> unwieldy and made for poor reading ({'str', 'robuffer'}).  Passing it in as
> a single string which I internally split and put in a set() was a bad
> compromise.  But the semantics of this whitespace-delimited string were a
> bit unclear, even to the experienced Clinic hacker.  This set-of-types
> version maps exactly to what the parameter was always meant to accept in the
> first place.  As with any other code, people will read Clinic declarations
> far, far more often than they will write them, so optimizing for clarity is
> paramount.
> Zen: "There should be one (and preferably only one) obvious way to do it."
> We have a way of specifying the types this parameter should accept;
> "allow_none" adds a second.
> Zen: "Special cases aren't special enough to break the rules".  "allow_none"
> was really just a special case of one possible type for "types".
>
>
> The downside of this approach:
>
> You have to know what the default accept= set is for each converter.
> Luckily this is not onerous; there are only four converters that need an
> "accept" parameter, and their default values are all simple:
>
>         int(accept={int})
>         str(accept={str})
>         Py_UNICODE(accept={str})
>         Py_buffer(accept={buffer})
>
>         I suggest this is only a (minor) problem when writing a Clinic
> declaration.  It doesn't affect later readability, which is much more
> important.
>
> It means repeating yourself a little.  If you just want to say "I want to
> accept None too", you have to redundantly specify the default type(s)
> accepted by the converter function.  In practice, it's really only redundant
> for four or five format units, and they're not the frequently-used ones.
> Right now I only see three uses of nullable for the built-in format units
> (there are two more for my path_converter) and they're all for the str
> converter.
>
> Yes, we could create a set containing the default types accepted by each
> converter function, and just let the caller specify that and incrementally
> add +{NoneType}.  But this would be far longer than simply redundantly
> respecifying the default (e.g. "accept=str_converter.accept_default +
> {NoneType}").
>
> Sometimes the best thing is just to bite the bullet and accept a little
> redundancy.
>
>
> Does "accept" sound good, including accepting "NoneType"?

"Accept" sounds great to me.

Allowing a parameter to be None by specifying NoneType is logical and
so makes a certain amount of sense. It seems to me that this approach
will make it very common to have parameter declarations of the form
"int(accept={int, NoneType})" in the stdlib. I'm conflicted whether
this is better or worse than "int(allow_none=True)". The former makes
it clear that the parameter can accept more than a single type,
necessitating additional processing to check what the actual type of
the passed value is, which I like. So I'm +0.5 for this at the moment.

As for the default set of accepted types for various convertors, if we
could choose any syntax we liked, something like "accept=+{NoneType}"
would be much better IMO. I'm definitely against repeating the default
set of accepted types, since this would require very wide changes
whenever the default set of types for a convertor is changed, as well
as breaking compatibility for 3rd party libraries using AC.

- Tal


More information about the Python-Dev mailing list