Apply more PEP 505 changes from Mark

This commit is contained in:
Chris Angelico 2015-10-21 12:44:00 +11:00
parent 5e5803d788
commit c5270848fe
2 changed files with 108 additions and 78 deletions

View File

@ -22,7 +22,7 @@ patterns involving null references.
* The "``null``-aware member access" operator accesses an instance member only * The "``null``-aware member access" operator accesses an instance member only
if that instance is non-``null``. Otherwise it returns ``null``. (This is also if that instance is non-``null``. Otherwise it returns ``null``. (This is also
called a "safe navigation" operator.) called a "safe navigation" operator.)
* The "``null``-aware index access" operator accesses a member of a collection * The "``null``-aware index access" operator accesses an element of a collection
only if that collection is non-``null``. Otherwise it returns ``null``. (This only if that collection is non-``null``. Otherwise it returns ``null``. (This
is another type of "safe navigation" operator.) is another type of "safe navigation" operator.)
@ -36,9 +36,10 @@ 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 when the question inevitably arises again. (This is the null alternative, so to
speak!) speak!)
This proposal includes several new operators and should not be considered an This proposal advances multiple alternatives, and it should be considered
all-or-nothing proposal. For example, the safe navigation operators might be severable. It may be accepted in whole or in part. For example, the safe
rejected even if the ``null``-coalescing operator is approved, or vice-versa. navigation operators might be rejected even if the ``null``-coalescing operator
is approved, or vice-versa.
Of course, Python does not have ``null``; it has ``None``, which is conceptually 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 distinct. Although this PEP is inspired by "``null``-aware" operators in other
@ -110,8 +111,8 @@ has `a get_notBefore() method
<https://github.com/pyca/pyopenssl/blob/3257877f8846e4357b495fa6c9344d01b11cf16d <https://github.com/pyca/pyopenssl/blob/3257877f8846e4357b495fa6c9344d01b11cf16d
/OpenSSL/crypto.py#L1219>`_ that returns either a timestamp or ``None``. This /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 function is a thin wrapper around an OpenSSL function with the return type
``ASN1_TIME *``. Since this C pointer is allowed to be null, the Python wrapper ``ASN1_TIME *``. Because this C pointer may be ``null``, the Python wrapper must
must be able to express a missing "not before" date, e.g. ``None``. be able to represent ``null``, and ``None`` is the chosen representation.
The representation of ``null`` is particularly noticeable when Python code is The representation of ``null`` is particularly noticeable when Python code is
marshalling data between two systems. For example, consider a Python server that marshalling data between two systems. For example, consider a Python server that
@ -123,7 +124,7 @@ converting between these representations adds unnecessary complexity to the
Python glue code. Python glue code.
Therefore, the preference for avoiding ``None`` is nothing more than a Therefore, the preference for avoiding ``None`` is nothing more than a
preference; ``None`` has legitimate uses, particularly in specific types of preference. ``None`` has legitimate uses, particularly in specific types of
software. Any hypothetical ``None``-aware operators should be construed as software. Any hypothetical ``None``-aware operators should be construed as
syntactic sugar for simplifying common patterns involving ``None``, and *should syntactic sugar for simplifying common patterns involving ``None``, and *should
not be construed* as error handling behavior. not be construed* as error handling behavior.
@ -219,8 +220,8 @@ products a customer has in his/her shopping cart::
An experienced Python developer should know how ``or`` works and be capable of 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 avoiding bugs like this. However, getting in the habit of using ``or`` for this
purpose still might cause even an experienced developer to occasionally make purpose still might cause an experienced developer to occasionally make this
this mistake, especially refactoring existing code and not carefully paying mistake, especially when refactoring existing code and not carefully paying
attention to the possible values of the left-hand operand. attention to the possible values of the left-hand operand.
For inexperienced developers, the problem is worse. The top Google hit for For inexperienced developers, the problem is worse. The top Google hit for
@ -266,9 +267,9 @@ The author of this package could have written it like this instead::
This ordering of the operands is more intuitive, but it requires 4 extra This ordering of the operands is more intuitive, but it requires 4 extra
characters (for "not "). It also highlights the repetition of identifiers: characters (for "not "). It also highlights the repetition of identifiers:
``data if data``, ``files if files``, etc. This example also benefits from short ``data if data``, ``files if files``, etc. This example benefits from short
identifiers. What if the tested expression is longer and/or has side effects? identifiers, but what if the tested expression is longer and/or has side
This is addressed in the next section. effects? This is addressed in the next section.
Motivating Examples Motivating Examples
@ -498,9 +499,10 @@ Usage Of ``None`` In The Standard Library
----------------------------------------- -----------------------------------------
The previous sections show some code patterns that are claimed to be "common", The previous sections show some code patterns that are claimed to be "common",
but how common are they? The attached script ``find-pep505.py`` is meant to but how common are they? The attached script `find-pep505.py
answer this question. It uses the ``ast`` module to search for these patterns in <https://hg.python.org/peps/file/tip/pep-0505/find-pep505.py>`_ is meant to
any ``*.py`` file. It checks for variations of the following patterns. answer this question. It uses the ``ast`` module to search for variations of the
following patterns in any ``*.py`` file.
>>> # None-coalescing if block >>> # None-coalescing if block
... ...
@ -566,14 +568,16 @@ The script prints out any matches it finds. Sample::
.. note:: .. note::
Coalescing with ``or`` is marked as a "possible" match, because it's not Coalescing with ``or`` is marked as a "possible" match, because it's not
trivial to infer whether ``or`` it is meant to coalesce False-y values trivial to infer whether ``or`` is meant to coalesce False-y values
(correct) or if it meant to coalesce ``None`` (incorrect). On the other (correct) or if it meant to coalesce ``None`` (incorrect). On the other
hand, we assume that `and` is always incorrect for safe navigation. hand, we assume that ``and`` is always incorrect for safe navigation.
The script is tested against ``test.py`` (also attached to this document) and The script has been tested against `test.py
the Python 3.4 standard library, but it should work on any arbitrary Python <https://hg.python.org/peps/file/tip/pep-0505/test.py>`_ and the Python 3.4
source code. The complete output from running it against the standard library is standard library, but it should work on any arbitrary Python 3 source code. The
attached to this proposal as ``find-pep505.out``. complete output from running it against the standard library is attached to this
proposal as `find-pep505.out <https://hg.python.org/peps/raw-file/tip/pep-0505
/find-pep505.out>`_.
The script counts how many matches it finds and prints the totals at the The script counts how many matches it finds and prints the totals at the
end:: end::
@ -602,13 +606,13 @@ those ideas are recorded here.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``None``-aware syntax applies to attribute and index access, so it seems The ``None``-aware syntax applies to attribute and index access, so it seems
natural to ask if it should also apply to function invocation syntax. Borrowing natural to ask if it should also apply to function invocation syntax. It might
a spelling similar to C#, it might be spelled ``foo?()``, where ``foo`` is only be written as ``foo?()``, where ``foo`` is only called if it is not None. This
called if it is not None. This idea was quickly rejected, for several reasons. idea was quickly rejected, for several reasons.
No other mainstream language has such syntax. Moreover, it would be difficult to First, no other mainstream language has such syntax. Moreover, it would be
discern if a function expression returned ``None`` because it was short- difficult to discern if a function call returned ``None`` because the function
circuited or because the function itself returned ``None``. Finally, Python itself returned ``None`` or because it was short-circuited. Finally, Python
evaluates arguments to a function before it looks up the function itself, so 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 ``foo?(bar())`` would still call ``bar()`` even if ``foo`` is ``None``. This
behaviour is unexpected for a so-called "short-circuiting" operator. behaviour is unexpected for a so-called "short-circuiting" operator.
@ -622,8 +626,10 @@ truly short-circuiting.
To generalize the ``None``-aware behavior and limit the number of new operators To generalize the ``None``-aware behavior and limit the number of new operators
introduced, a unary, postfix operator spelled ``?`` was suggested. The idea is introduced, a unary, postfix operator spelled ``?`` was suggested. The idea is
that ``?`` would return a special object that could would override dunder that ``?`` might return a special object that could would override dunder
methods to return itself or to return None. For example:: 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``::
class NoneQuestion(): class NoneQuestion():
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
@ -633,14 +639,13 @@ methods to return itself or to return None. For example::
return self return self
def __getitem__(self, key): def __getitem__(self, key):
reutrn self return self
An expression like ``foo?`` would return a ``NoneQuestion`` instance if ``foo`` With this new operator and new type, an expression like ``foo?.bar[baz]``
is ``None``; otherwise, it returns ``foo``. With this operator, an expression evaluates to ``NoneQuestion`` if ``foo`` is None. This is a nifty
like ``foo?.bar[baz]`` evaluates to ``NoneQuestion`` if ``foo`` is None. This is generalization, but it's difficult to use in practice since most existing code
a nice generalization, but it's difficult to use in practice since most existing won't know what ``NoneQuestion`` is.
code won't know what ``NoneQuestion`` is.
Going back to one of the motivating examples above, consider the following:: Going back to one of the motivating examples above, consider the following::
@ -652,8 +657,9 @@ The JSON serializer does not know how to serialize ``NoneQuestion``, nor will
any other API. This proposal actually requires *lots of specialized logic* any other API. This proposal actually requires *lots of specialized logic*
throughout the standard library and any third party library. throughout the standard library and any third party library.
The ``?`` operator may also be **too general**, in the sense that it can be At the same time, the ``?`` operator may also be **too general**, in the sense
combined with any other operator. What should the following expressions mean? that it can be combined with any other operator. What should the following
expressions mean?
>>> x? + 1 >>> x? + 1
>>> x? -= 1 >>> x? -= 1
@ -707,9 +713,12 @@ This PEP suggests 4 new operators be added to Python:
3. ``None``-aware attribute access 3. ``None``-aware attribute access
4. ``None``-aware index access/slicing 4. ``None``-aware index access/slicing
We will continue to assume the same spellings as in the previous sections in We will continue to assume the same spellings as in
order to focus on behavior before diving into the much more contentious issue of the previous sections in order to focus on behavior before diving into the much
how to spell these operators. more contentious issue of how to spell these operators.
A generalization of these operators is also proposed below under the heading
"Generalized Coalescing".
``None``-Coalescing Operator ``None``-Coalescing Operator
@ -831,13 +840,11 @@ preserving the short circuit semantics of the code that it replaces.
-------------------------- --------------------------
The idea of a ``None``-aware function invocation syntax was discussed on python- The idea of a ``None``-aware function invocation syntax was discussed on python-
ideas, but the idea was rejected by BDFL. The syntax comes dangerously close to ideas, but the idea was rejected by BDFL. The reasons for this rejection are
allowing a caller to change the return type of a function. If a function is detailed above.
defined to always return a value, then it seems strange that the call site could
change the function to return "value or ``None``".
Still, conditional function execution is a common idiom in Python, particularly Still, calling a function when it is not ``None`` is a common idiom in Python,
for callback functions. Consider this hypothetical example:: particularly for callback functions. Consider this hypothetical example::
import time import time
@ -847,8 +854,8 @@ for callback functions. Consider this hypothetical example::
if callback is not None: if callback is not None:
callback() callback()
With a ``None``-aware function invocation, this example might be written more With the rejected ``None``-aware function call syntax, this example might be
concisely as:: written more concisely as::
import time import time
@ -856,11 +863,12 @@ concisely as::
time.sleep(seconds) time.sleep(seconds)
callback?() callback?()
Consider a "``None``-severing" operator, however, which is a short-circuiting, Instead, consider a "``None``-severing" operator, however, which is a short-
boolean operator similar to the ``None``-coalesing operator, except it returns circuiting, boolean operator similar to the ``None``-coalesing operator, except
its left operand if that operand is None. If the left operand is None, then the it returns its left operand if that operand is None and otherwise returns the
right operand is not evaluated. Let's temporarily spell this operator ``✂`` and right operand. If the left operand is None, then the right operand is not
rewrite the example accordingly:: evaluated. Let's temporarily spell this operator ``✂`` and rewrite the example
accordingly::
import time import time
@ -925,8 +933,9 @@ section, we continue to use the temporary spelling ``💩``::
The operator has the same precedence and associativity as the plain attribute The operator has the same precedence and associativity as the plain attribute
access operator ``.``, but this operator is also short-circuiting in a unique access operator ``.``, but this operator is also short-circuiting in a unique
way: if the left operand is ``None``, then any adjacent attribute access, index way: if the left operand is ``None``, then any series of attribute access, index
access, slicing, or function call operators *are not evaluated*. access, slicing, or function call operators immediately to the right of it *are
not evaluated*.
>>> name = ' The Black Knight ' >>> name = ' The Black Knight '
>>> name.strip()[4:].upper() >>> name.strip()[4:].upper()
@ -953,10 +962,10 @@ To put it another way, the following expressions are semantically equivalent::
original goal of writing common cases more concisely. The Dart semantics are original goal of writing common cases more concisely. The Dart semantics are
nearly useless. nearly useless.
This operator short circuits one or more immediately adjacent attribute access, This operator short circuits one or more attribute access, index access,
index access, slicing, or function call operators, but it does not short circuit slicing, or function call operators that are immediately to its right, but it
any other operators (logical, bitwise, arithmetic, etc.), nor does it escape does not short circuit any other operators (logical, bitwise, arithmetic, etc.),
parentheses:: nor does it escape parentheses::
>>> d = date.today() >>> d = date.today()
>>> d💩year.numerator + 1 >>> d💩year.numerator + 1
@ -982,22 +991,23 @@ The third example fails because the operator does not escape parentheses. In
that example, the attribute access ``numerator`` is evaluated and fails because that example, the attribute access ``numerator`` is evaluated and fails because
``None`` does not have that attribute. ``None`` does not have that attribute.
Finally, observe that short circuiting adjacent operators is not at all the same thing as propagating ``None`` throughout an expression. Finally, observe that short circuiting adjacent operators is not at all the same thing as propagating ``None`` throughout an expression::
>>> user💩first_name.upper() >>> user💩first_name.upper()
If ``user`` is not ``None``, then ``user.first_name`` is evaluated. If If ``user`` is not ``None``, then ``user.first_name`` is evaluated. If
``user.first_name`` evaluates to ``None``, then ``user.first_name.upper()`` is ``user.first_name`` evaluates to ``None``, then ``user.first_name.upper()`` is
an error! In English, this expression says ``user`` is optional but if it has a an error! In English, this expression says, "``user`` is optional but if it has
value, then it must have a ``first_name``, too. a value, then it must have a ``first_name``, too.""
If ``first_name`` is supposed to be optional attribute, then the expression must If ``first_name`` is supposed to be optional attribute, then the expression must
make that explicit: make that explicit::
>>> user💩first_name💩upper() >>> user💩first_name💩upper()
The operator is not intended as an error silencing mechanism, and it would be The operator is not intended as an error silencing mechanism, and it would be
wrong if its presence infected nearby operators. undesirable if its presence infected nearby operators.
``None``-Aware Index Access/Slicing Operator ``None``-Aware Index Access/Slicing Operator
-------------------------------------------- --------------------------------------------
@ -1036,11 +1046,12 @@ The ``None``-aware slicing operator behaves similarly::
None None
These operators have the same precedence as the plain index access and slicing These operators have the same precedence as the plain index access and slicing
operators. operators. They also have the same short-circuiting behavior as the
``None``-aware attribute access.
Generalization Generalized Coalescing
-------------- ----------------------
Making ``None`` a special case may seem too specialized and magical. It is Making ``None`` a special case may seem too specialized and magical. It is
possible to generalize the behavior by making the ``None``-aware operators possible to generalize the behavior by making the ``None``-aware operators
@ -1058,13 +1069,32 @@ to this::
def __coalesce__(self): def __coalesce__(self):
return True return True
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::
>>> foo ✊🍆 bar
>>> 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()
This generalization allows for domain-specific ``null`` objects to be coalesced This generalization allows for domain-specific ``null`` objects to be coalesced
just like ``None``. For example the ``pyasn1`` package has a type called just like ``None``. For example the ``pyasn1`` package has a type called
``Null`` that represents an ASN.1 ``null``. ``Null`` that represents an ASN.1 ``null``.
If this generalization is accepted, then the operators will need to be renamed >>> from pyasn1.type import univ
such that the term ``None`` is not used, e.g. "Coalescing Operator", "Coalesced >>> univ.Null() ✊🍆 univ.Integer(123)
Member Access Operator", etc. 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``.
Operator Spelling Operator Spelling
@ -1081,7 +1111,7 @@ 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>`_.) <http://opensource.com/life/14/9/why-python-4-wont-be-python-3>`_.)
Furthermore, nearly every single punctuation character on a standard keyboard Furthermore, nearly every single punctuation character on a standard keyboard
already has special meaning in Python. The only exceptions are ``$``, ``~``, already has special meaning in Python. The only exceptions are ``$``, ``!``,
``?``, and backtick (as of Python 3). This leaves few options for a new, single- ``?``, 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 ``??`` character operator. A two character spelling is more likely, such as the ``??``
and ``?.`` spellings in other programming languages, but this decreases the and ``?.`` spellings in other programming languages, but this decreases the
@ -1124,24 +1154,24 @@ operator.
- Pros: similar to existing ``or`` operator - Pros: similar to existing ``or`` operator
- Cons: the difference between this and ``or`` is not intuitive; punctuation - Cons: the difference between this and ``or`` is not intuitive; punctuation
is ugly is ugly
3. ``foo ? bar`` 3. ``foo ? bar ? baz``
- Pros: similar to ``??`` used in other languages - Pros: similar to ``??`` used in other languages
- Cons: punctuation is ugly; possible conflict with IPython; not used by any - Cons: punctuation is ugly; possible conflict with IPython; not used by any
other language other language
4. ``foo $$ bar`` 4. ``foo $$ bar $$ baz``
- Pros: pronounced "value operator" because it returns the first operand - Pros: pronounced "value operator" because it returns the first operand
that has a "value" that has a "value"
- Cons: punctuation is ugly; not used by any other language - Cons: punctuation is ugly; not used by any other language
5. ``foo else bar`` 5. ``foo else bar else baz``
- Pros: prettier than punctuation; uses an existing keyword - Pros: prettier than punctuation; uses an existing keyword
- Cons: difficult or impossible to implement with Python's LL(1) parser - Cons: difficult or impossible to implement with Python's LL(1) parser
6. ``foo or else bar`` 6. ``foo or else bar or else baz``
- Pros: prettier than punctuation; use existing keywords - Pros: prettier than punctuation; use existing keywords
- Cons: difficult or impossible to implement with Python's LL(1) parser - Cons: difficult or impossible to implement with Python's LL(1) parser
7. ``foo def bar`` 7. ``foo def bar def baz``
- Pros: pronounced 'default'; prettier than punctuation - Pros: pronounced 'default'; prettier than punctuation
- Cons: difficult or impossible to implement with Python's LL(1) parser - Cons: difficult or impossible to implement with Python's LL(1) parser
8. ``foo then bar`` 8. ``foo then bar then baz``
- Pros: prettier than punctuation - Pros: prettier than punctuation
- Cons: requires a new keyword, probably can't be implemented until Python 4 - Cons: requires a new keyword, probably can't be implemented until Python 4
(and maybe not even then) (and maybe not even then)

View File

@ -1,5 +1,5 @@
''' '''
This file is used for testing find-pep505.out. This file is used for testing find-pep505.py.
nc_* and Nc* are examples of null coalescing. nc_* and Nc* are examples of null coalescing.
sn_* and Sn* are examples of save navigation. sn_* and Sn* are examples of save navigation.