8000 Clean up PEP-505. by mehaase · Pull Request #119 · python/peps · GitHub
[go: up one dir, main page]

Skip to content

Clean up PEP-505. #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 31, 2016
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
PEP-0505: Commmit to a specific syntax
I've removed all of the alternative syntax discussion and the
community poll. I expanded the reasoning for picking the spellings
that I did.
  • Loading branch information
mehaase committed Oct 31, 2016
commit a68b437b48216c5e7bd00b5ebbc8cbdd39c52343
236 changes: 77 additions & 159 deletions pep-0505.txt
Original file line number Diff line number Diff line change
Expand Up @@ -370,9 +370,7 @@ 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. We
will temporarily use the spelling ``?.`` for this operator, but later in the
PEP there will be a full discussion about alternative spellings::
As a quick preview, consider an alternative rewrite using a new operator::

class SiteView(FlaskView):
@route('/site/<id_>', methods=['GET'])
Expand Down Expand Up @@ -464,8 +462,7 @@ 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 will temporarily spell this
operator ``??``, but alternative spellings will be discussed further down::
this common need to supply a default value::

class BaseUploadObject(object):
def find_ctype(self, filename):
Expand Down Expand Up @@ -643,7 +640,7 @@ 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.

Going back to one of the motivating examples above, consider the following.
Going back to one of the motivating examples above, consider the following::

>>> import json
>>> created = None
Expand Down Expand Up @@ -716,6 +713,60 @@ A generalization of these operators is also proposed below under the heading
"Generalized Coalescing".


Operator Spelling
-----------------

Despite significant support for the proposed operators, the majority of
discussion on python-ideas fixated on the spelling. Many alternative spellings
were proposed, both punctuation and keywords, but each alternative drew some
criticism. Spelling the operator as a keyword is problematic, because adding new
keywords to the language is not backwards compatible.

It is not impossible to add a new keyword, however, and we can look at several
other PEPs for inspiration. For example, `PEP-492
<https://www.python.org/dev/peps/pep-0492/>`_ introduced the new keywords
``async`` and ``await`` into Python 3.5. These new keywords are fully backwards
compatible, because that PEP also introduces a new lexical context such that
``async`` and ``await`` are only treated as keywords when used inside of an
``async def`` function. In other locations, ``async`` and ``await`` may be used
as identifiers.

It is also possible to craft a new operator out of existing keywords, as was
the case with `PEP-308 <https://www.python.org/dev/peps/pep-0308/>`_, which
created a ternary operator by cobbling together the `if` and `else` keywords
into a new operator.

In addition to the lexical acrobatics required to create a new keyword, keyword
operators are also undesirable for creating an assignment shortcut syntax. In
Dart, for example, ``x ??= y`` is an assignment shortcut that approximately
means ``x = x ?? y`` except that ``x`` is only evaluated once. If Python's
coalesce operator is a keyword, e.g. ``foo``, then the assignment shortcut would
be very ugly: ``x foo= y``.

Spelling new logical operators with punctuation is unlikely, for several
reasons. First, Python eschews punctuation for logical operators. For example,
it uses ``not`` instead of ``!``, ``or`` instead of ``||``, and ``… if … else …``
instead of ``… ? … : …``.

Second, nearly every single punctuation character on a standard keyboard already
has special meaning in Python. The only exceptions are ``$``, ``!``, ``?``, and
backtick (as of Python 3). This leaves few options for a new, single-character
operator.

Third, 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.

The spellings ``??`` and ``?.`` will be familiar to programmers who have seen
them in other popular programming languages. Any alternative punctuation will be
just as ugly but without the benefit of familiarity from other languages.
Therefore, this proposal spells the new operators using the same punctuation
that already exists in other languages.


``None``-Coalescing Operator
----------------------------

Expand Down Expand Up @@ -764,39 +815,17 @@ The operator has higher precedence than the comparison operators ``==``, ``>``,
This precedence is chosen for making "default value" expressions intuitive to
read and write::

>>> user_flag = None
>>> default_flag = True
>>> not user_flag ?? default_flag # Same as next expression.
False
>>> not (user_flag ?? default_flag) # Same as previous.
False
>>> (not user_flag) ?? default_flag # Different from previous.
True
>>> not None ?? True
>>> not (None ?? True) # Same precedence

>>> user_quantity = None
>>> default_quantity = 1
>>> 1 == user_quantity ?? default_quantity # Same as next expression.
True
>>> 1 == (user_quantity ?? default_quantity) # Same as previous.
True
>>> (1 == user_quantity) ?? default_quantity # Different from previous.
False

>>> user_words = None
>>> default_words = ['foo', 'bar']
>>> 'foo' in user_words ?? default_words # Same as next expression.
True
>>> 'foo' in (user_words ?? default_words) # Same as previous.
True
>>> ('foo' in user_words) ?? default_words # Different from previous.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: argument of type 'NoneType' is not iterable
>>> 1 == None ?? 1
>>> 1 == (None ?? 1) # Same precedence

>>> user_discount = None
>>> default_discount = 0.9
>>> price = 100
>>> price * user_discount ?? default_discount
>>> 'foo' in None ?? ['foo', 'bar']
>>> 'foo' in (None ?? ['foo', 'bar']) # Same precedence

>>> 1 + None ?? 2
>>> 1 + (None ?? 2) # Same precedence

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``-
Expand All @@ -805,11 +834,11 @@ coalescing operator, because there is no implicit type coersion to ``bool``::
>>> requested_quantity = 0
>>> default_quantity = 1
>>> price = 100
>>> (requested_quantity ?? default_quantity) * price
>>> requested_quantity ?? default_quantity * price
0

The ``None``-coalescing operator also has a corresponding assignment shortcut.
The following assignments are semantically similar, except that `foo` is only
The following assignments are semantically similar, except that ```foo`` is only
looked up once when using the assignment shortcut::

>>> foo ??= []
Expand Down Expand Up @@ -837,9 +866,7 @@ preserving the short circuit semantics of the code that it replaces.
The ``None``-aware attribute access operator (also called "safe navigation")
checks its left operand. If the left operand is ``None``, then the operator
evaluates to ``None``. If the the left operand is not ``None``, then the
operator accesses the attribute named by the right operand. We will continue to
use the spelling ``?.`` in this section, but keep in mind that alternative
spellings will be discussed below::
operator accesses the attribute named by the right operand::

>>> from datetime import date
>>> d = date.today()
Expand Down Expand Up @@ -979,10 +1006,10 @@ operators. They also have the same short-circuiting behavior as the
Generalized Coalescing
----------------------

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.
Making ``None`` a special case is too specialized and magical. The behavior can
be generalized 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::
Expand Down Expand Up @@ -1025,122 +1052,13 @@ generalization also makes it easier to work with the Null Object Pattern, [3]_
for those developers who prefer to avoid using ``None``.


Operator Spelling
-----------------

Despite significant support for the proposed operators, the majority of
discussion on python-ideas fixated on the spelling. Many alternative spellings
were proposed, including punctutation and keywords, but each alternative
drew significant criticism.

Spelling the operators with punctuation has several downsides. First, Python
eschews punctuation for logical operators. For example, it uses ``not`` instead
of ``!`` and ``… if … else …`` instead of ``… ? … : …``.

Second, nearly every single punctuation character on a standard keyboard already
has special meaning in Python. The only exceptions are ``$``, ``!``, ``?``, and
backtick (as of Python 3). This leaves few options for a new, single- character
operator. A two character spelling is found in other mainstream langauges, e.g.
``??`` and ``?.``, but this might uglier than a single character operator.

Third, 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. All of the proposed spellings below are compatible
with existing IPython shells.

On the other hand, spelling the operator as a keyword is also unpopular. First,
adding new keywords to the language is not backwards compatible if programs are
already using identifiers with the same name. However, it is not impossible to
introduce a new keyword. For example, `PEP-492 <https://www.python.org/dev/pe
ps/pep-0492/>`_ introduced the new keywords ``async`` and ``await`` into Python
3.5.

It is also possible to craft a new operator out of existing keywords, as was
the case with `PEP-308 <https://www.python.org/dev/peps/pep-0308/>`_, which
created a ternary operator by cobbling together the `if` and `else` keywords
into a new operator. PEP-308 is a good inspiration for this PEP, because it
faced similar debates over spelling, particularly choosing whether to use the
same punctuation found in other languages or to choose a more Pythonic spelling
with keywords.


Alternative Spellings
~~~~~~~~~~~~~~~~~~~~~

In keeping with the spirit of the PEP, several alternative spellings for each of
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.

.. note::

Your author is not a CPython hacker nor a compiler developer. Some of the
proposed spellings may be difficult or impossible to implement with Python's
LL(1) parser. This is an area where I could use feedback on the PEP.

The following spellings have been proposed on the python-ideas list 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
is ugly; different precedence from ``or`` may be confusing
3. ``foo else bar else baz``
- Pros: prettier than punctuation; uses an existing keyword
- Cons: might be confusing to add another meaning to ``else``
4. ``foo then bar then baz``
- Pros: prettier than punctuation
- Cons: requires a new keyword

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 try .bar``
- Pros: uses an existing keyword
- Cons: might be confusing to add another meaning to ``try``

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 try ['bar']``
- Pros: uses an existing keyword;
- Cons: difficult or impossible to implement in Python's LL(1) parser


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.
The author of this PEP is not competent with grammars or lexers, and given the
contentiousness of this proposal, the implementation details for CPython will be
deferred until we have a clearer idea that one or more of the proposed
enhancements will be approved.

...TBD...

Expand Down
0