202 lines
5.7 KiB
Plaintext
202 lines
5.7 KiB
Plaintext
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
|
||
>>> title ?? 'Default Title'
|
||
'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
|
||
``[]`` as a fallback," but the code puts the fallback _before_ the preferred
|
||
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.
|
||
|
||
::
|
||
|
||
data ?= []
|
||
files ?= []
|
||
headers ?= {}
|
||
params ?= {}
|
||
hooks ?= {}
|
||
|
||
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
|
||
'Global Default Title'
|
||
|
||
The direction of associativity is important because the ``None`` coalesing
|
||
operator short circuits: if its left operand is non-null, then the right
|
||
operand is not evaluated.
|
||
|
||
::
|
||
|
||
>>> def get_default(): raise Exception()
|
||
>>> 'My Title' ?? get_default()
|
||
'My Title'
|
||
|
||
|
||
Null-Aware Member Access Operator
|
||
---------------------------------
|
||
|
||
>>> 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
|
||
---------------------------------
|
||
|
||
>>> 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:
|