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

Larry Hastings larry at hastings.org
Sun Apr 19 10:19:35 CEST 2015



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"?



FWIW I originally proposed this in an issue on the tracker:

    http://bugs.python.org/issue23920

But we should probably continue the discussion here.


Cheers,


//arry/

p.s. Yes, I never actually got around to renaming "nullable".  But I was 
going to get it done before beta, honest.  And anyway remembering to do 
that is what led me down this path, which I think has been fruitful in 
its own right.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20150419/6e305f45/attachment-0001.html>


More information about the Python-Dev mailing list