[Python-ideas] PEP 505: None-aware operators

Mark E. Haase mehaase at gmail.com
Fri Jul 20 13:16:13 EDT 2018


On Thu, Jul 19, 2018 at 2:36 PM Brendan Barnwell <brenbarn at brenbarn.net>
wrote:

>         People keep saying that this null-coalescing behavior is so common
> and
> useful, etc., but that hasn't been my experience at all.  In my
> experience, the desire to shortcut this kind of logic is more often a
> sign of corner-cutting and insufficiently specified data formats, and is
> likely to cause bugs later on.  Eventually it has to actually matter
> whether something is None or not, and these operators just kick that can
> down the road.  In terms of their abstract meaning, they are not
> remotely close to as common or useful as operators like & and |.
>
>
Brendan,

I am sure you didn't intend any offense, but the phrase "corner-cutting" is
pejorative, especially when stated as a generalization and not as a
critique of a specific example. I have used these operators in professional
projects in other languages (i.e. Dart), and I used them because they
seemed like the best tool for the job at hand, not because I was shirking
effort or short on time.

There are accepted PEPs that I don't find useful, e.g. PEP-465 (infix
matrix multiplication operator). It's not because it's a bad PEP; it's just
aimed at a type of programming that I don't do. That PEP had to address
some of the same criticisms that arise from PEP-505: there's no precedent
for that spelling, it's a small niche, and we can already to that in pure
python.[1] But I trust the Python numeric computing community in their
assertion that the @ syntax is valuable to their work.

In the same way that PEP-465 is valuable to a certain type of programming,
None-coalescing (as an abstract concept, not the concrete proposal in
PEP-505) is valuable to another type of programming. Python is often used a
glue language.[2] In my own experience, it's very common to request data
from one system, do some processing on it, and send it to another system.
For example, I might run a SQL query, convert the results to JSON, and
return it in an HTTP response. As a developer, I may not get to choose the
database schema. I may also not get to choose the JSON schema. My job is to
marshal data from one system to another. Consider the following example:

def get_user_json(user_id):
  user = user_table.find_by_id(user_id)
  user_dict = {
    # Username is always non-null in database.
    'username': user['username'],

    # Creation datetime is always non-null in database; must be ISO-8601
string in JSON.
    'created_at': user['created'].isoformat(),

    # Signature can be null in database and null in JSON.
    'signature': user['signature'],

    # Background color can be null in database but must not be null in JSON.
    'background_color': user.get('background_color', 'blue')

    # Login datetime can be null in database if user has never logged in.
Must either
    # be null or an ISO-8601 string in JSON.
    'last_logged_in_at': user['last_logged_in_at'].isoformat() if
user['last_login'] is not None else None,

    # Remaining fields omitted for brevity
  }
  return json.dumps(user_dict)

Python makes a lot of the desired behavior concise to write. For example,
the DBAPI (PEP-249) states that null and None are interchanged when reading
and writing from the database. The stdlib json module also converts between
None and null. This makes a lot of the mappings trivial to express in
Python. But it gets tricky for cases like "last_logged_in_at", where a null
is permitted by the business rules I've been given, but if it's non-null
then I need to call a method on it. As written above, it is over 100
characters long. With safe-navigation operators, it could be made to fit
the line length without loss of clarity:

    'last_logged_in_at': user['last_logged_in_at'] ?. isoformat(),

Many people in this thread prefer to write it as a block:

  if user['last_logged_in_at'] is None:
    user_dict['last_logged_in_at'] = None
  else:
    user_dict['last_logged_in_at'] = user['last_logged_in_at'].isoformat()

I agree that the block is readable, but only because my made-up function is
very short. In real-world glue code, you may have dozens of mappings, and
multiplying by 4 leads to functions that have so many lines of code that
readability is significantly worse, and that highly repetitive code
inevitably leads to typos.

It's not just databases and web services. An early draft of PEP-505
contains additional examples of where None arises in glue code.[4] And it's
not even just glue code: the standard library itself contains around 500
examples of none-coalescing or safe-navigation![5] I certainly don't think
anybody here will claim that stdlib authors have cut hundreds of corners.



[1] https://www.python.org/dev/peps/pep-0465/
[2] https://www.python.org/doc/essays/omg-darpa-mcc-position/
[3] https://www.python.org/dev/peps/pep-0249/
[4]
https://github.com/python/peps/blob/c5270848fe4481947ee951c2a415824b4dcd8a4f/pep-0505.txt#L63
[5] https://www.python.org/dev/peps/pep-0505/#examples
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180720/5b13a24f/attachment-0001.html>


More information about the Python-ideas mailing list