2015-09-18 22:10:53 -04:00
|
|
|
|
PEP: 505
|
|
|
|
|
Title: None coalescing operators
|
|
|
|
|
Version: $Revision$
|
|
|
|
|
Last-Modified: $Date$
|
|
|
|
|
Author: Mark E. Haase <mehaase@gmail.com>
|
|
|
|
|
Status: Draft
|
|
|
|
|
Type: Standards Track
|
|
|
|
|
Content-Type: text/x-rst
|
|
|
|
|
Created: 18-Sep-2015
|
|
|
|
|
Python-Version: 3.6
|
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
|
========
|
|
|
|
|
|
|
|
|
|
Several modern programming languages have so-called "null coalescing" or
|
|
|
|
|
"null aware" operators, including C#, Dart, Perl, Swift, and PHP (starting in
|
|
|
|
|
version 7). These operators provide syntactic sugar for common patterns
|
|
|
|
|
involving null references. [1]_ [2]_
|
|
|
|
|
|
|
|
|
|
* The "null coalescing" operator is a binary operator that returns its first
|
|
|
|
|
first non-null operand.
|
|
|
|
|
* The "null aware member access" operator is a binary operator that accesses
|
|
|
|
|
an instance member only if that instance is non-null. It returns null
|
|
|
|
|
otherwise.
|
|
|
|
|
* The "null aware index access" operator is a binary operator that accesses a
|
|
|
|
|
member of a collection only if that collection is non-null. It returns null
|
|
|
|
|
otherwise.
|
|
|
|
|
|
|
|
|
|
Python does not have any directly equivalent syntax. The ``or`` operator can
|
|
|
|
|
be used to similar effect but checks for a truthy value, not ``None``
|
|
|
|
|
specifically. The ternary operator ``... if ... else ...`` can be used for
|
|
|
|
|
explicit null checks but is more verbose and typically duplicates part of the
|
|
|
|
|
expression in between ``if`` and ``else``. The proposed ``None`` coalescing
|
|
|
|
|
and ``None`` aware operators ofter an alternative syntax that is more
|
|
|
|
|
intuitive and concise.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rationale
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
Null Coalescing Operator
|
|
|
|
|
------------------------
|
|
|
|
|
|
|
|
|
|
The following code illustrates how the ``None`` coalescing operators would
|
|
|
|
|
work in Python::
|
|
|
|
|
|
|
|
|
|
>>> title = 'My Title'
|
|
|
|
|
>>> title ?? 'Default Title'
|
|
|
|
|
'My Title'
|
|
|
|
|
>>> title = None
|
2015-09-19 15:10:05 -04:00
|
|
|
|
>>> title ?? 'Default Title'
|
2015-09-18 22:10:53 -04:00
|
|
|
|
'Default Title'
|
|
|
|
|
|
|
|
|
|
Similar behavior can be achieved with the ``or`` operator, but ``or`` checks
|
|
|
|
|
whether its left operand is false-y, not specifically ``None``. This can lead
|
|
|
|
|
to surprising behavior. Consider the scenario of computing the price of some
|
|
|
|
|
products a customer has in his/her shopping cart::
|
|
|
|
|
|
|
|
|
|
>>> price = 100
|
|
|
|
|
>>> requested_quantity = 5
|
|
|
|
|
>>> default_quantity = 1
|
|
|
|
|
>>> (requested_quantity or default_quantity) * price
|
|
|
|
|
500
|
|
|
|
|
>>> requested_quantity = None
|
|
|
|
|
>>> (requested_quantity or default_quantity) * price
|
|
|
|
|
100
|
|
|
|
|
>>> requested_quantity = 0
|
|
|
|
|
>>> (requested_quantity or default_quantity) * price # oops!
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
|
This type of bug is not possible with the ``None`` coalescing operator,
|
|
|
|
|
because there is no implicit type coersion to ``bool``::
|
|
|
|
|
|
|
|
|
|
>>> price = 100
|
|
|
|
|
>>> requested_quantity = 0
|
|
|
|
|
>>> default_quantity = 1
|
|
|
|
|
>>> (requested_quantity ?? default_quantity) * price
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
|
The same correct behavior can be achieved with the ternary operator. Here is
|
|
|
|
|
an excerpt from the popular Requests package::
|
|
|
|
|
|
|
|
|
|
data = [] if data is None else data
|
|
|
|
|
files = [] if files is None else files
|
|
|
|
|
headers = {} if headers is None else headers
|
|
|
|
|
params = {} if params is None else params
|
|
|
|
|
hooks = {} if hooks is None else hooks
|
|
|
|
|
|
|
|
|
|
This particular formulation has the undesirable effect of putting the operands
|
|
|
|
|
in an unintuitive order: the brain thinks, "use ``data`` if possible and use
|
2015-09-19 20:05:19 -04:00
|
|
|
|
``[]`` as a fallback," but the code puts the fallback *before* the preferred
|
2015-09-18 22:10:53 -04:00
|
|
|
|
value.
|
|
|
|
|
|
|
|
|
|
The author of this package could have written it like this instead::
|
|
|
|
|
|
|
|
|
|
data = data if data is not None else []
|
|
|
|
|
files = files if files is not None else []
|
|
|
|
|
headers = headers if headers is not None else {}
|
|
|
|
|
params = params if params is not None else {}
|
|
|
|
|
hooks = hooks if hooks is not None else {}
|
|
|
|
|
|
|
|
|
|
This ordering of the operands is more intuitive, but it requires 4 extra
|
|
|
|
|
characters (for "not "). It also highlights the repetition of identifiers:
|
|
|
|
|
``data if data``, ``files if files``, etc. The ``None`` coalescing operator
|
|
|
|
|
improves readability::
|
|
|
|
|
|
|
|
|
|
data = data ?? []
|
|
|
|
|
files = files ?? []
|
|
|
|
|
headers = headers ?? {}
|
|
|
|
|
params = params ?? {}
|
|
|
|
|
hooks = hooks ?? {}
|
|
|
|
|
|
|
|
|
|
The ``None`` coalescing operator also has a corresponding assignment shortcut.
|
|
|
|
|
|
|
|
|
|
::
|
|
|
|
|
|
2015-09-19 15:10:05 -04:00
|
|
|
|
data ?= []
|
|
|
|
|
files ?= []
|
|
|
|
|
headers ?= {}
|
|
|
|
|
params ?= {}
|
|
|
|
|
hooks ?= {}
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
|
|
|
|
The ``None`` coalescing operator is left-associative, which allows for easy
|
|
|
|
|
chaining::
|
|
|
|
|
|
|
|
|
|
>>> user_title = None
|
|
|
|
|
>>> local_default_title = None
|
|
|
|
|
>>> global_default_title = 'Global Default Title'
|
|
|
|
|
>>> title = user_title ?? local_default_title ?? global_default_title
|
2015-09-19 15:10:05 -04:00
|
|
|
|
'Global Default Title'
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-09-19 20:05:19 -04:00
|
|
|
|
The direction of associativity is important because the ``None`` coalescing
|
2015-09-18 22:20:26 -04:00
|
|
|
|
operator short circuits: if its left operand is non-null, then the right
|
2015-09-18 22:10:53 -04:00
|
|
|
|
operand is not evaluated.
|
|
|
|
|
|
2015-09-18 22:20:26 -04:00
|
|
|
|
::
|
|
|
|
|
|
2015-09-18 22:10:53 -04:00
|
|
|
|
>>> def get_default(): raise Exception()
|
|
|
|
|
>>> 'My Title' ?? get_default()
|
|
|
|
|
'My Title'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Null-Aware Member Access Operator
|
|
|
|
|
---------------------------------
|
|
|
|
|
|
2015-09-19 20:05:19 -04:00
|
|
|
|
::
|
|
|
|
|
|
2015-09-18 22:10:53 -04:00
|
|
|
|
>>> title = 'My Title'
|
|
|
|
|
>>> title.upper()
|
|
|
|
|
'MY TITLE'
|
|
|
|
|
>>> title = None
|
|
|
|
|
>>> title.upper()
|
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in <module>
|
|
|
|
|
AttributeError: 'NoneType' object has no attribute 'upper'
|
|
|
|
|
>>> title?.upper()
|
|
|
|
|
None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Null-Aware Index Access Operator
|
|
|
|
|
---------------------------------
|
|
|
|
|
|
2015-09-19 20:05:19 -04:00
|
|
|
|
::
|
|
|
|
|
|
2015-09-18 22:10:53 -04:00
|
|
|
|
>>> person = {'name': 'Mark', 'age': 32}
|
|
|
|
|
>>> person['name']
|
|
|
|
|
'Mark'
|
|
|
|
|
>>> person = None
|
|
|
|
|
>>> person['name']
|
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in <module>
|
|
|
|
|
TypeError: 'NoneType' object is not subscriptable
|
|
|
|
|
>>> person?['name']
|
|
|
|
|
None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specification
|
|
|
|
|
=============
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
References
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
|
|
.. [1] Wikipedia: Null coalescing operator
|
|
|
|
|
(https://en.wikipedia.org/wiki/Null_coalescing_operator)
|
|
|
|
|
|
|
|
|
|
.. [2] Seth Ladd's Blog: Null-aware operators in Dart
|
|
|
|
|
(http://blog.sethladd.com/2015/07/null-aware-operators-in-dart.html)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Copyright
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
..
|
|
|
|
|
Local Variables:
|
|
|
|
|
mode: indented-text
|
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
|
sentence-end-double-space: t
|
|
|
|
|
fill-column: 70
|
|
|
|
|
coding: utf-8
|
|
|
|
|
End:
|