2015-09-18 22:10:53 -04:00
|
|
|
|
PEP: 505
|
2015-10-20 11:25:57 -04:00
|
|
|
|
Title: None-aware operators
|
2015-09-18 22:10:53 -04:00
|
|
|
|
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
|
|
|
|
|
========
|
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
Several modern programming languages have so-called "``null``-coalescing" or
|
|
|
|
|
"``null``- aware" operators, including C# [1]_, Dart [2]_, Perl, Swift, and PHP
|
|
|
|
|
(starting in version 7). These operators provide syntactic sugar for common
|
|
|
|
|
patterns involving null references.
|
|
|
|
|
|
|
|
|
|
* The "``null``-coalescing" operator is a binary operator that returns its left
|
|
|
|
|
operand if it is not ``null``. Otherwise it returns its right operand.
|
|
|
|
|
* The "``null``-aware member access" operator accesses an instance member only
|
|
|
|
|
if that instance is non-``null``. Otherwise it returns ``null``. (This is also
|
|
|
|
|
called a "safe navigation" operator.)
|
2015-10-20 21:44:00 -04:00
|
|
|
|
* The "``null``-aware index access" operator accesses an element of a collection
|
2015-10-20 11:25:57 -04:00
|
|
|
|
only if that collection is non-``null``. Otherwise it returns ``null``. (This
|
|
|
|
|
is another type of "safe navigation" operator.)
|
|
|
|
|
|
|
|
|
|
The purpose of this PEP is to explore the possibility of implementing similar
|
|
|
|
|
operators in Python. It provides some background material and then offers
|
|
|
|
|
several competing alternatives for implementation.
|
|
|
|
|
|
|
|
|
|
The initial reaction to this idea is majority negative. Even if ultimately
|
|
|
|
|
rejected, this PEP still serves a purpose: to fully document the reasons why
|
|
|
|
|
Python should not add this behavior, so that it can be pointed to in the future
|
|
|
|
|
when the question inevitably arises again. (This is the null alternative, so to
|
|
|
|
|
speak!)
|
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
This proposal advances multiple alternatives, and it should be considered
|
|
|
|
|
severable. It may be accepted in whole or in part. For example, the safe
|
|
|
|
|
navigation operators might be rejected even if the ``null``-coalescing operator
|
|
|
|
|
is approved, or vice-versa.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
Of course, Python does not have ``null``; it has ``None``, which is conceptually
|
|
|
|
|
distinct. Although this PEP is inspired by "``null``-aware" operators in other
|
|
|
|
|
languages, it uses the term "``None``-aware" operators to describe some
|
|
|
|
|
hypothetical Python implementations.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Background
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
|
|
Specialness of ``None``
|
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
|
|
The Python language does not currently define any special behavior for ``None``.
|
|
|
|
|
This PEP suggests making ``None`` a special case. This loss of generality is a
|
|
|
|
|
noticeable drawback of the proposal. A generalization of ``None``-aware
|
|
|
|
|
operators is set forth later in this document in order to avoid this
|
|
|
|
|
specialization.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Utility of ``None``
|
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
|
|
One common criticism of adding special syntax for ``None`` is that ``None``
|
|
|
|
|
shouldn't be used in the first place: it's a code smell. A related criticism is
|
|
|
|
|
that ``None``-aware operators are used to silence errors (such as the novice
|
|
|
|
|
misunderstanding of an implicit ``return None``) akin to `PHP's @ operator
|
|
|
|
|
<http://php.net/manual/en/language.operators.errorcontrol.php>`_. Therefore,
|
|
|
|
|
the utility of ``None`` must be debated before discussing whether to add new
|
|
|
|
|
behavior around it.
|
|
|
|
|
|
|
|
|
|
Python does not have any concept of ``null``. Every Python identifier must
|
|
|
|
|
refer to an instance, so there cannot be any ``null`` references. Python does
|
|
|
|
|
have a special instance called ``None`` that can be used to represent missing
|
|
|
|
|
values, but ``None`` is conceptually distinct from ``null``.
|
|
|
|
|
|
|
|
|
|
The most frequent use of ``None`` in Python is to provide a default value for
|
|
|
|
|
optional arguments when some other default object is unwieldy. For example:
|
|
|
|
|
``def get(url, proxy=None):``. In this case, ``proxy`` is an optional
|
|
|
|
|
argument. If ``proxy`` is ``None``, then the request should be sent directly to
|
|
|
|
|
the server; otherwise, the request should be routed through the specified proxy
|
|
|
|
|
server. This use of ``None`` is preferred here to some other sentinel value or
|
|
|
|
|
the Null Object Pattern. [3]_
|
|
|
|
|
|
|
|
|
|
Examples of this form abound. Consider ``types.py`` in the standard library::
|
|
|
|
|
|
|
|
|
|
def prepare_class(name, bases=(), kwds=None):
|
|
|
|
|
if kwds is None:
|
|
|
|
|
kwds = {}
|
|
|
|
|
else:
|
|
|
|
|
kwds = dict(kwds)
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
Another frequent use of ``None`` is interfacing with external systems. Many of
|
|
|
|
|
those other systems have a concept of ``null``. Therefore, Python code must have
|
|
|
|
|
a way of representing ``null``, and typically it is represented by ``None``. For
|
|
|
|
|
example, databases can have ``null`` values, and most Python database drivers
|
|
|
|
|
will convert ``null`` to ``None`` when retrieving data from a database, and will
|
|
|
|
|
convert from ``None`` back to ``null`` when sending data to a database.
|
|
|
|
|
|
|
|
|
|
This convention of interchanging ``null`` and ``None`` is widespread in Python.
|
|
|
|
|
It is canonized in the Python DBAPI (PEP-249). [4]_ The ``json`` module in the
|
|
|
|
|
standard library and the third party PyYAML package both use ``None`` to
|
|
|
|
|
represent their respective languages' ``null``.
|
|
|
|
|
|
|
|
|
|
The C language ``null`` often bleeds into Python, too, particularly for thin
|
|
|
|
|
wrappers around C libraries. For example, in ``pyopenssl``, the ``X509`` class
|
|
|
|
|
has `a get_notBefore() method
|
|
|
|
|
<https://github.com/pyca/pyopenssl/blob/3257877f8846e4357b495fa6c9344d01b11cf16d
|
|
|
|
|
/OpenSSL/crypto.py#L1219>`_ that returns either a timestamp or ``None``. This
|
|
|
|
|
function is a thin wrapper around an OpenSSL function with the return type
|
2015-10-20 21:44:00 -04:00
|
|
|
|
``ASN1_TIME *``. Because this C pointer may be ``null``, the Python wrapper must
|
|
|
|
|
be able to represent ``null``, and ``None`` is the chosen representation.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
The representation of ``null`` is particularly noticeable when Python code is
|
|
|
|
|
marshalling data between two systems. For example, consider a Python server that
|
|
|
|
|
fetches data from a database and converts it to JSON for consumption by another
|
|
|
|
|
process. In this case, it's often desirable that ``null`` in the database can be
|
|
|
|
|
easily translated to ``null`` in JSON. If ``None`` is not used for this purpose,
|
|
|
|
|
then each package will have to define its own representation of ``null``, and
|
|
|
|
|
converting between these representations adds unnecessary complexity to the
|
|
|
|
|
Python glue code.
|
|
|
|
|
|
|
|
|
|
Therefore, the preference for avoiding ``None`` is nothing more than a
|
2015-10-20 21:44:00 -04:00
|
|
|
|
preference. ``None`` has legitimate uses, particularly in specific types of
|
2015-10-20 11:25:57 -04:00
|
|
|
|
software. Any hypothetical ``None``-aware operators should be construed as
|
|
|
|
|
syntactic sugar for simplifying common patterns involving ``None``, and *should
|
|
|
|
|
not be construed* as error handling behavior.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Behavior In Other Languages
|
|
|
|
|
---------------------------
|
|
|
|
|
|
|
|
|
|
Given that ``null``-aware operators exist in other modern languages, it may be
|
|
|
|
|
helpful to quickly understand how they work in those languages.
|
|
|
|
|
|
|
|
|
|
C# example::
|
|
|
|
|
|
|
|
|
|
/* Null-coalescing. */
|
|
|
|
|
|
|
|
|
|
String s1 = null;
|
|
|
|
|
String s2 = "hello";
|
|
|
|
|
String s3 = s1 ?? s2;
|
|
|
|
|
Console.WriteLine("s3 is: " + s3);
|
|
|
|
|
// s3 is: hello
|
|
|
|
|
|
|
|
|
|
/* Null-aware member access, a.k.a. safe navigation. */
|
|
|
|
|
|
|
|
|
|
Console.WriteLine("s1.Length is: " + s1?.Length);
|
|
|
|
|
Console.WriteLine("s2.Length is: " + s2?.Length);
|
|
|
|
|
// s1.Length is:
|
|
|
|
|
// s2.Length is: 5
|
|
|
|
|
|
|
|
|
|
/* Null-aware index access, a.k.a. safe navigation. */
|
|
|
|
|
|
|
|
|
|
Dictionary<string,string> d1 = null;
|
|
|
|
|
Dictionary<string,string> d2 = new Dictionary<string, string>
|
|
|
|
|
{
|
|
|
|
|
{ "foo", "bar" },
|
|
|
|
|
{ "baz", "bat" }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Console.WriteLine("d1[\"foo\"] is: " + d1?["foo"]);
|
|
|
|
|
Console.WriteLine("d2[\"foo\"] is: " + d2?["foo"]);
|
|
|
|
|
// d1["foo"] is:
|
|
|
|
|
// d2["foo"] is: bar
|
|
|
|
|
|
|
|
|
|
/* Short Circuiting */
|
|
|
|
|
|
|
|
|
|
Console.WriteLine("s1 trim/upper is: " + s1?.Trim().Length);
|
|
|
|
|
Console.WriteLine("s2 trim/upper is: " + s2?.Trim().Length);
|
|
|
|
|
// s1 trimmed length is:
|
|
|
|
|
// s2 trimmed length is: 5
|
|
|
|
|
|
|
|
|
|
String s4 = s1 ?? s2 ?? DoError();
|
|
|
|
|
Console.WriteLine("s4 is: " + s4)
|
|
|
|
|
// s4 is: hello
|
|
|
|
|
|
|
|
|
|
A `working example <https://dotnetfiddle.net/SxQNG8>`_ can be viewed online.
|
|
|
|
|
|
|
|
|
|
Of utmost importance, notice the short circuiting behavior. The short circuiting
|
|
|
|
|
of ``??`` is similar to short circuiting of other boolean operators such as
|
|
|
|
|
``||`` or ``&&`` and should not be surprising. Helpfully, `?.` is *also* short
|
|
|
|
|
circuiting: ``s1?.Trim()`` evaluates to null, but ``s1?.Trim().Length`` does not
|
|
|
|
|
attempt to dereference the ``null`` pointer.
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rationale
|
|
|
|
|
=========
|
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
Existing Alternatives
|
|
|
|
|
---------------------
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
Python does not have any specific ``None``-aware operators, but it does have
|
|
|
|
|
operators that can be used for a similar purpose. This section describes why
|
|
|
|
|
these alternatives may be undesirable for some common ``None`` patterns.
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
``or`` Operator
|
|
|
|
|
~~~~~~~~~~~~~~~
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
An experienced Python developer should know how ``or`` works and be capable of
|
|
|
|
|
avoiding bugs like this. However, getting in the habit of using ``or`` for this
|
2015-10-20 21:44:00 -04:00
|
|
|
|
purpose still might cause an experienced developer to occasionally make this
|
|
|
|
|
mistake, especially when refactoring existing code and not carefully paying
|
2015-10-20 11:25:57 -04:00
|
|
|
|
attention to the possible values of the left-hand operand.
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
For inexperienced developers, the problem is worse. The top Google hit for
|
|
|
|
|
"python null coalesce" is a `StackOverflow page
|
|
|
|
|
<http://stackoverflow.com/questions/4978738/is-there-a-python-equivalent-of-
|
|
|
|
|
the-c-sharp-null-coalescing-operator>`_, and the top answer says to use ``or``.
|
|
|
|
|
The top answer goes on to explain the caveats of using ``or`` like this, but how
|
2015-10-27 12:59:43 -04:00
|
|
|
|
many beginning developers go on to read all those caveats? The accepted answer
|
|
|
|
|
on `a more recent question <http://stackoverflow.com/questions/13710631/is-
|
|
|
|
|
there-shorthand-for-returning-a -default-value-if-none-in-python>`_ says to use
|
|
|
|
|
``or`` without any caveats at all. These two questions have a combined 26,000
|
|
|
|
|
views!
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
The common usage of ``or`` for the purpose of providing default values is
|
|
|
|
|
undeniable, and yet it is also booby-trapped for unsuspecting newcomers. This
|
|
|
|
|
suggests that a safe operator for providing default values would have positive
|
2015-10-27 12:59:43 -04:00
|
|
|
|
utility. While some critics claim that ``None``-aware operators will be abused
|
2015-10-20 11:25:57 -04:00
|
|
|
|
for error handling, they are no more prone to abuse than ``or`` is.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ternary Operator
|
|
|
|
|
~~~~~~~~~~~~~~~~
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2016-07-11 11:14:08 -04:00
|
|
|
|
Another common way to initialize default values is to use the ternary operator.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
Here is an excerpt from the popular `Requests package <https://github.com/kennet
|
|
|
|
|
hreitz/requests/blob/14a555ac716866678bf17e43e23230d81a8149f5/requests/models.py
|
|
|
|
|
#L212>`_::
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
|
|
|
|
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:
|
2015-10-20 21:44:00 -04:00
|
|
|
|
``data if data``, ``files if files``, etc. This example benefits from short
|
|
|
|
|
identifiers, but what if the tested expression is longer and/or has side
|
|
|
|
|
effects? This is addressed in the next section.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Motivating Examples
|
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
|
|
The purpose of this PEP is to simplify some common patterns involving ``None``.
|
|
|
|
|
This section presents some examples of common ``None`` patterns and explains
|
|
|
|
|
the drawbacks.
|
|
|
|
|
|
|
|
|
|
This first example is from a Python web crawler that uses the popular Flask
|
|
|
|
|
framework as a front-end. This function retrieves information about a web site
|
|
|
|
|
from a SQL database and formats it as JSON to send to an HTTP client::
|
|
|
|
|
|
|
|
|
|
class SiteView(FlaskView):
|
|
|
|
|
@route('/site/<id_>', methods=['GET'])
|
|
|
|
|
def get_site(self, id_):
|
|
|
|
|
site = db.query('site_table').find(id_)
|
|
|
|
|
|
|
|
|
|
return jsonify(
|
|
|
|
|
first_seen=site.first_seen.isoformat() if site.first_seen is not None else None,
|
|
|
|
|
id=site.id,
|
|
|
|
|
is_active=site.is_active,
|
|
|
|
|
last_seen=site.last_seen.isoformat() if site.last_seen is not None else None,
|
|
|
|
|
url=site.url.rstrip('/')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
Both ``first_seen`` and ``last_seen`` are allowed to be ``null`` in the
|
|
|
|
|
database, and they are also allowed to be ``null`` in the JSON response. JSON
|
2016-05-03 05:03:16 -04:00
|
|
|
|
does not have a native way to represent a ``datetime``, so the server's
|
2016-07-11 11:14:08 -04:00
|
|
|
|
contract states that any non-``null`` date is represented as an ISO-8601 string.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
Note that this code is invalid by PEP-8 standards: several lines are over the
|
|
|
|
|
line length limit. In fact, *including it in this document* violates the PEP
|
|
|
|
|
formatting standard! But it's not unreasonably indented, nor are any of the
|
|
|
|
|
identifiers excessively long. The excessive line length is due to the
|
|
|
|
|
repetition of identifiers on both sides of the ternary ``if`` and the verbosity
|
|
|
|
|
of the ternary itself (10 characters out of a 78 character line length).
|
|
|
|
|
|
|
|
|
|
One way to fix this code is to replace each ternary with a full ``if/else``
|
|
|
|
|
block::
|
|
|
|
|
|
|
|
|
|
class SiteView(FlaskView):
|
|
|
|
|
@route('/site/<id_>', methods=['GET'])
|
|
|
|
|
def get_site(self, id_):
|
|
|
|
|
site = db.query('site_table').find(id_)
|
|
|
|
|
|
|
|
|
|
if site.first_seen is None:
|
|
|
|
|
first_seen = None
|
|
|
|
|
else:
|
|
|
|
|
first_seen = site.first_seen.isoformat()
|
|
|
|
|
|
|
|
|
|
if site.last_seen is None:
|
|
|
|
|
last_seen = None
|
|
|
|
|
else:
|
|
|
|
|
last_seen = site.last_seen.isoformat()
|
|
|
|
|
|
|
|
|
|
return jsonify(
|
|
|
|
|
first_seen=first_seen,
|
|
|
|
|
id=site.id,
|
|
|
|
|
is_active=site.is_active,
|
|
|
|
|
last_seen=last_seen,
|
|
|
|
|
url=site.url.rstrip('/')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
This version definitely isn't *bad*. It is easy to read and understand. On the
|
|
|
|
|
other hand, adding 8 lines of code to express this common behavior feels a bit
|
|
|
|
|
heavy, especially for a deliberately simplified example. If a larger, more
|
|
|
|
|
complicated data model was being used, then it would get tedious to continually
|
|
|
|
|
write in this long form. The readability would start to suffer as the number of
|
|
|
|
|
lines in the function grows, and a refactoring would be forced.
|
|
|
|
|
|
|
|
|
|
Another alternative is to rename some of the identifiers::
|
|
|
|
|
|
|
|
|
|
class SiteView(FlaskView):
|
|
|
|
|
@route('/site/<id_>', methods=['GET'])
|
|
|
|
|
def get_site(self, id_):
|
|
|
|
|
site = db.query('site_table').find(id_)
|
|
|
|
|
|
|
|
|
|
fs = site.first_seen
|
|
|
|
|
ls = site.last_seen
|
|
|
|
|
|
|
|
|
|
return jsonify(
|
|
|
|
|
first_seen=fs.isodate() if fs is not None else None,
|
|
|
|
|
id=site.id,
|
|
|
|
|
is_active=site.is_active,
|
|
|
|
|
last_seen=ls.isodate() if ls is not None else None,,
|
|
|
|
|
url=site.url.rstrip('/')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
This adds fewer lines of code than the previous example, but it comes at the
|
|
|
|
|
expense of introducing extraneous identifiers that amount to nothing more than
|
|
|
|
|
aliases. These new identifiers are short enough to fit a ternary expression onto
|
|
|
|
|
one line, but the identifiers are also less intuitive, e.g. ``fs`` versus
|
|
|
|
|
``first_seen``.
|
|
|
|
|
|
|
|
|
|
As a quick preview, consider an alternative rewrite using a new operator ``💩``.
|
|
|
|
|
(This spelling of the operator is merely a placeholder so that the *concept* can
|
|
|
|
|
be debated without arguing about *spelling*. It is not intended to reflect the
|
|
|
|
|
public's opinion of said operator. It may, however, bring new meaning to the
|
|
|
|
|
phrase "code smell".)::
|
|
|
|
|
|
|
|
|
|
class SiteView(FlaskView):
|
|
|
|
|
@route('/site/<id_>', methods=['GET'])
|
|
|
|
|
def get_site(self, id_):
|
|
|
|
|
site = db.query('site_table').find(id_)
|
|
|
|
|
|
|
|
|
|
return jsonify(
|
|
|
|
|
first_seen=site💩first_seen.isoformat(),
|
|
|
|
|
id=site.id,
|
|
|
|
|
is_active=site.is_active,
|
|
|
|
|
last_seen=site💩last_seen.isoformat(),
|
|
|
|
|
url=site.url.rstrip('/')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
The ``💩`` operator behaves as a "safe navigation" operator, allowing a more
|
|
|
|
|
concise syntax where the expression ``site.first_seen`` is not duplicated.
|
|
|
|
|
|
|
|
|
|
The next example is from a trending project on GitHub called `Grab
|
|
|
|
|
<https://github.com/lorien/grab/blob/4c95b18dcb0fa88eeca81f5643c0ebfb114bf728/grab/upload.py>`_,
|
|
|
|
|
which is a Python scraping library::
|
|
|
|
|
|
|
|
|
|
class BaseUploadObject(object):
|
|
|
|
|
def find_content_type(self, filename):
|
|
|
|
|
ctype, encoding = mimetypes.guess_type(filename)
|
|
|
|
|
if ctype is None:
|
|
|
|
|
return 'application/octet-stream'
|
|
|
|
|
else:
|
|
|
|
|
return ctype
|
|
|
|
|
|
|
|
|
|
class UploadContent(BaseUploadObject):
|
|
|
|
|
def __init__(self, content, filename=None, content_type=None):
|
|
|
|
|
self.content = content
|
|
|
|
|
if filename is None:
|
|
|
|
|
self.filename = self.get_random_filename()
|
|
|
|
|
else:
|
|
|
|
|
self.filename = filename
|
|
|
|
|
if content_type is None:
|
|
|
|
|
self.content_type = self.find_content_type(self.filename)
|
|
|
|
|
else:
|
|
|
|
|
self.content_type = content_type
|
|
|
|
|
|
|
|
|
|
class UploadFile(BaseUploadObject):
|
|
|
|
|
def __init__(self, path, filename=None, content_type=None):
|
|
|
|
|
self.path = path
|
|
|
|
|
if filename is None:
|
|
|
|
|
self.filename = os.path.split(path)[1]
|
|
|
|
|
else:
|
|
|
|
|
self.filename = filename
|
|
|
|
|
if content_type is None:
|
|
|
|
|
self.content_type = self.find_content_type(self.filename)
|
|
|
|
|
else:
|
|
|
|
|
self.content_type = content_type
|
|
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
I don't know the author of the Grab project. I used it as an example
|
|
|
|
|
because it is a trending repo on GitHub and it has good examples of common
|
|
|
|
|
``None`` patterns.
|
|
|
|
|
|
|
|
|
|
This example contains several good examples of needing to provide default
|
|
|
|
|
values. It is a bit verbose as it is, and it is certainly not improved by the
|
|
|
|
|
ternary operator::
|
|
|
|
|
|
|
|
|
|
class BaseUploadObject(object):
|
|
|
|
|
def find_content_type(self, filename):
|
|
|
|
|
ctype, encoding = mimetypes.guess_type(filename)
|
|
|
|
|
return 'application/octet-stream' if ctype is None else ctype
|
|
|
|
|
|
|
|
|
|
class UploadContent(BaseUploadObject):
|
|
|
|
|
def __init__(self, content, filename=None, content_type=None):
|
|
|
|
|
self.content = content
|
|
|
|
|
self.filename = self.get_random_filename() if filename \
|
|
|
|
|
is None else filename
|
|
|
|
|
self.content_type = self.find_content_type(self.filename) \
|
|
|
|
|
if content_type is None else content_type
|
|
|
|
|
|
|
|
|
|
class UploadFile(BaseUploadObject):
|
|
|
|
|
def __init__(self, path, filename=None, content_type=None):
|
|
|
|
|
self.path = path
|
|
|
|
|
self.filename = os.path.split(path)[1] if filename is \
|
|
|
|
|
None else filename
|
|
|
|
|
self.content_type = self.find_content_type(self.filename) \
|
|
|
|
|
if content_type is None else content_type
|
|
|
|
|
|
|
|
|
|
The first ternary expression is tidy, but it reverses the intuitive order of
|
|
|
|
|
the operands: it should return ``ctype`` if it has a value and use the string
|
|
|
|
|
literal as fallback. The other ternary expressions are unintuitive and so
|
|
|
|
|
long that they must be wrapped. The overall readability is worsened, not
|
|
|
|
|
improved.
|
|
|
|
|
|
|
|
|
|
This code *might* be improved, though, if there was a syntactic shortcut for
|
|
|
|
|
this common need to supply a default value. We'll assume the fictitious
|
2016-09-19 15:42:18 -04:00
|
|
|
|
operator ``🔑`` to avoid a premature debate about the spelling of said
|
2015-10-20 11:25:57 -04:00
|
|
|
|
operator::
|
|
|
|
|
|
|
|
|
|
class BaseUploadObject(object):
|
|
|
|
|
def find_ctype(self, filename):
|
|
|
|
|
ctype, encoding = mimetypes.guess_type(filename)
|
2016-09-19 15:42:18 -04:00
|
|
|
|
return ctype 🔑 'application/octet-stream'
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
class UploadContent(BaseUploadObject):
|
|
|
|
|
def __init__(self, content, filename=None, content_type=None):
|
|
|
|
|
self.content = content
|
2016-09-19 15:42:18 -04:00
|
|
|
|
self.filename = filename 🔑 self.get_random_filename()
|
|
|
|
|
self.content_type = content_type 🔑 self.find_ctype(self.filename)
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
class UploadFile(BaseUploadObject):
|
|
|
|
|
def __init__(self, path, filename=None, content_type=None):
|
|
|
|
|
self.path = path
|
2016-09-19 15:42:18 -04:00
|
|
|
|
self.filename = filename 🔑 os.path.split(path)[1]
|
|
|
|
|
self.content_type = content_type 🔑 self.find_ctype(self.filename)
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
This syntax has an intuitive ordering of the operands, e.g. ``ctype`` -- the
|
|
|
|
|
preferred value -- comes before the fallback value. The terseness of the syntax
|
|
|
|
|
also makes for fewer lines of code and less code to visually parse.
|
|
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
I cheated on the last example: I renamed ``find_content_type`` to
|
|
|
|
|
``find_ctype`` in order to fit two of the lines under 80 characters. If you
|
|
|
|
|
find this underhanded, you can go back and apply the same renaming to the
|
|
|
|
|
previous 2 examples. You'll find that it doesn't change the
|
|
|
|
|
conclusions.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Usage Of ``None`` In The Standard Library
|
|
|
|
|
-----------------------------------------
|
|
|
|
|
|
|
|
|
|
The previous sections show some code patterns that are claimed to be "common",
|
2015-10-20 21:44:00 -04:00
|
|
|
|
but how common are they? The attached script `find-pep505.py
|
2016-09-19 15:56:22 -04:00
|
|
|
|
<https://github.com/python/peps/blob/master/pep-0505/find-pep505.py>`_ is meant to
|
2015-10-20 21:44:00 -04:00
|
|
|
|
answer this question. It uses the ``ast`` module to search for variations of the
|
|
|
|
|
following patterns in any ``*.py`` file.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
>>> # None-coalescing if block
|
|
|
|
|
...
|
|
|
|
|
>>> if a is None:
|
|
|
|
|
... a = b
|
|
|
|
|
|
|
|
|
|
>>> # [Possible] None-coalescing "or" operator
|
|
|
|
|
...
|
|
|
|
|
>>> a or 'foo'
|
|
|
|
|
>>> a or []
|
|
|
|
|
>>> a or {}
|
|
|
|
|
|
|
|
|
|
>>> # None-coalescing ternary
|
|
|
|
|
...
|
|
|
|
|
>>> a if a is not None else b
|
|
|
|
|
>>> b if a is None else a
|
|
|
|
|
|
|
|
|
|
>>> # Safe navigation "and" operator
|
|
|
|
|
...
|
|
|
|
|
>>> a and a.foo
|
|
|
|
|
>>> a and a['foo']
|
|
|
|
|
>>> a and a.foo()
|
|
|
|
|
|
|
|
|
|
>>> # Safe navigation if block
|
|
|
|
|
...
|
|
|
|
|
>>> if a is not None:
|
|
|
|
|
... a.foo()
|
|
|
|
|
|
|
|
|
|
>>> # Safe navigation ternary
|
|
|
|
|
...
|
|
|
|
|
>>> a.foo if a is not None else b
|
|
|
|
|
>>> b if a is None else a.foo
|
|
|
|
|
|
|
|
|
|
This script takes one or more names of Python source files to analyze::
|
|
|
|
|
|
|
|
|
|
$ python3 find-pep505.py test.py
|
|
|
|
|
$ find /usr/lib/python3.4 -name '*.py' | xargs python3 find-pep505.py
|
|
|
|
|
|
|
|
|
|
The script prints out any matches it finds. Sample::
|
|
|
|
|
|
|
|
|
|
None-coalescing if block: /usr/lib/python3.4/inspect.py:594
|
|
|
|
|
if _filename is None:
|
|
|
|
|
_filename = getsourcefile(object) or getfile(object)
|
|
|
|
|
|
|
|
|
|
[Possible] None-coalescing `or`: /usr/lib/python3.4/lib2to3/refactor.py:191
|
|
|
|
|
self.explicit = explicit or []
|
|
|
|
|
|
|
|
|
|
None-coalescing ternary: /usr/lib/python3.4/decimal.py:3909
|
|
|
|
|
self.clamp = clamp if clamp is not None else dc.clamp
|
|
|
|
|
|
|
|
|
|
Safe navigation `and`: /usr/lib/python3.4/weakref.py:512
|
|
|
|
|
obj = info and info.weakref()
|
|
|
|
|
|
|
|
|
|
Safe navigation `if` block: /usr/lib/python3.4/http/cookiejar.py:1895
|
|
|
|
|
if k is not None:
|
|
|
|
|
lc = k.lower()
|
|
|
|
|
else:
|
|
|
|
|
lc = None
|
|
|
|
|
|
|
|
|
|
Safe navigation ternary: /usr/lib/python3.4/sre_parse.py:856
|
|
|
|
|
literals = [None if s is None else s.encode('latin-1') for s in literals]
|
|
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
Coalescing with ``or`` is marked as a "possible" match, because it's not
|
2015-10-20 21:44:00 -04:00
|
|
|
|
trivial to infer whether ``or`` is meant to coalesce False-y values
|
2015-10-20 11:25:57 -04:00
|
|
|
|
(correct) or if it meant to coalesce ``None`` (incorrect). On the other
|
2015-10-20 21:44:00 -04:00
|
|
|
|
hand, we assume that ``and`` is always incorrect for safe navigation.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
The script has been tested against `test.py
|
2016-09-19 15:56:22 -04:00
|
|
|
|
<https://github.com/python/peps/blob/master/pep-0505/test.py>`_ and the Python 3.4
|
2015-10-20 21:44:00 -04:00
|
|
|
|
standard library, but it should work on any arbitrary Python 3 source code. The
|
|
|
|
|
complete output from running it against the standard library is attached to this
|
2016-09-11 20:01:02 -04:00
|
|
|
|
proposal as
|
2016-09-19 15:56:22 -04:00
|
|
|
|
`find-pep505.out <https://github.com/python/peps/blob/master/pep-0505/find-pep505.out>`_.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
The script counts how many matches it finds and prints the totals at the
|
|
|
|
|
end::
|
|
|
|
|
|
|
|
|
|
Total None-coalescing `if` blocks: 426
|
|
|
|
|
Total [possible] None-coalescing `or`: 119
|
|
|
|
|
Total None-coalescing ternaries: 21
|
|
|
|
|
Total Safe navigation `and`: 9
|
|
|
|
|
Total Safe navigation `if` blocks: 55
|
|
|
|
|
Total Safe navigation ternaries: 7
|
|
|
|
|
|
|
|
|
|
This is a total of 637 possible matches for these common code patterns in the
|
|
|
|
|
standard library. Allowing for some false positives and false negatives, it is
|
|
|
|
|
fair to say that these code patterns are definitely common in the standard
|
|
|
|
|
library.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rejected Ideas
|
|
|
|
|
--------------
|
|
|
|
|
|
|
|
|
|
Several related ideas were discussed on python-ideas, and some of these were
|
|
|
|
|
roundly rejected by BDFL, the community, or both. For posterity's sake, some of
|
|
|
|
|
those ideas are recorded here.
|
|
|
|
|
|
|
|
|
|
``None``-aware Function Call
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
The ``None``-aware syntax applies to attribute and index access, so it seems
|
2015-10-20 21:44:00 -04:00
|
|
|
|
natural to ask if it should also apply to function invocation syntax. It might
|
|
|
|
|
be written as ``foo?()``, where ``foo`` is only called if it is not None. This
|
|
|
|
|
idea was quickly rejected, for several reasons.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
First, no other mainstream language has such syntax. Moreover, it would be
|
|
|
|
|
difficult to discern if a function call returned ``None`` because the function
|
|
|
|
|
itself returned ``None`` or because it was short-circuited. Finally, Python
|
2015-10-20 11:25:57 -04:00
|
|
|
|
evaluates arguments to a function before it looks up the function itself, so
|
|
|
|
|
``foo?(bar())`` would still call ``bar()`` even if ``foo`` is ``None``. This
|
|
|
|
|
behaviour is unexpected for a so-called "short-circuiting" operator.
|
|
|
|
|
|
|
|
|
|
Instead, the "``None``-severing" operator is proposed below. This operator
|
|
|
|
|
offers a concise form for writing ``None``-aware function expressions that is
|
|
|
|
|
truly short-circuiting.
|
|
|
|
|
|
|
|
|
|
``?`` Unary Postfix Operator
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
To generalize the ``None``-aware behavior and limit the number of new operators
|
|
|
|
|
introduced, a unary, postfix operator spelled ``?`` was suggested. The idea is
|
2015-10-20 21:44:00 -04:00
|
|
|
|
that ``?`` might return a special object that could would override dunder
|
|
|
|
|
methods that return ``self``. For example, ``foo?`` would evaluate to ``foo`` if
|
|
|
|
|
it is not ``None``, otherwise it would evaluate to an instance of
|
|
|
|
|
``NoneQuestion``::
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
class NoneQuestion():
|
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def __getattr__(self, name):
|
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, key):
|
2015-10-20 21:44:00 -04:00
|
|
|
|
return self
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
With this new operator and new type, an expression like ``foo?.bar[baz]``
|
|
|
|
|
evaluates to ``NoneQuestion`` if ``foo`` is None. This is a nifty
|
|
|
|
|
generalization, but it's difficult to use in practice since most existing code
|
|
|
|
|
won't know what ``NoneQuestion`` is.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
Going back to one of the motivating examples above, consider the following::
|
|
|
|
|
|
|
|
|
|
>>> import json
|
|
|
|
|
>>> created = None
|
|
|
|
|
>>> json.dumps({'created': created?.isoformat()})``
|
|
|
|
|
|
|
|
|
|
The JSON serializer does not know how to serialize ``NoneQuestion``, nor will
|
|
|
|
|
any other API. This proposal actually requires *lots of specialized logic*
|
|
|
|
|
throughout the standard library and any third party library.
|
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
At the same time, the ``?`` operator may also be **too general**, in the sense
|
|
|
|
|
that it can be combined with any other operator. What should the following
|
|
|
|
|
expressions mean?
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
>>> x? + 1
|
|
|
|
|
>>> x? -= 1
|
|
|
|
|
>>> x? == 1
|
|
|
|
|
>>> ~x?
|
|
|
|
|
|
|
|
|
|
This degree of generalization is not useful. The operators actually proposed
|
|
|
|
|
herein are intentionally limited to a few operators that are expected to make it
|
|
|
|
|
easier to write common code patterns.
|
|
|
|
|
|
|
|
|
|
Haskell-style ``Maybe``
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
Haskell has a concept called `Maybe <https://wiki.haskell.org/Maybe>`_ that
|
|
|
|
|
encapsulates the idea of an optional value without relying on any special
|
|
|
|
|
keyword (e.g. ``null``) or any special instance (e.g. ``None``). In Haskell, the
|
|
|
|
|
purpose of ``Maybe`` is to avoid separate handling of "something" and nothing".
|
|
|
|
|
The concept is so heavily intertwined with Haskell's lazy evaluation that it
|
|
|
|
|
doesn't translate cleanly into Python.
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
There is a Python package called `pymaybe
|
|
|
|
|
<https://pypi.python.org/pypi/pymaybe/0.1.1>`_ that provides a rough
|
|
|
|
|
approximation. The documentation shows the following example that appears
|
|
|
|
|
relevant to the discussion at hand::
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
>>> maybe('VALUE').lower()
|
|
|
|
|
'value'
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
>>> maybe(None).invalid().method().or_else('unknown')
|
|
|
|
|
'unknown'
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
The function ``maybe()`` returns either a ``Something`` instance or a
|
|
|
|
|
``Nothing`` instance. Similar to the unary postfix operator described in the
|
|
|
|
|
previous section, ``Nothing`` overrides dunder methods in order to allow
|
|
|
|
|
chaining on a missing value.
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
Note that ``or_else()`` is eventually required to retrieve the underlying value
|
|
|
|
|
from ``pymaybe``'s wrappers. Furthermore, ``pymaybe`` does not short circuit any
|
|
|
|
|
evaluation. Although ``pymaybe`` has some strengths and may be useful in its own
|
|
|
|
|
right, it also demonstrates why a pure Python implementation of coalescing is
|
|
|
|
|
not nearly as powerful as support built into the language.
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
Specification
|
|
|
|
|
=============
|
|
|
|
|
|
|
|
|
|
This PEP suggests 4 new operators be added to Python:
|
|
|
|
|
|
|
|
|
|
1. ``None``-coalescing operator
|
|
|
|
|
2. ``None``-severing operator
|
|
|
|
|
3. ``None``-aware attribute access
|
|
|
|
|
4. ``None``-aware index access/slicing
|
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
We will continue to assume the same spellings as in
|
|
|
|
|
the previous sections in order to focus on behavior before diving into the much
|
|
|
|
|
more contentious issue of how to spell these operators.
|
|
|
|
|
|
|
|
|
|
A generalization of these operators is also proposed below under the heading
|
|
|
|
|
"Generalized Coalescing".
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``None``-Coalescing Operator
|
|
|
|
|
----------------------------
|
|
|
|
|
|
|
|
|
|
The ``None``-coalescing operator is a short-circuiting, binary operator that behaves
|
|
|
|
|
in the following way.
|
|
|
|
|
|
|
|
|
|
1. Evaluate the left operand first.
|
|
|
|
|
2. If the left operand is not ``None``, then return it immediately.
|
|
|
|
|
3. Else, evaluate the right operand and return the result.
|
|
|
|
|
|
|
|
|
|
Some simple examples::
|
|
|
|
|
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> 1 🔑 2
|
2015-10-20 11:25:57 -04:00
|
|
|
|
1
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> None 🔑 2
|
2015-10-20 11:25:57 -04:00
|
|
|
|
2
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> 1 🔑 None
|
2015-10-20 11:25:57 -04:00
|
|
|
|
1
|
|
|
|
|
|
|
|
|
|
Importantly, note that the right operand is not evaluated unless the left
|
|
|
|
|
operand is None::
|
|
|
|
|
|
|
|
|
|
>>> def err(): raise Exception('foo')
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> 1 🔑 err()
|
2015-10-20 11:25:57 -04:00
|
|
|
|
1
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> None 🔑 err()
|
2015-10-20 11:25:57 -04:00
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in <module>
|
|
|
|
|
File "<stdin>", line 1, in err
|
|
|
|
|
Exception: foo
|
|
|
|
|
|
|
|
|
|
The operator is left associative. Combined with its short circuiting behavior,
|
|
|
|
|
this makes the operator easy to chain::
|
|
|
|
|
|
|
|
|
|
>>> timeout = None
|
|
|
|
|
>>> local_timeout = 60
|
|
|
|
|
>>> global_timeout = 300
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> timeout 🔑 local_timeout 🔑 global_timeout
|
2015-10-20 11:25:57 -04:00
|
|
|
|
60
|
|
|
|
|
|
|
|
|
|
>>> local_timeout = None
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> timeout 🔑 local_timeout 🔑 global_timeout
|
2015-10-20 11:25:57 -04:00
|
|
|
|
300
|
|
|
|
|
|
|
|
|
|
>>> import time
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> timeout 🔑 local_timeout 🔑 global_timeout 🔑 time.sleep(10)
|
2015-10-20 11:25:57 -04:00
|
|
|
|
300
|
|
|
|
|
|
|
|
|
|
Note in the last example that ``time.sleep(10)`` represents an expensive
|
|
|
|
|
function call, e.g. initializing a complex data structure. In this example
|
|
|
|
|
``time.sleep`` is not evaluated, and the result ``300`` is returned instantly.
|
|
|
|
|
|
2015-10-27 12:59:43 -04:00
|
|
|
|
The operator has higher precedence than the comparison operators ``==``, ``>``,
|
|
|
|
|
``is``, etc., but lower precedence than any bitwise or arithmetic operators.
|
|
|
|
|
This precedence is chosen for making "default value" expressions intuitive to
|
|
|
|
|
read and write::
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
2015-10-27 12:59:43 -04:00
|
|
|
|
>>> user_flag = None
|
|
|
|
|
>>> default_flag = True
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> not user_flag 🔑 default_flag # Same as next expression.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
False
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> not (user_flag 🔑 default_flag) # Same as previous.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
False
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> (not user_flag) 🔑 default_flag # Different from previous.
|
2015-10-27 12:59:43 -04:00
|
|
|
|
True
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
2015-10-27 12:59:43 -04:00
|
|
|
|
>>> user_quantity = None
|
|
|
|
|
>>> default_quantity = 1
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> 1 == user_quantity 🔑 default_quantity # Same as next expression.
|
2015-10-27 12:59:43 -04:00
|
|
|
|
True
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> 1 == (user_quantity 🔑 default_quantity) # Same as previous.
|
2015-10-27 12:59:43 -04:00
|
|
|
|
True
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> (1 == user_quantity) 🔑 default_quantity # Different from previous.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
False
|
|
|
|
|
|
2015-10-27 12:59:43 -04:00
|
|
|
|
>>> user_words = None
|
|
|
|
|
>>> default_words = ['foo', 'bar']
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> 'foo' in user_words 🔑 default_words # Same as next expression.
|
2015-10-27 12:59:43 -04:00
|
|
|
|
True
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> 'foo' in (user_words 🔑 default_words) # Same as previous.
|
2015-10-27 12:59:43 -04:00
|
|
|
|
True
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> ('foo' in user_words) 🔑 default_words # Different from previous.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in <module>
|
2015-10-27 12:59:43 -04:00
|
|
|
|
TypeError: argument of type 'NoneType' is not iterable
|
|
|
|
|
|
|
|
|
|
>>> user_discount = None
|
|
|
|
|
>>> default_discount = 0.9
|
|
|
|
|
>>> price = 100
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> price * user_discount 🔑 default_discount
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
Recall the example above of calculating the cost of items in a shopping cart,
|
|
|
|
|
and the easy-to-miss bug. 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
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> (requested_quantity 🔑 default_quantity) * price
|
2015-10-20 11:25:57 -04:00
|
|
|
|
0
|
|
|
|
|
|
|
|
|
|
The ``None``-coalescing operator also has a corresponding assignment shortcut.
|
|
|
|
|
The following assignments are semantically equivalent::
|
|
|
|
|
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> foo 🔑= []
|
|
|
|
|
>>> foo = foo 🔑 []
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
The ``None`` coalescing operator improves readability, especially when handling
|
|
|
|
|
default function arguments. Consider again the example of requests, rewritten to
|
|
|
|
|
use ``None``-coalescing::
|
|
|
|
|
|
|
|
|
|
def __init__(self, data=None, files=None, headers=None, params=None, hooks=None):
|
2016-09-19 15:42:18 -04:00
|
|
|
|
self.data = data 🔑 []
|
|
|
|
|
self.files = files 🔑 []
|
|
|
|
|
self.headers = headers 🔑 {}
|
|
|
|
|
self.params = params 🔑 {}
|
|
|
|
|
self.hooks = hooks 🔑 {}
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
The operator makes the intent easier to follow (by putting operands in an
|
|
|
|
|
intuitive order) and is more concise than the ternary operator, while still
|
|
|
|
|
preserving the short circuit semantics of the code that it replaces.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``None``-Severing Operator
|
|
|
|
|
--------------------------
|
|
|
|
|
|
|
|
|
|
The idea of a ``None``-aware function invocation syntax was discussed on python-
|
2015-10-27 12:59:43 -04:00
|
|
|
|
ideas. The idea was not popular, so no such operator is included in this
|
|
|
|
|
proposal. (Justification for its exclusion is discussed in a previous section.)
|
2015-10-20 21:44:00 -04:00
|
|
|
|
Still, calling a function when it is not ``None`` is a common idiom in Python,
|
|
|
|
|
particularly for callback functions. Consider this hypothetical example::
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
def delay(seconds, callback=None):
|
|
|
|
|
time.sleep(seconds)
|
|
|
|
|
|
|
|
|
|
if callback is not None:
|
|
|
|
|
callback()
|
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
With the rejected ``None``-aware function call syntax, this example might be
|
|
|
|
|
written more concisely as::
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
def delay(seconds, callback=None):
|
|
|
|
|
time.sleep(seconds)
|
|
|
|
|
callback?()
|
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
Instead, consider a "``None``-severing" operator, however, which is a short-
|
|
|
|
|
circuiting, boolean operator similar to the ``None``-coalesing operator, except
|
2015-10-27 12:59:43 -04:00
|
|
|
|
it returns ``None`` if the left operand is ``None`` and returns the right
|
|
|
|
|
operand otherwise. It has short circuiting behavior that compliments the
|
|
|
|
|
``None``-coalescing operator: if the left operand is None, then the right
|
|
|
|
|
operand is not evaluated. Let's temporarily spell this operator ``✂`` and
|
|
|
|
|
rewrite the example accordingly::
|
2015-09-18 22:20:26 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
import time
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
def delay(seconds, callback=None):
|
|
|
|
|
time.sleep(seconds)
|
|
|
|
|
callback ✂ callback()
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
At this point, you may be astonished at the mere suggestion of such a strange
|
|
|
|
|
operator with limited practical usefulness. It is proposed here because of the
|
|
|
|
|
symmetry it has with the ``None``-coalescing operator. This symmetry may be more
|
|
|
|
|
apparent if the two operators have complementary spellings.
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
In the same way that ``or`` and ``and`` go together, ``None``-coalescing and
|
|
|
|
|
``None``- severing might be spelled in a pleasing, symmetric way, e.g. ``or?``
|
|
|
|
|
and ``and?``. If such a spelling can be decided on, then this operator adds very
|
|
|
|
|
little cognitive load or special machinery to the language, and it's minor
|
|
|
|
|
utility may justify its inclusion in the language.
|
2015-09-19 20:05:19 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
Note that ``None``-severing could also be used as an alternative to "safe
|
|
|
|
|
navigation", at the expense of some repeated expressions::
|
|
|
|
|
|
|
|
|
|
>>> from datetime import datetime
|
|
|
|
|
>>> d = None
|
|
|
|
|
>>> type(d ✂ d.isoformat())
|
|
|
|
|
<class 'NoneType'>
|
|
|
|
|
|
|
|
|
|
>>> d = datetime.now()
|
|
|
|
|
>>> d ✂ d.isoformat()
|
|
|
|
|
'2015-10-16T20:53:40.312135'
|
|
|
|
|
|
|
|
|
|
The repeated expression ``d`` makes this less useful than a ``None``-aware
|
|
|
|
|
attribute access operator, but to repeat what was said at the outset: this
|
|
|
|
|
proposal may be approved or rejected in whole or in part. This unlikely operator
|
|
|
|
|
is included in the proposal in order to be comprehensive.
|
|
|
|
|
|
|
|
|
|
The precedence and associativity of the ``None``-severing operator are the same
|
|
|
|
|
as the ``None``-coalescing operator.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``None``-Aware Attribute Access Operator
|
|
|
|
|
----------------------------------------
|
|
|
|
|
|
|
|
|
|
The ``None``-aware attribute access operator (also called "safe navigation")
|
|
|
|
|
checks its left operand. If the left operand is ``None``, then the operator
|
2016-05-03 05:03:16 -04:00
|
|
|
|
evaluates to ``None``. If the left operand is not ``None``, then the
|
2015-10-20 11:25:57 -04:00
|
|
|
|
operator accesses the attribute named by the right operand. As in the previous
|
|
|
|
|
section, we continue to use the temporary spelling ``💩``::
|
|
|
|
|
|
|
|
|
|
>>> from datetime import date
|
|
|
|
|
>>> d = date.today()
|
|
|
|
|
>>> d.year
|
|
|
|
|
2015
|
|
|
|
|
|
|
|
|
|
>>> d = None
|
|
|
|
|
>>> d.year
|
2015-09-18 22:10:53 -04:00
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in <module>
|
2015-10-20 11:25:57 -04:00
|
|
|
|
AttributeError: 'NoneType' object has no attribute 'year'
|
|
|
|
|
|
|
|
|
|
>>> d💩year
|
|
|
|
|
None
|
|
|
|
|
|
|
|
|
|
The operator has the same precedence and associativity as the plain attribute
|
|
|
|
|
access operator ``.``, but this operator is also short-circuiting in a unique
|
2015-10-20 21:44:00 -04:00
|
|
|
|
way: if the left operand is ``None``, then any series of attribute access, index
|
|
|
|
|
access, slicing, or function call operators immediately to the right of it *are
|
|
|
|
|
not evaluated*.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
>>> name = ' The Black Knight '
|
|
|
|
|
>>> name.strip()[4:].upper()
|
|
|
|
|
'BLACK KNIGHT'
|
|
|
|
|
|
|
|
|
|
>>> name = None
|
|
|
|
|
>>> name💩strip()[4:].upper()
|
2015-09-18 22:10:53 -04:00
|
|
|
|
None
|
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
If this operator did not short circuit in this way, then the second example
|
|
|
|
|
would partially evaluate ``name💩strip()`` to ``None()`` and then fail with
|
|
|
|
|
``TypeError: 'NoneType' object is not callable``.
|
|
|
|
|
|
|
|
|
|
To put it another way, the following expressions are semantically equivalent::
|
|
|
|
|
|
|
|
|
|
>>> name💩strip()[4:].upper()
|
|
|
|
|
>>> name.strip()[4:].upper() if name is not None else None
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
.. note::
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
C# implements its safe navigation operators with the same short-circuiting
|
|
|
|
|
semantics, but Dart does not. In Dart, the second example (suitably
|
|
|
|
|
translated) would fail. The C# semantics are obviously superior, given the
|
|
|
|
|
original goal of writing common cases more concisely. The Dart semantics are
|
|
|
|
|
nearly useless.
|
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
This operator short circuits one or more attribute access, index access,
|
|
|
|
|
slicing, or function call operators that are immediately to its right, but it
|
|
|
|
|
does not short circuit any other operators (logical, bitwise, arithmetic, etc.),
|
|
|
|
|
nor does it escape parentheses::
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
>>> d = date.today()
|
|
|
|
|
>>> d💩year.numerator + 1
|
|
|
|
|
2016
|
|
|
|
|
|
|
|
|
|
>>> d = None
|
|
|
|
|
>>> d💩year.numerator + 1
|
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in <module>
|
|
|
|
|
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
|
|
|
|
|
|
|
|
|
|
>>> (d💩year).numerator + 1
|
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in <module>
|
|
|
|
|
AttributeError: 'NoneType' object has no attribute 'numerator'
|
|
|
|
|
|
|
|
|
|
Note that the error in the second example is not on the attribute access
|
|
|
|
|
``numerator``. In fact, that attribute access is never performed. The error
|
|
|
|
|
occurs when adding ``None + 1``, because the ``None``-aware attribute access
|
|
|
|
|
does not short circuit ``+``.
|
|
|
|
|
|
|
|
|
|
The third example fails because the operator does not escape parentheses. In
|
|
|
|
|
that example, the attribute access ``numerator`` is evaluated and fails because
|
|
|
|
|
``None`` does not have that attribute.
|
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
Finally, observe that short circuiting adjacent operators is not at all the same thing as propagating ``None`` throughout an expression::
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
>>> user💩first_name.upper()
|
|
|
|
|
|
|
|
|
|
If ``user`` is not ``None``, then ``user.first_name`` is evaluated. If
|
|
|
|
|
``user.first_name`` evaluates to ``None``, then ``user.first_name.upper()`` is
|
2015-10-20 21:44:00 -04:00
|
|
|
|
an error! In English, this expression says, "``user`` is optional but if it has
|
|
|
|
|
a value, then it must have a ``first_name``, too.""
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
If ``first_name`` is supposed to be optional attribute, then the expression must
|
2015-10-20 21:44:00 -04:00
|
|
|
|
make that explicit::
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
>>> user💩first_name💩upper()
|
|
|
|
|
|
|
|
|
|
The operator is not intended as an error silencing mechanism, and it would be
|
2015-10-20 21:44:00 -04:00
|
|
|
|
undesirable if its presence infected nearby operators.
|
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
``None``-Aware Index Access/Slicing Operator
|
|
|
|
|
--------------------------------------------
|
|
|
|
|
|
|
|
|
|
The ``None``-aware index access/slicing operator (also called "safe navigation")
|
|
|
|
|
is nearly identical to the ``None``-aware attribute access operator. It combines
|
|
|
|
|
the familiar square bracket syntax ``[]`` with new punctuation or a new keyword,
|
|
|
|
|
the spelling of which is discussed later::
|
2015-09-19 20:05:19 -04:00
|
|
|
|
|
2015-09-18 22:10:53 -04:00
|
|
|
|
>>> person = {'name': 'Mark', 'age': 32}
|
|
|
|
|
>>> person['name']
|
|
|
|
|
'Mark'
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
2015-09-18 22:10:53 -04:00
|
|
|
|
>>> person = None
|
|
|
|
|
>>> person['name']
|
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in <module>
|
|
|
|
|
TypeError: 'NoneType' object is not subscriptable
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
>>> person💩['name']
|
2015-09-18 22:10:53 -04:00
|
|
|
|
None
|
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
The ``None``-aware slicing operator behaves similarly::
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
>>> name = 'The Black Knight'
|
|
|
|
|
>>> name[4:]
|
|
|
|
|
'Black Knight'
|
|
|
|
|
|
|
|
|
|
>>> name = None
|
|
|
|
|
>>> name[4:]
|
|
|
|
|
Traceback (most recent call last):
|
|
|
|
|
File "<stdin>", line 1, in <module>
|
|
|
|
|
TypeError: 'NoneType' object is not subscriptable
|
|
|
|
|
|
|
|
|
|
>>> name💩[4:]
|
|
|
|
|
None
|
|
|
|
|
|
|
|
|
|
These operators have the same precedence as the plain index access and slicing
|
2015-10-20 21:44:00 -04:00
|
|
|
|
operators. They also have the same short-circuiting behavior as the
|
|
|
|
|
``None``-aware attribute access.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
Generalized Coalescing
|
|
|
|
|
----------------------
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
Making ``None`` a special case may seem too specialized and magical. It is
|
|
|
|
|
possible to generalize the behavior by making the ``None``-aware operators
|
|
|
|
|
invoke a dunder method, e.g. ``__coalesce__(self)`` that returns ``True`` if an
|
|
|
|
|
object should be coalesced and ``False`` otherwise.
|
|
|
|
|
|
|
|
|
|
With this generalization, ``object`` would implement a dunder method equivalent
|
|
|
|
|
to this::
|
|
|
|
|
|
|
|
|
|
def __coalesce__(self):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
``NoneType`` would implement a dunder method equivalent to this::
|
|
|
|
|
|
|
|
|
|
def __coalesce__(self):
|
|
|
|
|
return True
|
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
If this generalization is accepted, then the operators will need to be renamed
|
|
|
|
|
such that the term ``None`` is not used, e.g. "Coalescing Operator", "Coalesced
|
|
|
|
|
Member Access Operator", etc.
|
|
|
|
|
|
|
|
|
|
The coalescing operator would invoke this dunder method. The following two expressions are semantically equivalent::
|
|
|
|
|
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> foo 🔑 bar
|
2015-10-20 21:44:00 -04:00
|
|
|
|
>>> bar if foo.__coalesce__() else foo
|
|
|
|
|
|
|
|
|
|
The coalesced attribute and index access operators would invoke the same dunder
|
|
|
|
|
method::
|
|
|
|
|
|
|
|
|
|
>>> user💩first_name.upper()
|
|
|
|
|
>>> None if user.__coalesce__() else user.first_name.upper()
|
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
This generalization allows for domain-specific ``null`` objects to be coalesced
|
2016-07-11 11:14:08 -04:00
|
|
|
|
just like ``None``. For example, the ``pyasn1`` package has a type called
|
2015-10-20 11:25:57 -04:00
|
|
|
|
``Null`` that represents an ASN.1 ``null``.
|
|
|
|
|
|
2015-10-20 21:44:00 -04:00
|
|
|
|
>>> from pyasn1.type import univ
|
2016-09-19 15:42:18 -04:00
|
|
|
|
>>> univ.Null() 🔑 univ.Integer(123)
|
2015-10-20 21:44:00 -04:00
|
|
|
|
Integer(123)
|
|
|
|
|
|
|
|
|
|
In addition to making the proposed operators less specialized, this
|
|
|
|
|
generalization also makes it easier to work with the Null Object Pattern, [3]_
|
|
|
|
|
for those developers who prefer to avoid using ``None``.
|
2015-10-20 11:25:57 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Operator Spelling
|
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
|
|
Despite significant support for the proposed operators, the majority of
|
|
|
|
|
discussion on python-ideas fixated on the spelling. No consensus was achieved on
|
|
|
|
|
this question, for two reasons. First, Python eschews punctuation for logical
|
|
|
|
|
operators. For example, it uses ``not`` instead of ``!`` and ``… if … else …``
|
|
|
|
|
instead of ``?:``. Introducing new punctuation is a major turnoff to many
|
|
|
|
|
Pythonistas, including BDFL. Second, adding new keywords to the language is
|
|
|
|
|
not backwards compatible. Any new keyword could only be introduced in the next
|
|
|
|
|
major version, e.g. Python 4. (Even then, `there would be resistance
|
|
|
|
|
<http://opensource.com/life/14/9/why-python-4-wont-be-python-3>`_.)
|
|
|
|
|
|
|
|
|
|
Furthermore, nearly every single punctuation character on a standard keyboard
|
2015-10-20 21:44:00 -04:00
|
|
|
|
already has special meaning in Python. The only exceptions are ``$``, ``!``,
|
2015-10-20 11:25:57 -04:00
|
|
|
|
``?``, and backtick (as of Python 3). This leaves few options for a new, single-
|
|
|
|
|
character operator. A two character spelling is more likely, such as the ``??``
|
|
|
|
|
and ``?.`` spellings in other programming languages, but this decreases the
|
|
|
|
|
appeal of punctuation even further.
|
|
|
|
|
|
|
|
|
|
Finally, other projects in the Python universe assign special meaning to
|
|
|
|
|
punctuation. For example, `IPython <https://ipython.org/ipython-
|
|
|
|
|
doc/2/interactive/reference.html>`_ assigns special meaning to ``%``, ``%%``,
|
|
|
|
|
``?``, ``??``, ``$``, and ``$$``, among others. Out of deference to those
|
|
|
|
|
projects and the large communities using them, introducing conflicting syntax
|
|
|
|
|
into Python is undesirable.
|
|
|
|
|
|
|
|
|
|
This is not the first PEP to deal with this dilemma. PEP-308 [5]_, which
|
|
|
|
|
introduced the ternary operator, faced similar issues.
|
|
|
|
|
|
|
|
|
|
Alternative Spellings
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
In keeping with the spirit of the PEP, many alternative spellings for these
|
|
|
|
|
``None``-aware operators are suggested, including some that conflict with each
|
|
|
|
|
other. Deconfliction will be handled only if any part of this proposal is
|
|
|
|
|
accepted.
|
|
|
|
|
|
|
|
|
|
One caveat noted by several respondents on python-ideas: using similar spelling
|
|
|
|
|
for ``None`` coalescing and other ``None``-aware operators may be confusing,
|
|
|
|
|
because they have different short circuit semantics: coalescing short circuits
|
|
|
|
|
on non-``None``, while ``None``-aware attribute/index access short circuit on
|
|
|
|
|
``None``. This is a potential downside to spellings like ``??`` and ``?.``. This
|
|
|
|
|
is only a practical concern if any part of this proposal is actually accepted,
|
|
|
|
|
so there is no need to pontificate any further.
|
|
|
|
|
|
|
|
|
|
The following spellings are proposed candidates for the ``None``-coalescing
|
|
|
|
|
operator.
|
|
|
|
|
|
|
|
|
|
1. ``foo ?? bar ?? baz``
|
|
|
|
|
- Pros: same spelling as C# and Dart
|
|
|
|
|
- Cons: punctuation is ugly; possible conflict with IPython; difficult to
|
|
|
|
|
google to find out what it means
|
|
|
|
|
2. ``foo or? bar or? baz``
|
|
|
|
|
- Pros: similar to existing ``or`` operator
|
|
|
|
|
- Cons: the difference between this and ``or`` is not intuitive; punctuation
|
2015-10-27 12:59:43 -04:00
|
|
|
|
is ugly; different precedence from ``or`` may be confusing
|
2015-10-20 21:44:00 -04:00
|
|
|
|
3. ``foo ? bar ? baz``
|
2015-10-20 11:25:57 -04:00
|
|
|
|
- Pros: similar to ``??`` used in other languages
|
|
|
|
|
- Cons: punctuation is ugly; possible conflict with IPython; not used by any
|
|
|
|
|
other language
|
2015-10-20 21:44:00 -04:00
|
|
|
|
4. ``foo $$ bar $$ baz``
|
2015-10-20 11:25:57 -04:00
|
|
|
|
- Pros: pronounced "value operator" because it returns the first operand
|
|
|
|
|
that has a "value"
|
|
|
|
|
- Cons: punctuation is ugly; not used by any other language
|
2015-10-20 21:44:00 -04:00
|
|
|
|
5. ``foo else bar else baz``
|
2015-10-20 11:25:57 -04:00
|
|
|
|
- Pros: prettier than punctuation; uses an existing keyword
|
|
|
|
|
- Cons: difficult or impossible to implement with Python's LL(1) parser
|
2015-10-20 21:44:00 -04:00
|
|
|
|
6. ``foo or else bar or else baz``
|
2015-10-20 11:25:57 -04:00
|
|
|
|
- Pros: prettier than punctuation; use existing keywords
|
|
|
|
|
- Cons: difficult or impossible to implement with Python's LL(1) parser
|
2015-10-20 21:44:00 -04:00
|
|
|
|
7. ``foo def bar def baz``
|
2015-10-20 11:25:57 -04:00
|
|
|
|
- Pros: pronounced 'default'; prettier than punctuation
|
|
|
|
|
- Cons: difficult or impossible to implement with Python's LL(1) parser
|
2015-10-20 21:44:00 -04:00
|
|
|
|
8. ``foo then bar then baz``
|
2015-10-20 11:25:57 -04:00
|
|
|
|
- Pros: prettier than punctuation
|
|
|
|
|
- Cons: requires a new keyword, probably can't be implemented until Python 4
|
|
|
|
|
(and maybe not even then)
|
|
|
|
|
9. No ``None``-coalescing operator.
|
|
|
|
|
- (Pros and cons discussed throughout this document.)
|
|
|
|
|
|
|
|
|
|
The following spellings are proposed candidates for the ``None``-severing
|
|
|
|
|
operator. Each alternative has symmetry with one of the proposed spellings of
|
|
|
|
|
the ``None``- coalescing operator.
|
|
|
|
|
|
|
|
|
|
1. ``foo !! bar``
|
|
|
|
|
- Pros: symmetric with ``??``
|
|
|
|
|
- Cons: punctuation is ugly; possible conflict with IPython; difficult to
|
|
|
|
|
google to find out what it means
|
|
|
|
|
2. ``foo and? bar``
|
|
|
|
|
- Pros: symmetric with ``or?``
|
|
|
|
|
- Cons: punctuation is ugly; possible conflict with IPython; difficult to
|
2015-10-27 12:59:43 -04:00
|
|
|
|
google to find out what it means; different precedence from ``and`` may be
|
|
|
|
|
confusing
|
2015-10-20 11:25:57 -04:00
|
|
|
|
3. No ``None``-severing operator.
|
|
|
|
|
- (Pros and cons discussed throughout this document.)
|
|
|
|
|
|
|
|
|
|
The following spellings are proposed candidates for the ``None``-aware attribute
|
|
|
|
|
access operator. If you find any of these hard to read, consider that we may
|
|
|
|
|
adopt a convention of adding whitespace around a ``None``-aware operator to
|
|
|
|
|
improve readability.
|
|
|
|
|
|
|
|
|
|
1. ``foo?.bar``, ``foo ?. bar``
|
|
|
|
|
- Pros: same spelling as C# and Dart
|
|
|
|
|
- Cons: punctuation is ugly; possible conflict with IPython; difficult to
|
|
|
|
|
google to find out what it means; difficult to differentiate from ``.``
|
|
|
|
|
when reading quickly
|
|
|
|
|
2. ``foo$.bar``, ``foo $. bar``
|
|
|
|
|
- Pros: symmetry with ``$$`` operator proposed above
|
|
|
|
|
- Cons: punctuation is ugly; difficult to google; possible confusion because
|
|
|
|
|
it looks a bit like other languages' string interpolation; difficult to
|
|
|
|
|
google to find out what it means; difficult to differentiate from ``.``
|
|
|
|
|
when reading quickly
|
|
|
|
|
3. ``foo!bar``, ``foo ! bar``
|
|
|
|
|
- Pros: similar to ordinary ``.`` operator
|
|
|
|
|
- Cons: punctuation is ugly; possible conflict with IPython; no corresponding
|
|
|
|
|
spelling for index access (e.g. ``foo!['bar']`` is ambiguous)
|
|
|
|
|
4. ``foo->bar``, ``foo -> bar``
|
|
|
|
|
- Pros: easier to read than other punctuation; less likely to be confused
|
|
|
|
|
with ordinary attribute access
|
|
|
|
|
- Cons: punctuation is ugly; difficult to google; confusing because it is
|
|
|
|
|
spelled the same as C's dereference operator
|
|
|
|
|
5. ``foo try .bar``
|
|
|
|
|
- Pros: uses an existing keyword;
|
|
|
|
|
- Cons: difficult or impossible to implement in Python's LL(1) parser
|
|
|
|
|
6. No ``None``-aware attribute access operator.
|
|
|
|
|
- (Pros and cons discussed throughout this document.)
|
|
|
|
|
|
|
|
|
|
The following spellings are proposed candidates for the ``None``-aware index
|
|
|
|
|
access/slicing operator. The punctuation used for this operator ought to
|
|
|
|
|
resemble the punctuation used for the ``None``-aware attribute access.
|
|
|
|
|
|
|
|
|
|
1. ``foo?['bar']``, ``foo ? ['bar']``
|
|
|
|
|
- Pros: same spelling as C# and Dart
|
|
|
|
|
- Cons: punctuation is ugly; possible conflict with IPython; difficult to
|
|
|
|
|
google to find out what it means
|
|
|
|
|
2. ``foo$['bar']``, ``foo $ ['bar']``
|
|
|
|
|
- Pros: symmetry with ``$$`` operator proposed above
|
|
|
|
|
- Cons: punctuation is ugly; possible confusion because
|
|
|
|
|
it looks a bit like other languages' string interpolation
|
|
|
|
|
3. ``foo->['bar']``, ``foo -> ['bar']``
|
|
|
|
|
- Pros: easier to read than other punctuation; less likely to be confused
|
|
|
|
|
with ordinary attribute access
|
|
|
|
|
- Cons: punctuation is ugly; difficult to google; confusing because it is
|
|
|
|
|
spelled the same as C's dereference operator
|
|
|
|
|
4. ``foo try ['bar']``
|
|
|
|
|
- Pros: uses an existing keyword;
|
|
|
|
|
- Cons: difficult or impossible to implement in Python's LL(1) parser
|
|
|
|
|
5. No ``None``-aware index access/slicing operator.
|
|
|
|
|
- (Pros and cons discussed throughout this document.)
|
|
|
|
|
|
|
|
|
|
Community Poll
|
|
|
|
|
~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
In order to collect data about the Python community's preferences for
|
|
|
|
|
``None``-aware operators, and with BDFL's consent, a public poll will be
|
|
|
|
|
conducted, just as with PEP-308. The poll is viewed as a data-gathering
|
|
|
|
|
exercise, not a democratic vote.
|
|
|
|
|
|
|
|
|
|
The poll will allow respondents to rank their favorite options from the previous
|
|
|
|
|
section. The results will
|
|
|
|
|
be placed in this section of the PEP.
|
|
|
|
|
|
|
|
|
|
...TBD...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Implementation
|
|
|
|
|
--------------
|
|
|
|
|
|
|
|
|
|
Given that the need for ``None``-aware operators is questionable and the
|
|
|
|
|
spelling of said operators is almost incendiary, the implementation details for
|
|
|
|
|
CPython will be deferred unless and until we have a clearer idea that one (or
|
|
|
|
|
more) of the proposed operators will be approved.
|
|
|
|
|
|
|
|
|
|
...TBD...
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
References
|
|
|
|
|
==========
|
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
.. [1] C# Reference: Operators
|
|
|
|
|
(https://msdn.microsoft.com/en-us/library/6a71f45d.aspx)
|
|
|
|
|
|
|
|
|
|
.. [2] A Tour of the Dart Language: Operators
|
|
|
|
|
(https://www.dartlang.org/docs/dart-up-and-running/ch02.html#operators)
|
|
|
|
|
|
|
|
|
|
.. [3] Wikipedia: Null Object Pattern
|
|
|
|
|
(https://en.wikipedia.org/wiki/Null_Object_pattern)
|
|
|
|
|
|
|
|
|
|
.. [4] PEP-249:
|
|
|
|
|
(https://www.python.org/dev/peps/pep-0249/)
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
2015-10-20 11:25:57 -04:00
|
|
|
|
.. [5] PEP-308
|
|
|
|
|
(https://www.python.org/dev/peps/pep-0308/)
|
2015-09-18 22:10:53 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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:
|