2016-10-25 03:15:18 -04:00
|
|
|
|
PEP: 531
|
|
|
|
|
Title: Existence checking operators
|
|
|
|
|
Version: $Revision$
|
|
|
|
|
Last-Modified: $Date$
|
|
|
|
|
Author: Nick Coghlan <ncoghlan@gmail.com>
|
|
|
|
|
Status: Draft
|
|
|
|
|
Type: Standards Track
|
|
|
|
|
Content-Type: text/x-rst
|
|
|
|
|
Created: 25-Oct-2016
|
|
|
|
|
Python-Version: 3.7
|
2016-10-28 02:53:30 -04:00
|
|
|
|
Post-History: 28-Oct-2016
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
|
========
|
|
|
|
|
|
|
|
|
|
Inspired by PEP 505 and the related discussions, this PEP proposes the addition
|
2016-10-28 02:53:30 -04:00
|
|
|
|
of two new control flow operators to Python:
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
* Existence-checking precondition ("exists-then"): ``expr1 ?then expr2``
|
|
|
|
|
* Existence-checking fallback ("exists-else"): ``expr1 ?else expr2``
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
as well as the following abbreviations for common existence checking
|
|
|
|
|
expressions and statements:
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
* Existence-checking attribute access:
|
|
|
|
|
``obj?.attr`` (for ``obj ?then obj.attr``)
|
|
|
|
|
* Existence-checking subscripting:
|
|
|
|
|
``obj?[expr]`` (for ``obj ?then obj[expr]``)
|
|
|
|
|
* Existence-checking assignment:
|
|
|
|
|
``value ?= expr`` (for ``value = value ?else expr``)
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
The common ``?`` symbol in these new operator definitions indicates that they
|
|
|
|
|
use a new "existence checking" protocol rather than the established
|
|
|
|
|
truth-checking protocol used by if statements, while loops, comprehensions,
|
|
|
|
|
generator expressions, conditional expressions, logical conjunction, and
|
|
|
|
|
logical disjunction.
|
|
|
|
|
|
|
|
|
|
This new protocol would be made available as ``operator.exists``, with the
|
|
|
|
|
following characteristics:
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
2016-10-25 04:47:42 -04:00
|
|
|
|
* types can define a new ``__exists__`` magic method (Python) or
|
|
|
|
|
``tp_exists`` slot (C) to override the default behaviour. This optional
|
|
|
|
|
method has the same signature and possible return values as ``__bool__``.
|
|
|
|
|
* ``operator.exists(None)`` returns ``False``
|
|
|
|
|
* ``operator.exists(NotImplemented)`` returns ``False``
|
|
|
|
|
* ``operator.exists(Ellipsis)`` returns ``False``
|
2016-10-28 02:53:30 -04:00
|
|
|
|
* ``float``, ``complex`` and ``decimal.Decimal`` will override the existence
|
|
|
|
|
check such that ``NaN`` values return ``False`` and other values (including
|
|
|
|
|
zero values) return ``True``
|
2016-10-25 04:47:42 -04:00
|
|
|
|
* for any other type, ``operator.exists(obj)`` returns True by default. Most
|
2016-10-28 02:53:30 -04:00
|
|
|
|
importantly, values that evaluate to False in a truth checking context
|
|
|
|
|
(zeroes, empty containers) will still evaluate to True in an existence
|
|
|
|
|
checking context
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Relationship with other PEPs
|
|
|
|
|
============================
|
|
|
|
|
|
|
|
|
|
While this PEP was inspired by and builds on Mark Haase's excellent work in
|
|
|
|
|
putting together PEP 505, it ultimately competes with that PEP due to
|
2016-10-28 02:53:30 -04:00
|
|
|
|
significant differences in the specifics of the proposed syntax and semantics
|
|
|
|
|
for the feature.
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
It also presents a different perspective on the rationale for the change by
|
|
|
|
|
focusing on the benefits to existing Python users as the typical demands of
|
|
|
|
|
application and service development activities are genuinely changing. It
|
|
|
|
|
isn't an accident that similar features are now appearing in multiple
|
2016-10-28 02:53:30 -04:00
|
|
|
|
programming languages, and while it's a good idea for us to learn from how other
|
|
|
|
|
language designers are handling the problem, precedents being set elsewhere
|
2016-10-25 03:15:18 -04:00
|
|
|
|
are more relevant to *how* we would go about tackling this problem than they
|
|
|
|
|
are to whether or not we think it's a problem we should address in the first
|
|
|
|
|
place.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rationale
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
Existence checking expressions
|
|
|
|
|
------------------------------
|
|
|
|
|
|
|
|
|
|
An increasingly common requirement in modern software development is the need
|
|
|
|
|
to work with "semi-structured data": data where the structure of the data is
|
|
|
|
|
known in advance, but pieces of it may be missing at runtime, and the software
|
|
|
|
|
manipulating that data is expected to degrade gracefully (e.g. by omitting
|
|
|
|
|
results that depend on the missing data) rather than failing outright.
|
|
|
|
|
|
|
|
|
|
Some particularly common cases where this issue arises are:
|
|
|
|
|
|
2016-10-25 04:47:42 -04:00
|
|
|
|
* handling optional application configuration settings and function parameters
|
|
|
|
|
* handling external service failures in distributed systems
|
|
|
|
|
* handling data sets that include some partial records
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
It is the latter two cases that are the primary motivation for this PEP - while
|
|
|
|
|
needing to deal with optional configuration settings and parameters is a design
|
|
|
|
|
requirement at least as old as Python itself, the rise of public cloud
|
|
|
|
|
infrastructure, the development of software systems as collaborative networks
|
|
|
|
|
of distributed services, and the availability of large public and private data
|
|
|
|
|
sets for analysis means that the ability to degrade operations gracefully in
|
|
|
|
|
the face of partial service failures or partial data availability is becoming
|
|
|
|
|
an essential feature of modern programming environments.
|
|
|
|
|
|
|
|
|
|
At the moment, writing such software in Python can be genuinely awkward, as
|
|
|
|
|
your code ends up littered with expressions like:
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
* ``value1 = expr1.field.of.interest if expr1 is not None else None``
|
|
|
|
|
* ``value2 = expr2["field"]["of"]["interest"] if expr2 is not None else None``
|
|
|
|
|
* ``value3 = expr3 if expr3 is not None else expr4 if expr4 is not None else expr5``
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
If these are only occasional, then expanding out to full statement forms may
|
|
|
|
|
help improve readability, but if you have 4 or 5 of them in a row (which is a
|
|
|
|
|
fairly common situation in data transformation pipelines), then replacing them
|
|
|
|
|
with 16 or 20 lines of conditional logic really doesn't help matters.
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
Expanding the three examples above that way hopefully helps illustrate that::
|
|
|
|
|
|
2016-10-28 04:35:53 -04:00
|
|
|
|
if expr1 is not None:
|
|
|
|
|
value1 = expr1.field.of.interest
|
2016-10-28 02:53:30 -04:00
|
|
|
|
else:
|
|
|
|
|
value1 = None
|
2016-10-28 04:35:53 -04:00
|
|
|
|
if expr2 is not None:
|
|
|
|
|
value2 = expr2["field"]["of"]["interest"]
|
2016-10-28 02:53:30 -04:00
|
|
|
|
else:
|
|
|
|
|
value2 = None
|
2016-10-28 04:35:53 -04:00
|
|
|
|
if expr3 is not None:
|
|
|
|
|
value3 = expr3
|
2016-10-28 02:53:30 -04:00
|
|
|
|
else:
|
2016-10-28 04:35:53 -04:00
|
|
|
|
if expr4 is not None:
|
|
|
|
|
value3 = expr4
|
2016-10-28 02:53:30 -04:00
|
|
|
|
else:
|
|
|
|
|
value3 = expr5
|
|
|
|
|
|
2016-10-25 03:15:18 -04:00
|
|
|
|
The combined impact of the proposals in this PEP is to allow the above sample
|
|
|
|
|
expressions to instead be written as:
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
* ``value1 = expr1?.field.of.interest``
|
|
|
|
|
* ``value2 = expr2?["field"]["of"]["interest"]``
|
|
|
|
|
* ``value3 = expr3 ?else expr4 ?else expr5``
|
|
|
|
|
|
|
|
|
|
In these forms, almost all of the information presented to the reader is
|
|
|
|
|
immediately relevant to the question "What does this code do?", while the
|
|
|
|
|
boilerplate code to handle missing data by passing it through to the output
|
|
|
|
|
or falling back to an alternative input, has shrunk to two uses of the ``?``
|
|
|
|
|
symbol and two uses of the ``?else`` keyword.
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
In the first two examples, the 31 character boilerplate clause
|
|
|
|
|
`` if exprN is not None else None`` (minimally 27 characters for a single letter
|
2016-10-25 03:15:18 -04:00
|
|
|
|
variable name) has been replaced by a single ``?`` character, substantially
|
|
|
|
|
improving the signal-to-pattern-noise ratio of the lines (especially if it
|
|
|
|
|
encourages the use of more meaningful variable and field names rather than
|
|
|
|
|
making them shorter purely for the sake of expression brevity).
|
|
|
|
|
|
|
|
|
|
In the last example, two instances of the 21 character boilerplate,
|
2016-10-28 02:53:30 -04:00
|
|
|
|
`` if exprN is not None`` (minimally 17 characters) are replaced with single
|
2016-10-25 03:15:18 -04:00
|
|
|
|
characters, again substantially improving the signal-to-pattern-noise ratio.
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
Furthermore, each of our 5 "subexpressions of potential interest" is included
|
|
|
|
|
exactly once, rather than 4 of them needing to be duplicated or pulled out
|
|
|
|
|
to a named variable in order to first check if they exist.
|
|
|
|
|
|
2016-10-25 03:15:18 -04:00
|
|
|
|
The existence checking precondition operator is mainly defined to provide a
|
2016-10-28 02:53:30 -04:00
|
|
|
|
clear conceptual basis for the existence checking attribute access and
|
2016-10-25 03:15:18 -04:00
|
|
|
|
subscripting operators:
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
* ``obj?.attr`` is roughly equivalent to ``obj ?then obj.attr``
|
|
|
|
|
* ``obj?[expr]``is roughly equivalent to ``obj ?then obj[expr]``
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
The main semantic difference between the shorthand forms and their expanded
|
|
|
|
|
equivalents is that the common subexpression to the left of the existence
|
|
|
|
|
checking operator is evaluated only once in the shorthand form (similar to
|
|
|
|
|
the benefit offered by augmented assignment statements).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Existence checking assignment
|
|
|
|
|
-----------------------------
|
|
|
|
|
|
|
|
|
|
Existence-checking assignment is proposed as a relatively straightforward
|
|
|
|
|
expansion of the concepts in this PEP to also cover the common configuration
|
|
|
|
|
handling idiom:
|
|
|
|
|
|
|
|
|
|
* ``value = value if value is not None else expensive_default()``
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
by allowing that to instead be abbreviated as:
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
* ``value ?= expensive_default()``
|
|
|
|
|
|
|
|
|
|
This is mainly beneficial when the target is a subscript operation or
|
2016-10-28 02:53:30 -04:00
|
|
|
|
subattribute, as even without this specific change, the PEP would still
|
2016-10-25 03:15:18 -04:00
|
|
|
|
permit this idiom to be updated to:
|
|
|
|
|
|
|
|
|
|
* ``value = value ?else expensive_default()``
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
The main argument *against* adding this form is that it's arguably ambiguous
|
|
|
|
|
and could mean either:
|
|
|
|
|
|
|
|
|
|
* ``value = value ?else expensive_default()``; or
|
|
|
|
|
* ``value = value ?then value.subfield.of.interest``
|
|
|
|
|
|
|
|
|
|
The second form isn't at all useful, but if this concern was deemed significant
|
|
|
|
|
enough to address while still keeping the augmented assignment feature,
|
|
|
|
|
the full keyword could be included in the syntax:
|
|
|
|
|
|
|
|
|
|
* ``value ?else= expensive_default()``
|
|
|
|
|
|
|
|
|
|
Alternatively, augmented assignment could just be dropped from the current
|
|
|
|
|
proposal entirely and potentially reconsidered at a later date.
|
|
|
|
|
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
Existence checking protocol
|
|
|
|
|
---------------------------
|
|
|
|
|
|
|
|
|
|
The existence checking protocol is including in this proposal primarily to
|
|
|
|
|
allow for proxy objects (e.g. local representations of remote resources) and
|
|
|
|
|
mock objects used in testing to correctly indicate non-existence of target
|
|
|
|
|
resources, even though the proxy or mock object itself is not None.
|
|
|
|
|
|
|
|
|
|
However, with that protocol defined, it then seems natural to expand it to
|
|
|
|
|
provide a type independent way of checking for ``NaN`` values in numeric types
|
|
|
|
|
- at the moment you need to be aware of the exact data type you're working with
|
|
|
|
|
(e.g. builtin floats, builtin complex numbers, the decimal module) and use the
|
|
|
|
|
appropriate operation (e.g. ``math.isnan``, ``cmath.isnan``,
|
|
|
|
|
``decimal.getcontext().is_nan()``, respectively)
|
|
|
|
|
|
|
|
|
|
Similarly, it seems reasonable to declare that the other placeholder builtin
|
|
|
|
|
singletons, ``Ellipsis`` and ``NotImplemented``, also qualify as objects that
|
|
|
|
|
represent the absence of data moreso than they represent data.
|
|
|
|
|
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
Proposed symbolic notation
|
|
|
|
|
--------------------------
|
|
|
|
|
|
|
|
|
|
Python has historically only had one kind of implied boolean context: truth
|
|
|
|
|
checking, which can be invoked directly via the ``bool()`` builtin. As this PEP
|
|
|
|
|
proposes a new kind of control flow operation based on existence checking rather
|
|
|
|
|
than truth checking, it is considered valuable to have a reminder directly
|
|
|
|
|
in the code when existence checking is being used rather than truth checking.
|
|
|
|
|
|
|
|
|
|
The mathematical symbol for existence assertions is U+2203 'THERE EXISTS': ``∃``
|
|
|
|
|
|
|
|
|
|
Accordingly, one possible approach to the syntactic additions proposed in this
|
|
|
|
|
PEP would be to use that already defined mathematical notation:
|
|
|
|
|
|
|
|
|
|
* ``expr1 ∃then expr2``
|
|
|
|
|
* ``expr1 ∃else expr2``
|
|
|
|
|
* ``obj∃.attr``
|
|
|
|
|
* ``obj∃[expr]``
|
|
|
|
|
* ``target ∃= expr``
|
|
|
|
|
|
|
|
|
|
However, there are two major problems with that approach, one practical, and
|
|
|
|
|
one pedagogical.
|
|
|
|
|
|
|
|
|
|
The practical problem is the usual one that most keyboards don't offer any easy
|
|
|
|
|
way of entering mathematical symbols other than those used in basic arithmetic
|
|
|
|
|
(even the symbols appearing in this PEP were ultimately copied & pasted
|
|
|
|
|
from [3]_ rather than being entered directly).
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
The pedagogical problem is that the symbols for existence assertions (``∃``)
|
|
|
|
|
and universal assertions (``∀``) aren't going to be familiar to most people
|
|
|
|
|
the way basic arithmetic operators are, so we wouldn't actually be making the
|
|
|
|
|
proposed syntax easier to understand by adopting ``∃``.
|
|
|
|
|
|
|
|
|
|
By contrast, ``?`` is one of the few remaining unused ASCII punctuation
|
|
|
|
|
characters in Python's syntax, making it available as a candidate syntactic
|
|
|
|
|
marker for "this control flow operation is based on an existence check, not a
|
|
|
|
|
truth check".
|
|
|
|
|
|
|
|
|
|
Taking that path would also have the advantage of aligning Python's syntax
|
|
|
|
|
with corresponding syntax in other languages that offer similar features.
|
|
|
|
|
|
|
|
|
|
Drawing from the existing summary in PEP 505 and the Wikipedia articles on
|
|
|
|
|
the "safe navigation operator [1]_ and the "null coalescing operator" [2]_,
|
2016-10-25 03:15:18 -04:00
|
|
|
|
we see:
|
|
|
|
|
|
|
|
|
|
* The ``?.`` existence checking attribute access syntax precisely aligns with:
|
|
|
|
|
|
|
|
|
|
* the "safe navigation" attribute access operator in C# (``?.``)
|
|
|
|
|
* the "optional chaining" operator in Swift (``?.``)
|
|
|
|
|
* the "safe navigation" attribute access operator in Groovy (``?.``)
|
|
|
|
|
* the "conditional member access" operator in Dart (``?.``)
|
|
|
|
|
|
|
|
|
|
* The ``?[]`` existence checking attribute access syntax precisely aligns with:
|
|
|
|
|
|
2016-10-25 04:47:42 -04:00
|
|
|
|
* the "safe navigation" subscript operator in C# (``?[]``)
|
2016-10-25 03:15:18 -04:00
|
|
|
|
* the "optional subscript" operator in Swift (``?[].``)
|
|
|
|
|
|
|
|
|
|
* The ``?else`` existence checking fallback syntax semantically aligns with:
|
|
|
|
|
|
|
|
|
|
* the "null-coalescing" operator in C# (``??``)
|
|
|
|
|
* the "null-coalescing" operator in PHP (``??``)
|
|
|
|
|
* the "nil-coalescing" operator in Swift (``??``)
|
|
|
|
|
|
|
|
|
|
To be clear, these aren't the only spelling of these operators used in other
|
2016-10-28 02:53:30 -04:00
|
|
|
|
languages, but they're the most common ones, and the ``?`` symbol is the most
|
|
|
|
|
common syntactic marker by far (presumably prompted by the use of ``?`` to
|
|
|
|
|
introduce the "then" clause in C-style conditional expressions, which many
|
|
|
|
|
of these languages also offer).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Proposed keywords
|
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
|
|
Given the symbolic marker ``?``, it would be syntactically unambiguous to spell
|
|
|
|
|
the existence checking precondition and fallback operations using the same
|
|
|
|
|
keywords as their truth checking counterparts:
|
|
|
|
|
|
|
|
|
|
* ``expr1 ?and expr2`` (instead of ``expr1 ?then expr2``)
|
|
|
|
|
* ``expr1 ?or expr2`` (instead of ``expr1 ?else expr2``)
|
|
|
|
|
|
|
|
|
|
However, while syntactically unambiguous when written, this approach makes
|
|
|
|
|
the code incredibly hard to *pronounce* (What's the pronunciation of "?"?) and
|
|
|
|
|
also hard to *describe* (given reused keywords, there's no obvious shorthand
|
|
|
|
|
terms for "existence checking precondition (?and)" and "existence checking
|
|
|
|
|
fallback (?or)" that would distinguish them from "logical conjunction (and)"
|
|
|
|
|
and "logical disjunction (or)").
|
|
|
|
|
|
|
|
|
|
We could try to encourage folks to pronounce the ``?`` symbol as "exists",
|
|
|
|
|
making the shorthand names the "exists-and expression" and the
|
|
|
|
|
"exists-or expression", but there'd be no way of guessing those names purely
|
|
|
|
|
from seeing them written in a piece of code.
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
Instead, this PEP takes advantage of the proposed symbolic syntax to introduce
|
|
|
|
|
a new keyword (``?then``) and borrow an existing one (``?else``) in a way
|
|
|
|
|
that allows people to refer to "then expressions" and "else expressions"
|
|
|
|
|
without ambiguity.
|
2016-10-25 04:47:42 -04:00
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
These keywords also align well with the conditional expressions that are
|
|
|
|
|
semantically equivalent to the proposed expressions.
|
|
|
|
|
|
|
|
|
|
For ``?else`` expressions, ``expr1 ?else expr2`` is equivalent to::
|
|
|
|
|
|
|
|
|
|
_lhs_result = expr1
|
|
|
|
|
_lhs_result if operator.exists(_lhs_result) else expr2
|
|
|
|
|
|
|
|
|
|
Here the parallel is clear, since the ``else expr2`` appears at the end of
|
|
|
|
|
both the abbreviated and expanded forms.
|
|
|
|
|
|
|
|
|
|
For ``?then`` expressions, ``expr1 ?then expr2`` is equivalent to::
|
|
|
|
|
|
|
|
|
|
_lhs_result = expr1
|
|
|
|
|
expr2 if operator.exists(_lhs_result) else _lhs_result
|
|
|
|
|
|
|
|
|
|
Here the parallel isn't as immediately obvious due to Python's traditionally
|
|
|
|
|
anonymous "then" clauses (introduced by ``:`` in ``if`` statements and suffixed
|
|
|
|
|
by ``if`` in conditional expressions), but it's still reasonably clear as long
|
|
|
|
|
as you're already familiar with the "if-then-else" explanation of conditional
|
|
|
|
|
control flow.
|
2016-10-25 04:47:42 -04:00
|
|
|
|
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
Risks and concerns
|
|
|
|
|
==================
|
|
|
|
|
|
|
|
|
|
Readability
|
|
|
|
|
-----------
|
|
|
|
|
|
|
|
|
|
Learning to read and write the new syntax effectively mainly requires
|
|
|
|
|
internalising two concepts:
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
* expressions containing ``?`` include an existence check and may short circuit
|
2016-10-25 03:15:18 -04:00
|
|
|
|
* if ``None`` or another "non-existent" value is an expected input, and the
|
|
|
|
|
correct handling is to propagate that to the result, then the existence
|
|
|
|
|
checking operators are likely what you want
|
|
|
|
|
|
|
|
|
|
Currently, these concepts aren't explicitly represented at the language level,
|
|
|
|
|
so it's a matter of learning to recognise and use the various idiomatic
|
|
|
|
|
patterns based on conditional expressions and statements.
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
|
2016-10-25 03:15:18 -04:00
|
|
|
|
Magic syntax
|
|
|
|
|
------------
|
|
|
|
|
|
|
|
|
|
There's nothing about ``?`` as a syntactic element that inherently suggests
|
|
|
|
|
``is not None`` or ``operator.exists``. The main current use of ``?`` as a
|
|
|
|
|
symbol in Python code is as a trailing suffix in IPython environments to
|
|
|
|
|
request help information for the result of the preceding expression.
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
However, the notion of existence checking really does benefit from a pervasive
|
|
|
|
|
visual marker that distinguishes it from truth checking, and that calls for
|
2016-10-25 03:15:18 -04:00
|
|
|
|
a single-character symbolic syntax if we're going to do it at all.
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
|
2016-10-25 03:15:18 -04:00
|
|
|
|
Conceptual complexity
|
|
|
|
|
---------------------
|
|
|
|
|
|
|
|
|
|
This proposal takes the currently ad hoc and informal concept of "existence
|
|
|
|
|
checking" and elevates it to the status of being a syntactic language feature
|
|
|
|
|
with a clearly defined operator protocol.
|
|
|
|
|
|
|
|
|
|
In many ways, this should actually *reduce* the overall conceptual complexity
|
|
|
|
|
of the language, as many more expectations will map correctly between truth
|
|
|
|
|
checking with ``bool(expr)`` and existence checking with
|
|
|
|
|
``operator.exists(expr)`` than currently map between truth checking and
|
|
|
|
|
existence checking with ``expr is not None`` (or ``expr is not NotImplemented``
|
2016-10-28 02:53:30 -04:00
|
|
|
|
in the context of operand coercion, or the various NaN-checking operations
|
|
|
|
|
in mathematical libraries).
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
As a simple example of the new parallels introduced by this PEP, compare::
|
|
|
|
|
|
|
|
|
|
all_are_true = all(map(bool, iterable))
|
|
|
|
|
at_least_one_is_true = any(map(bool, iterable))
|
|
|
|
|
all_exist = all(map(operator.exists, iterable))
|
|
|
|
|
at_least_one_exists = any(map(operator.exists, iterable))
|
|
|
|
|
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
Design Discussion
|
|
|
|
|
=================
|
|
|
|
|
|
|
|
|
|
Subtleties in chaining existence checking expressions
|
|
|
|
|
-----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
Similar subtleties arise in chaining existence checking expressions as already
|
|
|
|
|
exist in chaining logical operators: the behaviour can be surprising if the
|
|
|
|
|
right hand side of one of the expressions in the chain itself returns a
|
|
|
|
|
value that doesn't exist.
|
|
|
|
|
|
|
|
|
|
As a result, ``value = arg1 ?then f(arg1) ?else default()`` would be dubious for
|
|
|
|
|
essentially the same reason that ``value = cond and expr1 or expr2`` is dubious:
|
|
|
|
|
the former will evaluate ``default()`` if ``f(arg1)`` returns ``None``, just
|
|
|
|
|
as the latter will evaluate ``expr2`` if ``expr1`` evaluates to ``False`` in
|
|
|
|
|
a boolean context.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Ambiguous interaction with conditional expressions
|
|
|
|
|
--------------------------------------------------
|
|
|
|
|
|
|
|
|
|
In the proposal as currently written, the following is a syntax error:
|
|
|
|
|
|
|
|
|
|
* ``value = f(arg) if arg ?else default``
|
|
|
|
|
|
|
|
|
|
While the following is a valid operation that checks a second condition if the
|
|
|
|
|
first doesn't exist rather than merely being false:
|
|
|
|
|
|
|
|
|
|
* ``value = expr1 if cond1 ?else cond2 else expr2``
|
|
|
|
|
|
|
|
|
|
The expression chaining problem described above means that the argument can be
|
|
|
|
|
made that the first operation should instead be equivalent to:
|
|
|
|
|
|
|
|
|
|
* ``value = f(arg) if operator.exists(arg) else default``
|
|
|
|
|
|
|
|
|
|
requiring the second to be written in the arguably clearer form:
|
|
|
|
|
|
|
|
|
|
* ``value = expr1 if (cond1 ?else cond2) else expr2``
|
|
|
|
|
|
|
|
|
|
Alternatively, the first form could remain a syntax error, and the existence
|
|
|
|
|
checking symbol could instead be attached to the ``if`` keyword:
|
|
|
|
|
|
|
|
|
|
* ``value = expr1 if? cond else expr2``
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Existence checking in other truth-checking contexts
|
|
|
|
|
---------------------------------------------------
|
|
|
|
|
|
|
|
|
|
The truth-checking protocol is currently used in the following syntactic
|
|
|
|
|
constructs:
|
|
|
|
|
|
|
|
|
|
* logical conjunction (and-expressions)
|
|
|
|
|
* logical disjunction (or-expressions)
|
|
|
|
|
* conditional expressions (if-else expressions)
|
|
|
|
|
* if statements
|
|
|
|
|
* while loops
|
|
|
|
|
* filter clauses in comprehensions and generator expressions
|
|
|
|
|
|
|
|
|
|
In the current PEP, switching from truth-checking with ``and`` and ``or`` to
|
|
|
|
|
existence-checking is a matter of substituting in the new keywords, ``?then``
|
|
|
|
|
and ``?else`` in the appropriate places.
|
|
|
|
|
|
|
|
|
|
For other truth-checking contexts, it proposes either importing and
|
|
|
|
|
using the ``operator.exists`` API, or else continuing with the current idiom
|
|
|
|
|
of checking specifically for ``expr is not None`` (or the context appropriate
|
|
|
|
|
equivalent).
|
|
|
|
|
|
|
|
|
|
The simplest possible enhancement in that regard would be to elevate the
|
|
|
|
|
proposed ``exists()`` API from an operator module function to a new builtin
|
|
|
|
|
function.
|
|
|
|
|
|
|
|
|
|
Alternatively, the ``?`` existence checking symbol could be supported as a
|
|
|
|
|
modifier on the ``if`` and ``while`` keywords to indicate the use of an
|
|
|
|
|
existence check rather than a truth check.
|
|
|
|
|
|
|
|
|
|
However, it isn't at all clear that the potential consistency benefits gained
|
|
|
|
|
for either suggestion would justify the additional disruption, so they've
|
|
|
|
|
currently been omitted from the proposal.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Defining expected invariant relations between ``__bool__`` and ``__exists__``
|
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
The PEP currently leaves the definition of ``__bool__`` on all existing types
|
|
|
|
|
unmodified, which ensures the entire proposal remains backwards compatible,
|
|
|
|
|
but results in the following cases where ``bool(obj)`` returns ``True``, but
|
|
|
|
|
the proposed ``operator.exists(obj)`` would return ``False``:
|
|
|
|
|
|
|
|
|
|
* ``NaN`` values for ``float``, ``complex``, and ``decimal.Decimal``
|
|
|
|
|
* ``Ellipsis``
|
|
|
|
|
* ``NotImplemented``
|
|
|
|
|
|
|
|
|
|
The main argument for potentially changing these is that it becomes easier to
|
|
|
|
|
reason about potential code behaviour if we have a recommended invariant in
|
|
|
|
|
place saying that values which indicate they don't exist in an existence
|
|
|
|
|
checking context should also report themselves as being ``False`` in a truth
|
|
|
|
|
checking context.
|
|
|
|
|
|
|
|
|
|
Failing to define such an invariant would lead to arguably odd outcomes like
|
|
|
|
|
``float("NaN") ?else 0.0`` returning ``0.0`` while ``float("NaN") or 0.0``
|
|
|
|
|
returns ``NaN``.
|
|
|
|
|
|
|
|
|
|
|
2016-10-25 03:15:18 -04:00
|
|
|
|
Limitations
|
|
|
|
|
===========
|
|
|
|
|
|
|
|
|
|
Arbitrary sentinel objects
|
|
|
|
|
--------------------------
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
This proposal doesn't attempt to provide syntactic support for the "sentinel
|
|
|
|
|
object" idiom, where ``None`` is a permitted explicit value, so a
|
2016-10-25 03:15:18 -04:00
|
|
|
|
separate sentinel object is defined to indicate missing values::
|
|
|
|
|
|
|
|
|
|
_SENTINEL = object()
|
|
|
|
|
def f(obj=_SENTINEL):
|
|
|
|
|
return obj if obj is not _SENTINEL else default_value()
|
|
|
|
|
|
|
|
|
|
This could potentially be supported at the expense of making the existence
|
|
|
|
|
protocol definition significantly more complex, both to define and to use:
|
|
|
|
|
|
|
|
|
|
* at the Python layer, ``operator.exists`` and ``__exists__`` implementations
|
|
|
|
|
would return the empty tuple to indicate non-existence, and otherwise return
|
|
|
|
|
a singleton tuple containing a reference to the object to be used as the
|
|
|
|
|
result of the existence check
|
|
|
|
|
* at the C layer, ``tp_exists`` implementations would return NULL to indicate
|
|
|
|
|
non-existence, and otherwise return a `PyObject *` pointer as the
|
|
|
|
|
result of the existence check
|
|
|
|
|
|
|
|
|
|
Given that change, the sentinel object idiom could be rewritten as::
|
|
|
|
|
|
|
|
|
|
class Maybe:
|
|
|
|
|
SENTINEL = object()
|
|
|
|
|
def __init__(self, value):
|
|
|
|
|
self._result = (value,) is value is not self.SENTINEL else ()
|
|
|
|
|
def __exists__(self):
|
|
|
|
|
return self._result
|
|
|
|
|
|
|
|
|
|
def f(obj=Maybe.SENTINEL):
|
|
|
|
|
return Maybe(obj) ?else default_value()
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
However, I don't think cases where the 3 proposed standard sentinel values (i.e.
|
|
|
|
|
``None``, ``Ellipsis`` and ``NotImplemented``) can't be used are going to be
|
2016-10-25 03:15:18 -04:00
|
|
|
|
anywhere near common enough for the additional protocol complexity and the loss
|
|
|
|
|
of symmetry between ``__bool__`` and ``__exists__`` to be worth it.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Specification
|
|
|
|
|
=============
|
|
|
|
|
|
|
|
|
|
The Abstract already gives the gist of the proposal and the Rationale gives
|
|
|
|
|
some specific examples. If there's enough interest in the basic idea, then a
|
|
|
|
|
full specification will need to provide a precise correspondence between the
|
2016-10-28 02:53:30 -04:00
|
|
|
|
proposed syntactic sugar and the underlying conditional expressions that is
|
|
|
|
|
sufficient to guide the creation of a reference implementation.
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
...TBD...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Implementation
|
|
|
|
|
==============
|
|
|
|
|
|
|
|
|
|
As with PEP 505, actual implementation has been deferred pending in-principle
|
|
|
|
|
interest in the idea of adding these operators - the implementation isn't
|
|
|
|
|
the hard part of these proposals, the hard part is deciding whether or not
|
|
|
|
|
this is a change where the long term benefits for new and existing Python users
|
|
|
|
|
outweigh the short term costs involved in the wider ecosystem (including
|
|
|
|
|
developers of other implementations, language curriculum developers, and
|
|
|
|
|
authors of other Python related educational material) adjusting to the change.
|
|
|
|
|
|
|
|
|
|
...TBD...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
References
|
|
|
|
|
==========
|
|
|
|
|
|
|
|
|
|
.. [1] Wikipedia: Safe navigation operator
|
|
|
|
|
(https://en.wikipedia.org/wiki/Safe_navigation_operator)
|
|
|
|
|
|
|
|
|
|
.. [2] Wikipedia: Null coalescing operator
|
|
|
|
|
(https://en.wikipedia.org/wiki/Null_coalescing_operator)
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
.. [3] FileFormat.info: Unicode Character 'THERE EXISTS' (U+2203)
|
|
|
|
|
(http://www.fileformat.info/info/unicode/char/2203/index.htm)
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Copyright
|
|
|
|
|
=========
|
|
|
|
|
|
2016-10-28 02:53:30 -04:00
|
|
|
|
This document has been placed in the public domain under the terms of the
|
|
|
|
|
CC0 1.0 license: https://creativecommons.org/publicdomain/zero/1.0/
|
2016-10-25 03:15:18 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
..
|
|
|
|
|
Local Variables:
|
|
|
|
|
mode: indented-text
|
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
|
sentence-end-double-space: t
|
|
|
|
|
fill-column: 70
|
|
|
|
|
coding: utf-8
|
|
|
|
|
End:
|