PEP: 505 Title: None coalescing operators Version: $Revision$ Last-Modified: $Date$ Author: Mark E. Haase 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`` coalescing 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 "", line 1, in 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 "", line 1, in 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: