python-peps/pep-0505.txt

202 lines
5.7 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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: