
Possibly this is exactly the wrong time to propose the next big syntax change, since we currently have nobody to declare on it, but since we're likely to argue for a while anyway it probably can't hurt (and maybe this will become the test PEP for whoever takes the reins?).
FWIW, Guido had previously indicated that he was generally favourable towards most of this proposal, provided we could figure out coherent semantics. Last time we tried, that didn't happen, so this time I've made the semantics much more precise, have implemented and verified them, and made much stronger statements about why we are proposing these.
Additional thanks to Mark Haase for writing most of the PEP. All the fair and balanced parts are his - all the overly strong opinions are mine.
Also thanks to Nick Coghlan for writing PEPs 531 and 532 last time we went through this - if you're unhappy with "None" being treated as a special kind of value, I recommend reading those before you start repeating them.
There is a formatted version of this PEP at https://www.python.org/dev/peps/pep-0505/
My current implementation is at https://github.com/zooba/cpython/tree/pep-505 (though I'm considering removing some of the new opcodes I added and just generating more complex code - in any case, let's get hung up on the proposal rather than the implementation :) )
Let the discussions begin!
---
PEP: 505 Title: None-aware operators Version: $Revision$ Last-Modified: $Date$ Author: Mark E. Haase mehaase@gmail.com, Steve Dower steve.dower@python.org Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 18-Sep-2015 Python-Version: 3.8
Abstract ========
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.) * The "``null``-aware index access" operator accesses an element of a collection only if that collection is non-``null``. Otherwise it returns ``null``. (This is another type of "safe navigation" operator.)
This PEP proposes three ``None``-aware operators for Python, based on the definitions and other language's implementations of those above. Specifically:
* The "``None`` coalescing`` binary operator ``??`` returns the left hand side if it evaluates to a value that is not ``None``, or else it evaluates and returns the right hand side. A coalescing ``??=`` augmented assignment operator is included. * The "``None``-aware attribute access" operator ``?.`` evaluates the complete expression if the left hand side evaluates to a value that is not ``None`` * The "``None``-aware indexing" operator ``?[]`` evaluates the complete expression if the left hand site evaluates to a value that is not ``None``
Syntax and Semantics ====================
Specialness of ``None`` -----------------------
The ``None`` object denotes the lack of a value. For the purposes of these operators, the lack of a value indicates that the remainder of the expression also lacks a value and should not be evaluated.
A rejected proposal was to treat any value that evaluates to false in a Boolean context as not having a value. However, the purpose of these operators is to propagate the "lack of value" state, rather that the "false" state.
Some argue that this makes ``None`` special. We contend that ``None`` is already special, and that using it as both the test and the result of these operators does not change the existing semantics in any way.
See the `Rejected Ideas`_ section for discussion on the rejected approaches.
Grammar changes ---------------
The following rules of the Python grammar are updated to read::
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '**=' | '//=' | '??=')
power: coalesce ['**' factor] coalesce: atom_expr ['??' factor] atom_expr: ['await'] atom trailer* trailer: ('(' [arglist] ')' | '[' subscriptlist ']' | '?[' subscriptlist ']' | '.' NAME | '?.' NAME)
Inserting the ``coalesce`` rule in this location ensures that expressions resulting in ``None`` are natuarlly coalesced before they are used in operations that would typically raise ``TypeError``. Like ``and`` and ``or`` the right-hand expression is not evaluated until the left-hand side is determined to be ``None``. For example::
a, b = None, None def c(): return None def ex(): raise Exception()
(a ?? 2 ** b ?? 3) == a ?? (2 ** (b ?? 3)) (a * b ?? c // d) == a * (b ?? c) // d (a ?? True and b ?? False) == (a ?? True) and (b ?? False) (c() ?? c() ?? True) == True (True ?? ex()) == True (c ?? ex)() == c()
Augmented coalescing assignment only rebinds the name if its current value is ``None``. If the target name already has a value, the right-hand side is not evaluated. For example::
a = None b = '' c = 0
a ??= 'value' b ??= undefined_name c ??= shutil.rmtree('/') # don't try this at home, kids
assert a == 'value' assert b == '' assert c == '0' and any(os.scandir('/'))
Adding new trailers for the other ``None``-aware operators ensures that they may be used in all valid locations for the existing equivalent operators, including as part of an assignment target (more details below). As the existing evaluation rules are not directly embedded in the grammar, we specify the required changes here.
Assume that the ``atom`` is always successfully evaluated. Each ``trailer`` is then evaluated from left to right, applying its own parameter (either its arguments, subscripts or attribute name) to produce the value for the next ``trailer``. Finally, if present, ``await`` is applied.
For example, ``await a.b(c).d[e]`` is currently parsed as ``['await', 'a', '.b', '(c)', '.d', '[e]']`` and evaluated::
_v = a _v = _v.b _v = _v(c) _v = _v.d _v = _v[e] await _v
When a ``None``-aware operator is present, the left-to-right evaluation may be short-circuited. For example, ``await a?.b(c).d?[e]`` is evaluated::
_v = a if _v is not None: _v = _v.b _v = _v(c) _v = _v.d if _v is not None: _v = _v[e] await _v
.. note:: ``await`` will almost certainly fail in this context, as it would in the case where code attempts ``await None``. We are not proposing to add a ``None``-aware ``await`` keyword here, and merely include it in this example for completeness of the specification, since the ``atom_expr`` grammar rule includes the keyword. If it were in its own rule, we would have never mentioned it.
Parenthesised expressions are handled by the ``atom`` rule (not shown above), which will implicitly terminate the short-circuiting behaviour of the above transformation. For example, ``(a?.b ?? c).d?.e`` is evaluated as::
# a?.b _v = a if _v is not None: _v = _v.b
# ... ?? c if _v is None: _v = c
# (...).d?.e _v = _v.d if _v is not None: _v = _v.e
When used as an assignment target, the ``None``-aware operations may only be used in a "load" context. That is, ``a?.b = 1`` and ``a?[b] = 1`` will raise ``SyntaxError``. Use earlier in the expression (``a?.b.c = 1``) is permitted, though unlikely to be useful unless combined with a coalescing operation::
(a?.b ?? d).c = 1
Examples ========
This section presents some examples of common ``None`` patterns and shows what conversion to use ``None``-aware operators may look like.
Standard Library ----------------
Using the ``find-pep505.py`` script[3]_ an analysis of the Python 3.7 standard library discovered up to 678 code snippets that could be replaced with use of one of the ``None``-aware operators::
$ find /usr/lib/python3.7 -name '*.py' | xargs python3.7 find-pep505.py <snip> Total None-coalescing `if` blocks: 449 Total [possible] None-coalescing `or`: 120 Total None-coalescing ternaries: 27 Total Safe navigation `and`: 13 Total Safe navigation `if` blocks: 61 Total Safe navigation ternaries: 8
Some of these are shown below as examples before and after converting to use the new operators.
From ``bisect.py``::
def insort_right(a, x, lo=0, hi=None): # ... if hi is None: hi = len(a) # ...
After updating to use the ``??=`` augmented assignment statement::
def insort_right(a, x, lo=0, hi=None): # ... hi ??= len(a) # ...
From ``calendar.py``::
encoding = options.encoding if encoding is None: encoding = sys.getdefaultencoding() optdict = dict(encoding=encoding, css=options.css)
After updating to use the ``??`` operator::
optdict = dict(encoding=encoding ?? sys.getdefaultencoding(), css=options.css)
From ``dis.py``::
def _get_const_info(const_index, const_list): argval = const_index if const_list is not None: argval = const_list[const_index] return argval, repr(argval)
After updating to use the ``?[]`` and ``??`` operators::
def _get_const_info(const_index, const_list): argval = const_list?[const_index] ?? const_index return argval, repr(argval)
From ``inspect.py``::
for base in object.__bases__: for name in getattr(base, "__abstractmethods__", ()): value = getattr(object, name, None) if getattr(value, "__isabstractmethod__", False): return True
After updating to use the ``?.`` operator (and deliberately not converting to use ``any()``)::
for base in object.__bases__: for name in base?.__abstractmethods__ ?? (): if object?.name?.__isabstractmethod__: return True
From ``os.py``::
if entry.is_dir(): dirs.append(name) if entries is not None: entries.append(entry) else: nondirs.append(name)
After updating to use the ``?.`` operator::
if entry.is_dir(): dirs.append(name) entries?.append(entry) else: nondirs.append(name)
jsonify -------
This example is from a Python web crawler that uses the Flask framework as its 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 does not have a native way to represent a ``datetime``, so the server's contract states that any non-``null`` date is represented as an ISO-8601 string.
Without knowing the exact semantics of the ``first_seen`` and ``last_seen`` attributes, it is impossible to know whether the attribute can be safely or performantly accessed multiple times.
One way to fix this code is to replace each conditional expression with an explicit value assignment and 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_)
first_seen_dt = site.first_seen if first_seen_dt is None: first_seen = None else: first_seen = first_seen_dt.isoformat()
last_seen_dt = site.last_seen if last_seen_dt is None: last_seen = None else: last_seen = last_seen_dt.isoformat()
return jsonify( first_seen=first_seen, id=site.id, is_active=site.is_active, last_seen=last_seen, url=site.url.rstrip('/') )
This adds ten lines of code and four new code paths to the function, dramatically increasing the apparent complexity. Rewriting using the ``None``-aware attribute operator results in shorter code with more clear intent::
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('/') )
Grab ----
The next example is from a Python scraping library called `Grab https://github.com/lorien/grab/blob/4c95b18dcb0fa88eeca81f5643c0ebfb114bf728/gr ab/upload.py`_::
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
This example contains several good examples of needing to provide default values. Rewriting to use conditional expressions reduces the overall lines of code, but does not necessarily improve readability::
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.
Rewriting using the ``None`` coalescing operator::
class BaseUploadObject(object): def find_content_type(self, filename): ctype, encoding = mimetypes.guess_type(filename) return ctype ?? 'application/octet-stream'
class UploadContent(BaseUploadObject): def __init__(self, content, filename=None, content_type=None): self.content = content self.filename = filename ?? self.get_random_filename() self.content_type = content_type ?? self.find_content_type(self.filename)
class UploadFile(BaseUploadObject): def __init__(self, path, filename=None, content_type=None): self.path = path self.filename = filename ?? os.path.split(path)[1] self.content_type = content_type ?? self.find_content_type(self.filename)
This syntax has an intuitive ordering of the operands. In ``find_content_type``, for example, the preferred value ``ctype`` appears before the fallback value. The terseness of the syntax also makes for fewer lines of code and less code to visually parse, and reading from left-to-right and top-to-bottom more accurately follows the execution flow.
Rejected Ideas ==============
The first three ideas in this section are oft-proposed alternatives to treating ``None`` as special. For further background on why these are rejected, see their treatment in `PEP 531 https://www.python.org/dev/peps/pep-0531/`_ and `PEP 532 https://www.python.org/dev/peps/pep-0532/`_ and the associated discussions.
No-Value Protocol -----------------
The operators could be generalised to user-defined types by defining a protocol to indicate when a value represents "no value". Such a protocol may be a dunder method ``__has_value__(self)` that returns ``True`` if the value should be treated as having a value, and ``False`` if the value should be treated as no value.
With this generalization, ``object`` would implement a dunder method equivalent to this::
def __has_value__(self): return True
``NoneType`` would implement a dunder method equivalent to this::
def __has_value__(self): return False
In the specification section, all uses of ``x is None`` would be replaced with ``not x.__has_value__()``.
This generalization would allow for domain-specific "no-value" objects to be coalesced just like ``None``. For example the ``pyasn1`` package has a type called ``Null`` that represents an ASN.1 ``null``::
>>> from pyasn1.type import univ >>> univ.Null() ?? univ.Integer(123) Integer(123)
Similarly, values such as ``math.nan`` and ``NotImplemented`` could be treated as representing no value.
However, the "no-value" nature of these values is domain-specific, which means they *should* be treated as a value by the language. For example, ``math.nan.imag`` is well defined (it's ``0.0``), and so short-circuiting ``math.nan?.imag`` to return ``math.nan`` would be incorrect.
As ``None`` is already defined by the language as being the value that represents "no value", and the current specification would not preclude switching to a protocol in the future (though changes to built-in objects would not be compatible), this idea is rejected for now.
Boolean-aware operators -----------------------
This suggestion is fundamentally the same as adding a no-value protocol, and so the discussion above also applies.
Similar behavior to the ``??`` operator can be achieved with an ``or`` expression, however ``or`` checks whether its left operand is false-y and not specifically ``None``. This approach is attractive, as it requires fewer changes to the language, but ultimately does not solve the underlying problem correctly.
Assuming the check is for truthiness rather than ``None``, there is no longer a need for the ``??`` operator. However, applying this check to the ``?.`` and ``?[]`` operators prevents perfectly valid operations applying
Consider the following example, where ``get_log_list()`` may return either a list containing current log messages (potentially empty), or ``None`` if logging is not enabled::
lst = get_log_list() lst?.append('A log message')
If ``?.`` is checking for true values rather than specifically ``None`` and the log has not been initialized with any items, no item will ever be appended. This violates the obvious intent of the code, which is to append an item. The ``append`` method is available on an empty list, as are all other list methods, and there is no reason to assume that these members should not be used because the list is presently empty.
Further, there is no sensible result to use in place of the expression. A normal ``lst.append`` returns ``None``, but under this idea ``lst?.append`` may result in either ``[]`` or ``None``, depending on the value of ``lst``. As with the examples in the previous section, this makes no sense.
As checking for truthiness rather than ``None`` results in apparently valid expressions no longer executing as intended, this idea is rejected.
Exception-aware operators -------------------------
Arguably, the reason to short-circuit an expression when ``None`` is encountered is to avoid the ``AttributeError`` or ``TypeError`` that would be raised under normal circumstances. As an alternative to testing for ``None``, the ``?.`` and ``?[]`` operators could instead handle ``AttributeError`` and ``TypeError`` raised by the operation and skip the remainder of the expression.
This produces a transformation for ``a?.b.c?.d.e`` similar to this::
_v = a try: _v = _v.b except AttributeError: pass else: _v = _v.c try: _v = _v.d except AttributeError: pass else: _v = _v.e
One open question is which value should be returned as the expression when an exception is handled. The above example simply leaves the partial result, but this is not helpful for replacing with a default value. An alternative would be to force the result to ``None``, which then raises the question as to why ``None`` is special enough to be the result but not special enough to be the test.
Secondly, this approach masks errors within code executed implicitly as part of the expression. For ``?.``, any ``AttributeError`` within a property or ``__getattr__`` implementation would be hidden, and similarly for ``?[]`` and ``__getitem__`` implementations.
Similarly, simple typing errors such as ``{}?.ietms()`` could go unnoticed.
Existing conventions for handling these kinds of errors in the form of the ``getattr`` builtin and the ``.get(key, default)`` method pattern established by ``dict`` show that it is already possible to explicitly use this behaviour.
As this approach would hide errors in code, it is rejected.
``None``-aware Function Call ----------------------------
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. It might be written as ``foo?()``, where ``foo`` is only called if it is not None.
This has been deferred on the basis of the proposed operators being intended to aid traversal of partially populated hierarchical data structures, *not* for traversal of arbitrary class hierarchies. This is reflected in the fact that none of the other mainstream languages that already offer this syntax have found it worthwhile to support a similar syntax for optional function invocations.
A workaround similar to that used by C# would be to write ``maybe_none?.__call__(arguments)``. If the callable is ``None``, the expression will not be evaluated. (The C# equivalent uses ``?.Invoke()`` on its callable type.)
``?`` 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 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``::
class NoneQuestion(): def __call__(self, *args, **kwargs): return self
def __getattr__(self, name): return self
def __getitem__(self, key): return self
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.
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.
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?::
>>> 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.
Built-in ``maybe`` ------------------
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".
A Python package called `pymaybe https://pypi.org/p/pymaybe/`_ provides a rough approximation. The documentation shows the following example::
>>> maybe('VALUE').lower() 'value'
>>> maybe(None).invalid().method().or_else('unknown') 'unknown'
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.
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.
The idea of adding a builtin ``maybe`` type to enable this scenario is rejected.
Just use a conditional expression ---------------------------------
Another common way to initialize default values is to use the ternary operator. Here is an excerpt from the popular `Requests package https://github.com/kennethreitz/requests/blob/14a555ac716866678bf17e43e23230d81 a8149f5/requests/models.py#L212`_::
data = [] if data is None else data files = [] if files is None else files headers = {} if headers is None else headers params = {} if params is None else params hooks = {} if hooks is None else hooks
This particular formulation has the undesirable effect of putting the operands in an unintuitive order: the brain thinks, "use ``data`` if possible and use ``[]`` as a fallback," but the code puts the fallback *before* the preferred value.
The author of this package could have written it like this instead::
data = data if data is not None else [] files = files if files is not None else [] headers = headers if headers is not None else {} params = params if params is not None else {} hooks = hooks if hooks is not None else {}
This ordering of the operands is more intuitive, but it requires 4 extra characters (for "not "). It also highlights the repetition of identifiers: ``data if data``, ``files if files``, etc.
When written using the ``None`` coalescing operator, the sample reads::
data = data ?? [] files = files ?? [] headers = headers ?? {} params = params ?? {} hooks = hooks ?? {}
References ==========
.. [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] Associated scripts (https://github.com/python/peps/tree/master/pep-0505/)
Copyright =========
This document has been placed in the public domain.

On 2018-07-18 18:43, Steve Dower wrote:
Possibly this is exactly the wrong time to propose the next big syntax change, since we currently have nobody to declare on it, but since we're likely to argue for a while anyway it probably can't hurt (and maybe this will become the test PEP for whoever takes the reins?).
FWIW, Guido had previously indicated that he was generally favourable towards most of this proposal, provided we could figure out coherent semantics. Last time we tried, that didn't happen, so this time I've made the semantics much more precise, have implemented and verified them, and made much stronger statements about why we are proposing these.
Additional thanks to Mark Haase for writing most of the PEP. All the fair and balanced parts are his - all the overly strong opinions are mine.
Also thanks to Nick Coghlan for writing PEPs 531 and 532 last time we went through this - if you're unhappy with "None" being treated as a special kind of value, I recommend reading those before you start repeating them.
There is a formatted version of this PEP at https://www.python.org/dev/peps/pep-0505/
My current implementation is at https://github.com/zooba/cpython/tree/pep-505 (though I'm considering removing some of the new opcodes I added and just generating more complex code - in any case, let's get hung up on the proposal rather than the implementation :) )
Let the discussions begin!
[snip]
Grammar changes
The following rules of the Python grammar are updated to read::
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' |
'^=' | '<<=' | '>>=' | '**=' | '//=' | '??=')
power: coalesce ['**' factor] coalesce: atom_expr ['??' factor] atom_expr: ['await'] atom trailer* trailer: ('(' [arglist] ')' | '[' subscriptlist ']' | '?[' subscriptlist ']' | '.' NAME | '?.' NAME)
The precedence is higher than I expected. I think of it more like 'or'. What is its precedence in the other languages?
Inserting the ``coalesce`` rule in this location ensures that expressions resulting in ``None`` are natuarlly coalesced before they are used in
Typo "natuarlly".
operations that would typically raise ``TypeError``. Like ``and`` and ``or`` the right-hand expression is not evaluated until the left-hand side is determined to be ``None``. For example::
a, b = None, None def c(): return None def ex(): raise Exception() (a ?? 2 ** b ?? 3) == a ?? (2 ** (b ?? 3)) (a * b ?? c // d) == a * (b ?? c) // d (a ?? True and b ?? False) == (a ?? True) and (b ?? False) (c() ?? c() ?? True) == True (True ?? ex()) == True (c ?? ex)() == c()
Augmented coalescing assignment only rebinds the name if its current value is ``None``. If the target name already has a value, the right-hand side is not evaluated. For example::
a = None b = '' c = 0 a ??= 'value' b ??= undefined_name c ??= shutil.rmtree('/') # don't try this at home, kids assert a == 'value' assert b == '' assert c == '0' and any(os.scandir('/'))
Wouldn't the last assertion fail, because c == 0?
[snip]

Thanks! Bit of discussion below about precedence, but thanks for spotting the typos.
On 18Jul2018 1318, MRAB wrote:
On 2018-07-18 18:43, Steve Dower wrote:
Grammar changes
The following rules of the Python grammar are updated to read::
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | '>>=' | '**=' | '//=' | '??=')
power: coalesce ['**' factor] coalesce: atom_expr ['??' factor] atom_expr: ['await'] atom trailer* trailer: ('(' [arglist] ')' | '[' subscriptlist ']' | '?[' subscriptlist ']' | '.' NAME | '?.' NAME)
The precedence is higher than I expected. I think of it more like 'or'. What is its precedence in the other languages?
Yes, I expected this to be the contentious part. I may have to add a bit of discussion.
Mostly, I applied intuition rather than copying other languages on precedence (and if you could go through my non-git history, you'd see I tried four other places ;) ). The most "obvious" cases were these::
a ?? 1 + b()
b ** a() ?? 2
In the first case, both "(a ?? 1) + b()" and "a ?? (1 + b())" make sense, so it's really just my own personal preference that I think it looks like the first. If you flip the operands to get "b() + a ?? 1" then you end up with either "b() + (a ?? 1)" or "(b() + a) ?? 1", then it's more obvious that the latter doesn't make any sense (why would __add__ return None?), and so binding more tightly than "+" helps write sensible expressions with fewer parentheses.
Similarly, I feel like "b ** (a() ?? 2)" makes more sense than "(b ** a()) ?? 2", where for the latter we would have to assume a __pow__ implementation that returns None, or one that handles being passed None without raising a TypeError.
Contrasting this with "or", it is totally legitimate for arithmetic operators to return falsey values.
As I open the text file to correct the typos, I see this is what I tried to capture with:
Inserting the ``coalesce`` rule in this location ensures that expressions resulting in ``None`` are naturally coalesced before they are used in operations that would typically raise ``TypeError``.
Take (2 ** a.b) ?? 0. The result of __pow__ is rarely going to be None, unless we train all the builtin types to do so (which, incidentally, I am not proposing and have no intention of proposing), whereas something like "2 ** coord?.exponent" attempting to call "2.__pow__(None)" seems comparatively likely. (Unfortunately, nobody writes code like this yet :) So there aren't any real-life examples. Originally I didn't include "??" in the proposal, but it became obvious in the examples that the presence of None-propagating operators ?. and ?[] just cause more pain without having the None-terminating operator ?? as well.)
Inserting the ``coalesce`` rule in this location ensures that expressions resulting in ``None`` are natuarlly coalesced before they are used in
Typo "natuarlly".
Thanks.
assert a == 'value' assert b == '' assert c == '0' and any(os.scandir('/'))
Wouldn't the last assertion fail, because c == 0?
Correct, another typo.
Cheers, Steve

On Wed, Jul 18, 2018 at 7:46 PM Steve Dower steve.dower@python.org wrote:
Possibly this is exactly the wrong time to propose the next big syntax change, since we currently have nobody to declare on it, but since we're likely to argue for a while anyway it probably can't hurt (and maybe this will become the test PEP for whoever takes the reins?).
FWIW, Guido had previously indicated that he was generally favourable towards most of this proposal, provided we could figure out coherent semantics. Last time we tried, that didn't happen, so this time I've made the semantics much more precise, have implemented and verified them, and made much stronger statements about why we are proposing these.
Additional thanks to Mark Haase for writing most of the PEP. All the fair and balanced parts are his - all the overly strong opinions are mine.
Also thanks to Nick Coghlan for writing PEPs 531 and 532 last time we went through this - if you're unhappy with "None" being treated as a special kind of value, I recommend reading those before you start repeating them.
There is a formatted version of this PEP at https://www.python.org/dev/peps/pep-0505/
My current implementation is at https://github.com/zooba/cpython/tree/pep-505 (though I'm considering removing some of the new opcodes I added and just generating more complex code - in any case, let's get hung up on the proposal rather than the implementation :) )
Let the discussions begin!
PEP: 505 Title: None-aware operators Version: $Revision$ Last-Modified: $Date$ Author: Mark E. Haase mehaase@gmail.com, Steve Dower steve.dower@python.org Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 18-Sep-2015 Python-Version: 3.8
Abstract
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.)
- The "``null``-aware index access" operator accesses an element of a
collection only if that collection is non-``null``. Otherwise it returns ``null``. (This is another type of "safe navigation" operator.)
This PEP proposes three ``None``-aware operators for Python, based on the definitions and other language's implementations of those above. Specifically:
- The "``None`` coalescing`` binary operator ``??`` returns the left
hand side if it evaluates to a value that is not ``None``, or else it evaluates
and
returns the right hand side. A coalescing ``??=`` augmented assignment operator is included.
- The "``None``-aware attribute access" operator ``?.`` evaluates the
complete expression if the left hand side evaluates to a value that is not ``None``
- The "``None``-aware indexing" operator ``?[]`` evaluates the complete expression if the left hand site evaluates to a value that is not
``None``
Syntax and Semantics
Specialness of ``None``
The ``None`` object denotes the lack of a value. For the purposes of these operators, the lack of a value indicates that the remainder of the expression also lacks a value and should not be evaluated.
A rejected proposal was to treat any value that evaluates to false in a Boolean context as not having a value. However, the purpose of these operators is to propagate the "lack of value" state, rather that the "false" state.
Some argue that this makes ``None`` special. We contend that ``None`` is already special, and that using it as both the test and the result of
these
operators does not change the existing semantics in any way.
See the `Rejected Ideas`_ section for discussion on the rejected
approaches.
Grammar changes
The following rules of the Python grammar are updated to read::
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' |
'^=' | '<<=' | '>>=' | '**=' | '//=' | '??=')
power: coalesce ['**' factor] coalesce: atom_expr ['??' factor] atom_expr: ['await'] atom trailer* trailer: ('(' [arglist] ')' | '[' subscriptlist ']' | '?[' subscriptlist ']' | '.' NAME | '?.' NAME)
Inserting the ``coalesce`` rule in this location ensures that expressions resulting in ``None`` are natuarlly coalesced before they are used in operations that would typically raise ``TypeError``. Like ``and`` and
``or``
the right-hand expression is not evaluated until the left-hand side is determined to be ``None``. For example::
a, b = None, None def c(): return None def ex(): raise Exception() (a ?? 2 ** b ?? 3) == a ?? (2 ** (b ?? 3)) (a * b ?? c // d) == a * (b ?? c) // d (a ?? True and b ?? False) == (a ?? True) and (b ?? False) (c() ?? c() ?? True) == True (True ?? ex()) == True (c ?? ex)() == c()
Augmented coalescing assignment only rebinds the name if its current value is ``None``. If the target name already has a value, the right-hand side is
not
evaluated. For example::
a = None b = '' c = 0 a ??= 'value' b ??= undefined_name c ??= shutil.rmtree('/') # don't try this at home, kids assert a == 'value' assert b == '' assert c == '0' and any(os.scandir('/'))
Adding new trailers for the other ``None``-aware operators ensures that
they
may be used in all valid locations for the existing equivalent operators, including as part of an assignment target (more details below). As the existing evaluation rules are not directly embedded in the grammar, we specify the required changes here.
Assume that the ``atom`` is always successfully evaluated. Each ``trailer`` is then evaluated from left to right, applying its own parameter (either its arguments, subscripts or attribute name) to produce the value for the next ``trailer``. Finally, if present, ``await`` is applied.
For example, ``await a.b(c).d[e]`` is currently parsed as ``['await', 'a', '.b', '(c)', '.d', '[e]']`` and evaluated::
_v = a _v = _v.b _v = _v(c) _v = _v.d _v = _v[e] await _v
When a ``None``-aware operator is present, the left-to-right evaluation may be short-circuited. For example, ``await a?.b(c).d?[e]`` is evaluated::
_v = a if _v is not None: _v = _v.b _v = _v(c) _v = _v.d if _v is not None: _v = _v[e] await _v
.. note:: ``await`` will almost certainly fail in this context, as it would in the case where code attempts ``await None``. We are not proposing to add a ``None``-aware ``await`` keyword here, and merely include it in this example for completeness of the specification, since the
``atom_expr``
grammar rule includes the keyword. If it were in its own rule, we
would have never mentioned it.
Parenthesised expressions are handled by the ``atom`` rule (not shown above), which will implicitly terminate the short-circuiting behaviour of the
above
transformation. For example, ``(a?.b ?? c).d?.e`` is evaluated as::
# a?.b _v = a if _v is not None: _v = _v.b # ... ?? c if _v is None: _v = c # (...).d?.e _v = _v.d if _v is not None: _v = _v.e
When used as an assignment target, the ``None``-aware operations may only
be
used in a "load" context. That is, ``a?.b = 1`` and ``a?[b] = 1`` will
raise
``SyntaxError``. Use earlier in the expression (``a?.b.c = 1``) is permitted, though unlikely to be useful unless combined with a coalescing operation::
(a?.b ?? d).c = 1
Examples
This section presents some examples of common ``None`` patterns and shows what conversion to use ``None``-aware operators may look like.
Standard Library
Using the ``find-pep505.py`` script[3]_ an analysis of the Python 3.7 standard library discovered up to 678 code snippets that could be replaced with use of one of the ``None``-aware operators::
$ find /usr/lib/python3.7 -name '*.py' | xargs python3.7
find-pep505.py
<snip> Total None-coalescing `if` blocks: 449 Total [possible] None-coalescing `or`: 120 Total None-coalescing ternaries: 27 Total Safe navigation `and`: 13 Total Safe navigation `if` blocks: 61 Total Safe navigation ternaries: 8
Some of these are shown below as examples before and after converting to use the new operators.
From ``bisect.py``::
def insort_right(a, x, lo=0, hi=None): # ... if hi is None: hi = len(a) # ...
After updating to use the ``??=`` augmented assignment statement::
def insort_right(a, x, lo=0, hi=None): # ... hi ??= len(a) # ...
From ``calendar.py``::
encoding = options.encoding if encoding is None: encoding = sys.getdefaultencoding() optdict = dict(encoding=encoding, css=options.css)
After updating to use the ``??`` operator::
optdict = dict(encoding=encoding ?? sys.getdefaultencoding(), css=options.css)
From ``dis.py``::
def _get_const_info(const_index, const_list): argval = const_index if const_list is not None: argval = const_list[const_index] return argval, repr(argval)
After updating to use the ``?[]`` and ``??`` operators::
def _get_const_info(const_index, const_list): argval = const_list?[const_index] ?? const_index return argval, repr(argval)
From ``inspect.py``::
for base in object.__bases__: for name in getattr(base, "__abstractmethods__", ()): value = getattr(object, name, None) if getattr(value, "__isabstractmethod__", False): return True
After updating to use the ``?.`` operator (and deliberately not converting to use ``any()``)::
for base in object.__bases__: for name in base?.__abstractmethods__ ?? (): if object?.name?.__isabstractmethod__: return True
From ``os.py``::
if entry.is_dir(): dirs.append(name) if entries is not None: entries.append(entry) else: nondirs.append(name)
After updating to use the ``?.`` operator::
if entry.is_dir(): dirs.append(name) entries?.append(entry) else: nondirs.append(name)
jsonify
This example is from a Python web crawler that uses the Flask framework as its 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 does not have a native way to represent a ``datetime``, so the server's contract states that any non-``null`` date is represented as an ISO-8601 string.
Without knowing the exact semantics of the ``first_seen`` and
``last_seen``
attributes, it is impossible to know whether the attribute can be safely
or
performantly accessed multiple times.
One way to fix this code is to replace each conditional expression with an explicit value assignment and 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_) first_seen_dt = site.first_seen if first_seen_dt is None: first_seen = None else: first_seen = first_seen_dt.isoformat() last_seen_dt = site.last_seen if last_seen_dt is None: last_seen = None else: last_seen = last_seen_dt.isoformat() return jsonify( first_seen=first_seen, id=site.id, is_active=site.is_active, last_seen=last_seen, url=site.url.rstrip('/') )
This adds ten lines of code and four new code paths to the function, dramatically increasing the apparent complexity. Rewriting using the ``None``-aware attribute operator results in shorter code with more clear intent::
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('/') )
Grab
The next example is from a Python scraping library called `Grab <
https://github.com/lorien/grab/blob/4c95b18dcb0fa88eeca81f5643c0ebfb114bf728...
ab/upload.py>`_::
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
This example contains several good examples of needing to provide default values. Rewriting to use conditional expressions reduces the overall lines of code, but does not necessarily improve readability::
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.
Rewriting using the ``None`` coalescing operator::
class BaseUploadObject(object): def find_content_type(self, filename): ctype, encoding = mimetypes.guess_type(filename) return ctype ?? 'application/octet-stream' class UploadContent(BaseUploadObject): def __init__(self, content, filename=None, content_type=None): self.content = content self.filename = filename ?? self.get_random_filename() self.content_type = content_type ??
self.find_content_type(self.filename)
class UploadFile(BaseUploadObject): def __init__(self, path, filename=None, content_type=None): self.path = path self.filename = filename ?? os.path.split(path)[1] self.content_type = content_type ??
self.find_content_type(self.filename)
This syntax has an intuitive ordering of the operands. In ``find_content_type``, for example, the preferred value ``ctype`` appears before the fallback value. The terseness of the syntax also makes for fewer lines of code and less code to visually parse, and reading from left-to-right and top-to-bottom more accurately follows the execution flow.
Rejected Ideas
The first three ideas in this section are oft-proposed alternatives to treating ``None`` as special. For further background on why these are rejected, see their treatment in `PEP 531 https://www.python.org/dev/peps/pep-0531/`_ and `PEP 532 https://www.python.org/dev/peps/pep-0532/`_ and the associated discussions.
No-Value Protocol
The operators could be generalised to user-defined types by defining a protocol to indicate when a value represents "no value". Such a protocol may be a dunder method ``__has_value__(self)` that returns ``True`` if the value should be treated as having a value, and ``False`` if the value should be treated as no value.
With this generalization, ``object`` would implement a dunder method equivalent to this::
def __has_value__(self): return True
``NoneType`` would implement a dunder method equivalent to this::
def __has_value__(self): return False
In the specification section, all uses of ``x is None`` would be replaced with ``not x.__has_value__()``.
This generalization would allow for domain-specific "no-value" objects to
be
coalesced just like ``None``. For example the ``pyasn1`` package has a
type
called ``Null`` that represents an ASN.1 ``null``::
>>> from pyasn1.type import univ >>> univ.Null() ?? univ.Integer(123) Integer(123)
Similarly, values such as ``math.nan`` and ``NotImplemented`` could be treated as representing no value.
However, the "no-value" nature of these values is domain-specific, which means they *should* be treated as a value by the language. For example, ``math.nan.imag`` is well defined (it's ``0.0``), and so short-circuiting ``math.nan?.imag`` to return ``math.nan`` would be incorrect.
As ``None`` is already defined by the language as being the value that represents "no value", and the current specification would not preclude switching to a protocol in the future (though changes to built-in objects would not be compatible), this idea is rejected for now.
Boolean-aware operators
This suggestion is fundamentally the same as adding a no-value protocol, and so the discussion above also applies.
Similar behavior to the ``??`` operator can be achieved with an ``or`` expression, however ``or`` checks whether its left operand is false-y and not specifically ``None``. This approach is attractive, as it requires fewer changes to the language, but ultimately does not solve the underlying problem correctly.
Assuming the check is for truthiness rather than ``None``, there is no longer a need for the ``??`` operator. However, applying this check to the ``?.``
and
``?[]`` operators prevents perfectly valid operations applying
Consider the following example, where ``get_log_list()`` may return
either a
list containing current log messages (potentially empty), or ``None`` if logging is not enabled::
lst = get_log_list() lst?.append('A log message')
If ``?.`` is checking for true values rather than specifically ``None`` and the log has not been initialized with any items, no item will ever be appended. This violates the obvious intent of the code, which is to append an item. The ``append`` method is available on an empty list, as are all other list methods, and there is no reason to assume that these members should not be used because the list is presently empty.
Further, there is no sensible result to use in place of the expression. A normal ``lst.append`` returns ``None``, but under this idea ``lst?.append`` may result in either ``[]`` or ``None``, depending on the value of ``lst``. As with the examples in the previous section, this makes no sense.
As checking for truthiness rather than ``None`` results in apparently
valid
expressions no longer executing as intended, this idea is rejected.
Exception-aware operators
Arguably, the reason to short-circuit an expression when ``None`` is encountered is to avoid the ``AttributeError`` or ``TypeError`` that would be raised under normal circumstances. As an alternative to testing for ``None``, the ``?.`` and ``?[]`` operators could instead handle ``AttributeError`` and
``TypeError``
raised by the operation and skip the remainder of the expression.
This produces a transformation for ``a?.b.c?.d.e`` similar to this::
_v = a try: _v = _v.b except AttributeError: pass else: _v = _v.c try: _v = _v.d except AttributeError: pass else: _v = _v.e
One open question is which value should be returned as the expression when an exception is handled. The above example simply leaves the partial result, but this is not helpful for replacing with a default value. An alternative would be to force the result to ``None``, which then raises the question as to why ``None`` is special enough to be the result but not special enough to be
the
test.
Secondly, this approach masks errors within code executed implicitly as part of the expression. For ``?.``, any ``AttributeError`` within a property or ``__getattr__`` implementation would be hidden, and similarly for ``?[]`` and ``__getitem__`` implementations.
Similarly, simple typing errors such as ``{}?.ietms()`` could go
unnoticed.
Existing conventions for handling these kinds of errors in the form of the ``getattr`` builtin and the ``.get(key, default)`` method pattern established by ``dict`` show that it is already possible to explicitly use this
behaviour.
As this approach would hide errors in code, it is rejected.
``None``-aware Function Call
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. It might be written as ``foo?()``, where ``foo`` is only called if it is not None.
This has been deferred on the basis of the proposed operators being
intended
to aid traversal of partially populated hierarchical data structures,
*not*
for traversal of arbitrary class hierarchies. This is reflected in the
fact
that none of the other mainstream languages that already offer this syntax have found it worthwhile to support a similar syntax for optional function invocations.
A workaround similar to that used by C# would be to write ``maybe_none?.__call__(arguments)``. If the callable is ``None``, the expression will not be evaluated. (The C# equivalent uses ``?.Invoke()`` on its callable type.)
``?`` 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 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``::
class NoneQuestion(): def __call__(self, *args, **kwargs): return self def __getattr__(self, name): return self def __getitem__(self, key): return self
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.
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.
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?::
>>> 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.
Built-in ``maybe``
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".
A Python package called `pymaybe https://pypi.org/p/pymaybe/`_ provides
a
rough approximation. The documentation shows the following example::
>>> maybe('VALUE').lower() 'value' >>> maybe(None).invalid().method().or_else('unknown') 'unknown'
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.
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.
The idea of adding a builtin ``maybe`` type to enable this scenario is rejected.
Just use a conditional expression
Another common way to initialize default values is to use the ternary operator. Here is an excerpt from the popular `Requests package <
https://github.com/kennethreitz/requests/blob/14a555ac716866678bf17e43e23230...
a8149f5/requests/models.py#L212>`_::
data = [] if data is None else data files = [] if files is None else files headers = {} if headers is None else headers params = {} if params is None else params hooks = {} if hooks is None else hooks
This particular formulation has the undesirable effect of putting the operands in an unintuitive order: the brain thinks, "use ``data`` if possible and
use
``[]`` as a fallback," but the code puts the fallback *before* the
preferred
value.
The author of this package could have written it like this instead::
data = data if data is not None else [] files = files if files is not None else [] headers = headers if headers is not None else {} params = params if params is not None else {} hooks = hooks if hooks is not None else {}
This ordering of the operands is more intuitive, but it requires 4 extra characters (for "not "). It also highlights the repetition of identifiers: ``data if data``, ``files if files``, etc.
When written using the ``None`` coalescing operator, the sample reads::
data = data ?? [] files = files ?? [] headers = headers ?? {} params = params ?? {} hooks = hooks ?? {}
References
.. [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] Associated scripts (https://github.com/python/peps/tree/master/pep-0505/)
Copyright
This document has been placed in the public domain. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
With all due respect (and I am sorry for being “vocal” about a PEP once again) I find this simply ugly. To me this basically doesn’t look like python anymore, so a strong -1 from me.
On a more general note, I think Python is evolving too quickly. If I try to envision a python code which uses all the syntax which was added in the last 2-3 releases (say function annotations, assignment expressions, type hints and now this) the picture I get is something which hardly resembles the good old python I am used to. I don’t think we can have a clear sense of how all these syntax changes put together will impact the future python ecosystem when people will take them for granted and massively use them, since as of now they are largely untested. With python 2 still being the most used version I think we should go the opposite direction like we did with u”” literals, possibly freeze the syntax and provide more palatable “carrots” as an incentive for the migration (speed and stdlib improvements come to mind). Keep adding new syntax will just keep the two versions even more far apart, make the language harder to learn and harder to read because the same thing can be expressed by using too many different styles.
My 2 cents

On Thu, Jul 19, 2018 at 9:55 AM, Giampaolo Rodola' g.rodola@gmail.com wrote: [ please trim quotes, you just quoted the entire PEP in your post ]
With all due respect (and I am sorry for being “vocal” about a PEP once again) I find this simply ugly. To me this basically doesn’t look like python anymore, so a strong -1 from me.
I'd love to hear an explanation of WHY this doesn't look like Python any more. For instance, is the + operator somehow wrong for Python, and it should have been the word "add"? People complain about proposals with words like these, but the best explanation I've ever had is "well, Python uses words and this proposal uses punctuation".
Personally, I'm +0 on this. It'd be a few small wins here and there, nothing huge, and I could easily live without it; but it's something that I know some people will love.
ChrisA

On Thu, Jul 19, 2018 at 2:06 AM Chris Angelico rosuav@gmail.com wrote:
With all due respect (and I am sorry for being “vocal” about a PEP once again) I find this simply ugly. To me this basically doesn’t look like python anymore, so a strong -1 from me.
I'd love to hear an explanation of WHY this doesn't look like Python any more.
Because it looks like Perl.
For instance, is the + operator somehow wrong for Python, and it should have been the word "add"?
The meaning of "+" is obvious to anybody, including non programmers. "?" is arbitrary so you cannot guess what it does or means, especially when it can be spelled in so many different forms (?, ??, a?.b, ??=, ...), each form requiring a different mental replacement. Use this and := on the same line and the reader will practically be reading another language.
[ please trim quotes, you just quoted the entire PEP in your post ]
Ouch! Sorry about that.

Chris Angelico wrote:
I'd love to hear an explanation of WHY this doesn't look like Python any more. For instance, is the + operator somehow wrong for Python, and it should have been the word "add"?
There's a very long tradition of using the symbol "+" to represent addition, so it's something most people are familiar with. There's no such tradition for the new operators being proposed.

On Thu, Jul 19, 2018 at 4:06 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
Chris Angelico wrote:
I'd love to hear an explanation of WHY this doesn't look like Python any more. For instance, is the + operator somehow wrong for Python, and it should have been the word "add"?
There's a very long tradition of using the symbol "+" to represent addition, so it's something most people are familiar with. There's no such tradition for the new operators being proposed.
Okay. What about bitwise operators, then? They don't have centuries of mathematical backing to support them, yet it isn't considered "unpythonic" to have &|^~ peppering our code. Judging by the current levels of backlash against symbolic operators, it would have been better to use "more explicit" function calls for all bitwise operations. Coalescing None to a value is _at least_ as common as performing bit manipulations in integers.
ChrisA

On Thu, 19 Jul 2018 19:11:33 +1000 Chris Angelico rosuav@gmail.com wrote:
On Thu, Jul 19, 2018 at 4:06 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
Chris Angelico wrote:
I'd love to hear an explanation of WHY this doesn't look like Python any more. For instance, is the + operator somehow wrong for Python, and it should have been the word "add"?
There's a very long tradition of using the symbol "+" to represent addition, so it's something most people are familiar with. There's no such tradition for the new operators being proposed.
Okay. What about bitwise operators, then? They don't have centuries of mathematical backing to support them, yet it isn't considered "unpythonic" to have &|^~ peppering our code.
They have decades of widespread presence in other programming languages, though.
Coalescing None to a value is _at least_ as common as performing bit manipulations in integers.
Certainly, but spelling that as a "?*" operator is a syntactical novelty.
Consider that for the ternary operator, Python chose "B if A else C" over "A ? B : C", even though the latter had precedent in several languages.
Regards
Antoine.

On Thu, Jul 19, 2018 at 11:22 AM Antoine Pitrou solipsis@pitrou.net wrote:
On Thu, 19 Jul 2018 19:11:33 +1000 Chris Angelico rosuav@gmail.com wrote:
On Thu, Jul 19, 2018 at 4:06 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
Chris Angelico wrote:
I'd love to hear an explanation of WHY this doesn't look like Python any more. For instance, is the + operator somehow wrong for Python, and it should have been the word "add"?
There's a very long tradition of using the symbol "+" to represent addition, so it's something most people are familiar with. There's no such tradition for the new operators being proposed.
Okay. What about bitwise operators, then? They don't have centuries of mathematical backing to support them, yet it isn't considered "unpythonic" to have &|^~ peppering our code.
They have decades of widespread presence in other programming languages, though.
Coalescing None to a value is _at least_ as common as performing bit manipulations in integers.
Certainly, but spelling that as a "?*" operator is a syntactical novelty.
Consider that for the ternary operator, Python chose "B if A else C" over "A ? B : C", even though the latter had precedent in several languages.
Regards
Antoine.
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
^ Agreed. To me even bitwise operators "feel" a bit weird when using them but fortunately they are rare. On the other hand "?" has the potential to be used (and abused) much more than bitwise operators. Also I don't consider "since we have X then let's add Y" a valid enough reasoning.

On Thu, Jul 19, 2018, 5:12 AM Chris Angelico rosuav@gmail.com wrote:
On Thu, Jul 19, 2018 at 4:06 PM, Greg Ewing greg.ewing@canterbury.ac.nz wrote:
There's a very long tradition of using the symbol "+" to represent addition, so it's something most people are familiar with. There's no such tradition for the new operators being proposed.
Okay. What about bitwise operators, then? They don't have centuries of mathematical backing to support them, yet it isn't considered "unpythonic" to have &|^~ peppering our code.
I have quite literally NEVER seem Python code with much use of the bitwise operators. I guess the closest I've come is in some NumPy and Pandas code where filters cannot use plain 'and' and ' or' but every student would find it more intuitive if they could. E.g.
myDf[(myDf.field1 > 4) & (myDf.field2 < 2)]
Everyone still gets tripped up by the need for those parentheses because of operator precedence. But this already yells or "special domain" rather than "plain Python".
Indeed, it not uncommon or unreasonable to recommend using np.bitwise_and() to avoid those confusing operators.
In the case where bitwise masking is used in its C manner, the code screams special domain even more loudly. It definitely feels strongly unPythonic, but it's often a reasonable compromise for dealing with just a little bit of binary data without having to write a C extension.
I would ALWAYS want the code that used bitwise operators wrapped in a separate function that most users and developers didn't need to look at, but rather they'd call a more Pythonic API for the overall operation.
A huge difference is that bitwise operators do something you simply cannot do other ways in Python at all. None-aware operators *at best* allow you to write something with an existing straightforward approach using a couple fewer lines. I think the result is ALWAYS less clear to read. Code golf is an anti-goal in Python.

On 2018-07-19 02:11, Chris Angelico wrote:
Okay. What about bitwise operators, then? They don't have centuries of mathematical backing to support them, yet it isn't considered "unpythonic" to have &|^~ peppering our code. Judging by the current levels of backlash against symbolic operators, it would have been better to use "more explicit" function calls for all bitwise operations. Coalescing None to a value is_at least_ as common as performing bit manipulations in integers.
The use of & to mean "and" does indeed have centuries of backing to support it. The other operators, and the use of & to specifically mean bitwise-and, are newer, but still go back decades in other programming languages. Moreover, the fact that these are bitwise versions of more general logical operations (conjunction, disjunction, etc.) means that they can be overridden for custom types in a way that is still intuitive. For instance, sets override & to mean "intersection" because it means "everything that is in set A AND in set B", and similarly for | and ^.
So basically I do not regard these operators as bitwise operators. They are symbols standing for logical operations. Their default implementations on built-in numeric types do bitwise operations, but that is not what the symbols "mean". Their meaning is more general and bitwise operations are one reasonable narrowing of that meaning for use in the context of builtin numeric types, but there are many other uses of these symbols that are equally consistent with their meaning.
To me, this notion of symbols with an abstract meaning which can be narrowed for particular types by overriding magic methods is one of Python's greatest strengths. + doesn't mean "add two numbers", it means "add", and that makes sense for many types, and they can override __add__ to define sensible behavior for their own purposes. [] doesn't mean "get list item" it means "get", and types can override __getitem__ to define sensible behavior for their own purposes.
As far as I can see, these null-coalescing operators would break that model. The PEP doesn't seem to provide for a "real" magic method allowing users to override the actual behavior of the method. (You can only override __has_value__ to hook into it, but not define by fiat what A ?? B does, as you can with other operators.) And I think the reason for this is that the operator itself is too specific, much more specific in semantics than other operators. (I had similar doubts about adding the matrix-multiplication operator @.)
People keep saying that this null-coalescing behavior is so common and useful, etc., but that hasn't been my experience at all. In my experience, the desire to shortcut this kind of logic is more often a sign of corner-cutting and insufficiently specified data formats, and is likely to cause bugs later on. Eventually it has to actually matter whether something is None or not, and these operators just kick that can down the road. In terms of their abstract meaning, they are not remotely close to as common or useful as operators like & and |.

On Thu, Jul 19, 2018 at 11:37 AM Brendan Barnwell brenbarn@brenbarn.net wrote:
As far as I can see, these null-coalescing operators would break
that model. The PEP doesn't seem to provide for a "real" magic method allowing users to override the actual behavior of the method. (You can only override __has_value__ to hook into it, but not define by fiat what A ?? B does, as you can with other operators.) And I think the reason for this is that the operator itself is too specific, much more specific in semantics than other operators. (I had similar doubts about adding the matrix-multiplication operator @.)
It is just as specific as the `is` operator, and for the same reason.
Elazar

On 2018-07-19 11:43, Elazar wrote:
On Thu, Jul 19, 2018 at 11:37 AM Brendan Barnwell <brenbarn@brenbarn.net mailto:brenbarn@brenbarn.net> wrote:
As far as I can see, these null-coalescing operators would break that model. The PEP doesn't seem to provide for a "real" magic method allowing users to override the actual behavior of the method. (You can only override __has_value__ to hook into it, but not define by fiat what A ?? B does, as you can with other operators.) And I think the reason for this is that the operator itself is too specific, much more specific in semantics than other operators. (I had similar doubts about adding the matrix-multiplication operator @.)
It is just as specific as the `is` operator, and for the same reason.
What reason is that?
The "is" operator is the ONLY operator that is un-overridable in this way, and that's because by its nature it is concerned with object identity, which we don't want people to be able to redefine. (Okay, the logical operators like "and" and "or" are also un-overridable, but that's not because we don't want people to be able to override them, it's because it's hard to figure out a way to do it practically without giving up short-circuiting.)
From my perspective "A if A is None else B" is just not remotely close to being as special as "A is B". If we didn't have the "is" operator there would be no way to do what it does. Everything that these new operators do can already be done.

Let me just address this point:
2018-07-19 20:36 GMT+02:00 Brendan Barnwell brenbarn@brenbarn.net:
As far as I can see, these null-coalescing operators would break
that model. The PEP doesn't seem to provide for a "real" magic method allowing users to override the actual behavior of the method. (You can only override __has_value__ to hook into it, but not define by fiat what A ?? B does, as you can with other operators.) And I think the reason for this is that the operator itself is too specific, much more specific in semantics than other operators. (I had similar doubts about adding the matrix-multiplication operator @.)
I think the actual reason is that it is a short-cutting operator, and none of the shortcutting operators (and, or, if/else) have an associated method. They cannot have, since they induce a non-standard evaluation order, hence their effect cannot be emulated with a method invocation.
Stephan

On Fri, Jul 20, 2018 at 4:45 AM, Stephan Houben stephanh42@gmail.com wrote:
Let me just address this point:
2018-07-19 20:36 GMT+02:00 Brendan Barnwell brenbarn@brenbarn.net:
As far as I can see, these null-coalescing operators would break
that model. The PEP doesn't seem to provide for a "real" magic method allowing users to override the actual behavior of the method. (You can only override __has_value__ to hook into it, but not define by fiat what A ?? B does, as you can with other operators.) And I think the reason for this is that the operator itself is too specific, much more specific in semantics than other operators. (I had similar doubts about adding the matrix-multiplication operator @.)
I think the actual reason is that it is a short-cutting operator, and none of the shortcutting operators (and, or, if/else) have an associated method. They cannot have, since they induce a non-standard evaluation order, hence their effect cannot be emulated with a method invocation.
Also for the same reason that the 'is' operator doesn't have a corresponding dunder. You can't ask an object if it's the same object as another; it either is or is not, intrinsically. It's the same here; it either is None, or is not None, intrinsically. These new operators have very clearly defined semantics involving the special object None, and they short-circuit; two good reasons NOT to have them overridable.
ChrisA

Thanks everyone for the feedback and discussion so far. I want to address some of the themes, so apologies for not quoting individuals and for doing this in one post instead of twenty.
------
* "It looks like line noise"
Thanks for the feedback. There's nothing constructive for me to take from this.
* "I've never needed this"
Also not very actionable, but as background I'll say that this was exactly my argument against adding them to C#. But my coding style has adapted to suit (for example, I'm more likely to use "null" as a default value and have a single function implementation than two mostly-duplicated overloads).
* "It makes it more complex" * "It's harder to follow the flow"
Depends on your measure of complexity. For me, I prioritise "area under the indentation" as my preferred complexity metric (more lines*indents == more complex), as well as left-to-right reading of each line (more random access == more complex). By these measures, ?. significantly reduces the complexity over any of the current or future alternatives::
def f(a=None): name = 'default' if a is not None: user = a.get_user() if user is not None: name = user.name print(name)
def f(a=None): if a is not None: user = a.get_user() name = user.name if user is not None else 'default' print(name) else print('default')
def f(a=None): user = a.get_user() if a is not None else None name = user.name if user is not None else 'default' print(name)
def f(a=None): print(user.name if (user := a.get_user() if a is not None else None) is not None else 'default')
def f(a=None): print(a?.get_user()?.name ?? 'none')
* "We have 'or', we don't need '??'"
Nearly-agreed, but I think the tighter binding on ?? makes it more valuable and tighter test make it valuable in place of 'or'.
For example, compare:
a ** b() or 2 # actual: (a ** b()) or 2 a ** b() ?? 2 # proposed: a ** (b() ?? 2)
In the first, the presence of 'or' implies that either b() or __pow__(a, b()) could return a non-True value. This is correct (it could return 0 if a == 0). And the current precedence results in the result of __pow__ being used for the check.
In the second one, the presence of the '??' implies that either b() or __pow__(a, b()) could return None. The latter should never happen, and so the choices are to make the built-in types propagate Nones when passed None (uhh... no) or to make '??' bind to the closer part of the expression.
(If you don't think it's likely enough that a function could return [float, None], then assume 'a ** b?.c ?? 2' instead.)
* "We could have '||', we don't need '??'"
Perhaps, though this is basically just choosing the bikeshed colour. In the absence of a stronger argument, matching existing languages equivalent operators instead of operators that do different things in those languages should win.
* "We could have 'else', we don't need '??'"
This is the "a else 'default'" rather than "a ?? 'default'" proposal, which I do like the look of, but I think it will simultaneously mess with operator precedence and also force me to search for the 'if' that we actually need to be comparing "(a else 'default')" vs. "a ?? 'default'"::
x = a if b else c else d x = a if (b else c) else d x = a if b else (c else d)
* "It's not clear whether it's 'is not None' or 'hasattr' checks"
I'm totally sympathetic to this. Ultimately, like everything else, this is a concept that has to be taught/learned rather than known intrinsically.
The main reasons for not having 'a?.b' be directly equivalent to getattr(a, 'b', ???) is that you lose the easy ability to find typos, and we also already have the getattr() approach.
(Aside: in this context, why should the result be 'None' if an attribute is missing? For None, the None value propagates (getattr(a, 'b', a)), while for falsies you could argue the same thing applies. But for a silently handled AttributeError? You still have to make the case that None is special here, just special as a return value vs. special as a test.)
* "The semantics of this example changed from getattr() with ?."
Yes, this was a poor example. On re-reading, all of the checks are indeed looking for optional attributes, rather than looking them up on optional targets. I'll find a better one (I've certainly seen and/or written code like this that was intended to avoid crashing on None, but I stopped my search of the stdlib too soon after finding this example).
* "Bitwise operators"
Uh... yeah. Have fun over there :)
* "Assumes the only falsie ever returned [in some context] is None"
I argue that it assumes the only falsie you want to replace with a different value is None. In many cases, I'd expect the None to be replaced with a falsie of the intended type:
x = maybe_get_int() ?? 0 y = maybe_get_list() ?? []
Particularly for the second case, if you are about to mutate the list, then it could be very important that you don't replace the provided reference with your own, just because it happens to be empty right now (this is the logging example in the PEP). Supporting a default value in the get() calls is obviously a better way to do this though, provided you have the ability to modify the API.
But for me, the ?? operator makes its strongest case when you are getting attributes from potentially None values:
x = maybe_get()?.get_int() ?? 0 y = maybe_get()?.get_list() ?? []
In this case, using 'or' may replace an intentionally falsie value with your own, while using a default parameter is still going to leave you with None if the first maybe_get() returned nothing.
"?." without "??" feels like a trap, while "??" without "?." feels largely unnecessary. But both together lets you turn many lines of code into a much shorter snippet that reads left-to-right and lets you assume success until you reach the "??". That, for me, is peak readability.

On Thu, Jul 19, 2018 at 5:29 PM Steve Dower steve.dower@python.org wrote:
- "It makes it more complex"
- "It's harder to follow the flow"
Depends on your measure of complexity. For me, I prioritise "area under the indentation" as my preferred complexity metric (more lines*indents == more complex), as well as left-to-right reading of each line (more random access == more complex). By these measures, ?. significantly reduces the complexity over any of the current or future alternatives::
def f(a=None): name = 'default' if a is not None: user = a.get_user() if user is not None: name = user.name print(name)
You left one out, I think, that looks decent. I converted the print to a return, because I think it's more common to return than print.
def f(a=None): try: return a.get_user().name except AttributeError: return 'default'
def f(a=None):
print(a?.get_user()?.name ?? 'none')

Steve Dower writes:
But for me, the ?? operator makes its strongest case when you are getting attributes from potentially None values:
x = maybe_get()?.get_int() ?? 0 y = maybe_get()?.get_list() ?? []
In this case, using 'or' may replace an intentionally falsie value with your own,
I wish the PEP's examples had such cases marked and explained. I didn't understand the context for most examples (ie, *why* the left operand could be None, and why that needed to be distinguished from "intentionally falsie values").
In the "find_content_type" example, None is just a way of saying "oops, you handle it" that is less "cute" than "". "or" is far more resilient in that context than "??" would be. I am a mail geek, and *much* more likely to encounter that code than the others. I would be distressed if the email module or Mailman grew a lot of "??". Email is a domain where resilience really matters because you can receive arbitrary data over SMTP, there's no provision for rejecting garbage, and all too often you do get garbage. :-/
while using a default parameter is still going to leave you with None if the first maybe_get() returned nothing.
"?." without "??" feels like a trap, while "??" without "?." feels largely unnecessary. But both together lets you turn many lines of code into a much shorter snippet that reads left-to-right and lets you assume success until you reach the "??".
I find that pretty persuasive. Perhaps that should be added to PEP 8.
I would advocate write those
x = maybe_get() ?. get_int() ?? 0 y = maybe_get() ?. get_list() ?? []
to emphasize that this is an operator that "does something" to the operand, rather than "just" extracting an attribute. (No, I'm not going to try to make that more precise, so feel free to ignore.) I'm not sure about the ?[ operator:
x = a?[b] ?? c x = a ?[b] ?? c x = a ?[ b] ?? c x = a ?[ b ] ?? c
I guess I like the second line best (and
x = a?[b]??c
is right out, of course).
I do think Terry Reedy's criticism that in many cases having maybe_get() return None rather than an appropriate default object is bad API design may be important. I suspect that the "a ?. b ?? c" pattern will discourage refactoring of that code in favor of writing short workarounds.

On Thu, 19 Jul 2018 at 14:29 Steve Dower steve.dower@python.org wrote:
[SNIP]
- "We could have 'else', we don't need '??'"
This is the "a else 'default'" rather than "a ?? 'default'" proposal, which I do like the look of, but I think it will simultaneously mess with operator precedence and also force me to search for the 'if' that we actually need to be comparing "(a else 'default')" vs. "a ?? 'default'"::
x = a if b else c else d x = a if (b else c) else d x = a if b else (c else d)
The searching for the 'if' also throws me with this proposal. I think someone else proposed `A unless B` but I always prefer knowing upfront what I'm working with, not later by having to read farther along.
Personally, if the `A?? B` is the part people like but hate the syntax then my vote would go to `A otherwise B` since it's unambiguous, the case you care about the state of comes first, and it doesn't trip your brain up looking for 'if'. :)

my vote would go to `A otherwise B` since it's unambiguous, the case you care about the state of comes first, and it doesn't trip your brain up looking for 'if'. :)
And I’d hope “otherwise” is a rare variable name :-)
- CHB
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

Coming from the @ side (I was strong +1 on this), I have troubles seeing the real benefits from ?? (And even more from associates): did we really have long and complex expressions where the compactness of an operator would help?
Operators are inherently obscure (except for those that are learnt in elementary school), but they help when you combine multiple operators and benefit from their precedence rules. There you can benefit from them, even if explicit it's better than implicit... It was the case for @, and even with proofs of long formulas, and a history of matrix algebra from hundreds of years before computing science, the resistance was strong: a majority of non-numpy users resisted it, saying a function matmul(A,B) was good enough and A@B would bring nothing. It was eventually accepted, 7 or so years after the initial proposal, through another PEP, when relative weight of numpy community was probably larger. So i'd like to see examples of long expressions that would really benefit groin using an very specific operator. At this point, the explicitness of "a if a is not None else []" wins, by a long shot...

On 2018-07-19 02:11, Chris Angelico wrote:
-snip- As far as I can see, these null-coalescing operators would break that model. The PEP doesn't seem to provide for a "real" magic method allowing users to override the actual behavior of the method. (You can only override __has_value__ to hook into it, but not define by fiat what A ?? B does, as you can with other operators.) And I think the reason for this is that the operator itself is too specific, much more specific in semantics than other operators. (I had similar doubts about adding the matrix-multiplication operator @.)
People keep saying that this null-coalescing behavior is so common
and useful, etc., but that hasn't been my experience at all. In my experience, the desire to shortcut this kind of logic is more often a sign of corner-cutting and insufficiently specified data formats, and is likely to cause bugs later on. Eventually it has to actually matter whether something is None or not, and these operators just kick that can down the road. In terms of their abstract meaning, they are not remotely close to as common or useful as operators like & and |.
Fully agree with you on this, so -1 on those new operators. I like operators (I was behind one of the early proposals for matmul operator (even proposed a bunch of others, for orthogonality purpose: element-wise/object-as-a-whole is a dichotomy that can happen for other things that multiplication), but they need to benefit from operators specifics: precendence order, tradition meaning direct translation of formula/recipies already written with a similar notation, and be generic enough to allow productive use of overloading (instead of confusing use).
Matmul have this (with possible exception for overloading, but they are quite a few other mathematical entities where you want to have more than 1 multiplication, beside matrices).
Those new operators, not so much, the only benefit is more compact notation of something that is already quite compact (A if A is not None else B) and very explicit (it reads like pseudocode, which is a huge strengh of python). ?? and associates is much more cryptic, force you to think much harder about precedence, just to be slightly more compact. Granted, compact notation can be valuable, but usually if you want to couple multiple such things (like you often do with matmul), and precedence rules allows you to do it elegantly combine multiple operator with infix notation (again matmul is present in formulas that have scalar mul, + and -) . Do we do this with None-coalescence? I don't think so, not often anyway... Do we want to do this more with None-coalescence? Not me, certainly not: When I None coalesce, I want it to be as clear and atomic as possible because it's a corner case.
so strong -1...

On Fri, Jul 20, 2018 at 5:10 PM, Grégory Lielens gregory.lielens@gmail.com wrote:
On 2018-07-19 02:11, Chris Angelico wrote:
-snip- As far as I can see, these null-coalescing operators would break that model. The PEP doesn't seem to provide for a "real" magic method allowing users to override the actual behavior of the method. (You can only override __has_value__ to hook into it, but not define by fiat what A ?? B does, as you can with other operators.) And I think the reason for this is that the operator itself is too specific, much more specific in semantics than other operators. (I had similar doubts about adding the matrix-multiplication operator @.)
People keep saying that this null-coalescing behavior is so common
and useful, etc., but that hasn't been my experience at all. In my experience, the desire to shortcut this kind of logic is more often a sign of corner-cutting and insufficiently specified data formats, and is likely to cause bugs later on. Eventually it has to actually matter whether something is None or not, and these operators just kick that can down the road. In terms of their abstract meaning, they are not remotely close to as common or useful as operators like & and |.
Fully agree with you on this
Excuse me, that wasn't my words you quoted. Watch your citations please :)
ChrisA

On Friday, July 20, 2018 at 11:12:26 AM UTC+2, Chris Angelico wrote:
On Fri, Jul 20, 2018 at 5:10 PM, Grégory Lielens <gregory...@gmail.com javascript:> wrote:
Excuse me, that wasn't my words you quoted. Watch your citations please :)
ChrisA
Yes, sorry, it was Brendan Barnwell. And I can not edit my previous post....:-/

On Thu, Jul 19, 2018 at 2:36 PM Brendan Barnwell brenbarn@brenbarn.net wrote:
People keep saying that this null-coalescing behavior is so common
and useful, etc., but that hasn't been my experience at all. In my experience, the desire to shortcut this kind of logic is more often a sign of corner-cutting and insufficiently specified data formats, and is likely to cause bugs later on. Eventually it has to actually matter whether something is None or not, and these operators just kick that can down the road. In terms of their abstract meaning, they are not remotely close to as common or useful as operators like & and |.
Brendan,
I am sure you didn't intend any offense, but the phrase "corner-cutting" is pejorative, especially when stated as a generalization and not as a critique of a specific example. I have used these operators in professional projects in other languages (i.e. Dart), and I used them because they seemed like the best tool for the job at hand, not because I was shirking effort or short on time.
There are accepted PEPs that I don't find useful, e.g. PEP-465 (infix matrix multiplication operator). It's not because it's a bad PEP; it's just aimed at a type of programming that I don't do. That PEP had to address some of the same criticisms that arise from PEP-505: there's no precedent for that spelling, it's a small niche, and we can already to that in pure python.[1] But I trust the Python numeric computing community in their assertion that the @ syntax is valuable to their work.
In the same way that PEP-465 is valuable to a certain type of programming, None-coalescing (as an abstract concept, not the concrete proposal in PEP-505) is valuable to another type of programming. Python is often used a glue language.[2] In my own experience, it's very common to request data from one system, do some processing on it, and send it to another system. For example, I might run a SQL query, convert the results to JSON, and return it in an HTTP response. As a developer, I may not get to choose the database schema. I may also not get to choose the JSON schema. My job is to marshal data from one system to another. Consider the following example:
def get_user_json(user_id): user = user_table.find_by_id(user_id) user_dict = { # Username is always non-null in database. 'username': user['username'],
# Creation datetime is always non-null in database; must be ISO-8601 string in JSON. 'created_at': user['created'].isoformat(),
# Signature can be null in database and null in JSON. 'signature': user['signature'],
# Background color can be null in database but must not be null in JSON. 'background_color': user.get('background_color', 'blue')
# Login datetime can be null in database if user has never logged in. Must either # be null or an ISO-8601 string in JSON. 'last_logged_in_at': user['last_logged_in_at'].isoformat() if user['last_login'] is not None else None,
# Remaining fields omitted for brevity } return json.dumps(user_dict)
Python makes a lot of the desired behavior concise to write. For example, the DBAPI (PEP-249) states that null and None are interchanged when reading and writing from the database. The stdlib json module also converts between None and null. This makes a lot of the mappings trivial to express in Python. But it gets tricky for cases like "last_logged_in_at", where a null is permitted by the business rules I've been given, but if it's non-null then I need to call a method on it. As written above, it is over 100 characters long. With safe-navigation operators, it could be made to fit the line length without loss of clarity:
'last_logged_in_at': user['last_logged_in_at'] ?. isoformat(),
Many people in this thread prefer to write it as a block:
if user['last_logged_in_at'] is None: user_dict['last_logged_in_at'] = None else: user_dict['last_logged_in_at'] = user['last_logged_in_at'].isoformat()
I agree that the block is readable, but only because my made-up function is very short. In real-world glue code, you may have dozens of mappings, and multiplying by 4 leads to functions that have so many lines of code that readability is significantly worse, and that highly repetitive code inevitably leads to typos.
It's not just databases and web services. An early draft of PEP-505 contains additional examples of where None arises in glue code.[4] And it's not even just glue code: the standard library itself contains around 500 examples of none-coalescing or safe-navigation![5] I certainly don't think anybody here will claim that stdlib authors have cut hundreds of corners.
[1] https://www.python.org/dev/peps/pep-0465/ [2] https://www.python.org/doc/essays/omg-darpa-mcc-position/ [3] https://www.python.org/dev/peps/pep-0249/ [4] https://github.com/python/peps/blob/c5270848fe4481947ee951c2a415824b4dcd8a4f... [5] https://www.python.org/dev/peps/pep-0505/#examples

On 19/07/18 07:06, Greg Ewing wrote:
Chris Angelico wrote:
I'd love to hear an explanation of WHY this doesn't look like Python any more. For instance, is the + operator somehow wrong for Python, and it should have been the word "add"?
There's a very long tradition of using the symbol "+" to represent addition, so it's something most people are familiar with. There's no such tradition for the new operators being proposed.
There is, actually, it's just not a long one. C# has had null-aware operators for a while, for example. I'll admit the syntax sits better in a C-like language, though.

Rhodri James wrote:
On 19/07/18 07:06, Greg Ewing wrote:
There's no such tradition for the new operators being proposed.
There is, actually, it's just not a long one. C# has had null-aware operators for a while, for example.
THere's a precedent, yes, but I wouldn't call it a tradition. A substantial period of time is part of the definition of the word.
Wikipedia:
"A tradition is a belief or behavior passed down within a group or society with symbolic meaning or special significance with origins in the past."
Merriam-Webster:
"the handing down of information, beliefs, or customs from one generation to another."
I don't think C# has been around long enought to span multiple generations of programmers.

On 20/07/18 01:30, Greg Ewing wrote:
Rhodri James wrote:
On 19/07/18 07:06, Greg Ewing wrote:
There's no such tradition for the new operators being proposed.
There is, actually, it's just not a long one. C# has had null-aware operators for a while, for example.
THere's a precedent, yes, but I wouldn't call it a tradition. A substantial period of time is part of the definition of the word.
Wikipedia:
"A tradition is a belief or behavior passed down within a group or society with symbolic meaning or special significance with origins in the past."
Merriam-Webster:
"the handing down of information, beliefs, or customs from one generation to another."
I don't think C# has been around long enought to span multiple generations of programmers.
I go with SF fandom's traditional :-) definition: "somebody did it once." If it's been done more than once, it's an honoured tradition.

On Fri, Jul 20, 2018 at 10:14 PM, Rhodri James rhodri@kynesim.co.uk wrote:
I go with SF fandom's traditional :-) definition: "somebody did it once." If it's been done more than once, it's an honoured tradition.
But if Shakespeare did it, it's just the way the language is.
I think Fortran is the programming world's Shakespeare.
ChrisA

On 20 July 2018 at 13:16, Chris Angelico rosuav@gmail.com wrote:
On Fri, Jul 20, 2018 at 10:14 PM, Rhodri James rhodri@kynesim.co.uk wrote:
I go with SF fandom's traditional :-) definition: "somebody did it once." If it's been done more than once, it's an honoured tradition.
But if Shakespeare did it, it's just the way the language is.
I think Fortran is the programming world's Shakespeare.
Or maybe COBOL:
"And lo from yonder file a record doth appear"...
Paul

On Fri, Jul 20, 2018 at 10:24 PM, Paul Moore p.f.moore@gmail.com wrote:
On 20 July 2018 at 13:16, Chris Angelico rosuav@gmail.com wrote:
On Fri, Jul 20, 2018 at 10:14 PM, Rhodri James rhodri@kynesim.co.uk wrote:
I go with SF fandom's traditional :-) definition: "somebody did it once." If it's been done more than once, it's an honoured tradition.
But if Shakespeare did it, it's just the way the language is.
I think Fortran is the programming world's Shakespeare.
Or maybe COBOL:
"And lo from yonder file a record doth appear"...
Hah! But I was thinking of all those uber-obvious features like "a + b" meaning addition. Those symbols, and the infix style, aren't "traditions" - they're the baseline that we measure everything else against. Also, the use of decimal digits to represent literals; if you *don't* use decimal, you're unusual. (Which makes the Shakespeare Programming Language [1] an ironic example, since it doesn't use decimal digits for numeric literals.)
ChrisA [1] http://shakespearelang.sourceforge.net/report/shakespeare/

One of the big problems with most of the proposed spellings is that question marks to a learner will feel like truthiness tests and not None tests. I would propose adding a new default statement for the most useful part of this PEP, the ?= assignment
currently: if x is None: x = blah
with `?=`: x ?= blah
with defalult: default x: blah
This a) makes it obvious even to an amateur python-reader that x will only be changed if it does not yet exist, and b) has no similarity to the truthiness checks involved with the standard meaning of `?` as the ternary operator in many other languages.
On Fri, Jul 20, 2018 at 8:30 AM Chris Angelico rosuav@gmail.com wrote:
On Fri, Jul 20, 2018 at 10:24 PM, Paul Moore p.f.moore@gmail.com wrote:
On 20 July 2018 at 13:16, Chris Angelico rosuav@gmail.com wrote:
On Fri, Jul 20, 2018 at 10:14 PM, Rhodri James rhodri@kynesim.co.uk
wrote:
I go with SF fandom's traditional :-) definition: "somebody did it
once."
If it's been done more than once, it's an honoured tradition.
But if Shakespeare did it, it's just the way the language is.
I think Fortran is the programming world's Shakespeare.
Or maybe COBOL:
"And lo from yonder file a record doth appear"...
Hah! But I was thinking of all those uber-obvious features like "a + b" meaning addition. Those symbols, and the infix style, aren't "traditions" - they're the baseline that we measure everything else against. Also, the use of decimal digits to represent literals; if you *don't* use decimal, you're unusual. (Which makes the Shakespeare Programming Language [1] an ironic example, since it doesn't use decimal digits for numeric literals.)
ChrisA [1] http://shakespearelang.sourceforge.net/report/shakespeare/ _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

As before, I'm a strong -1 on this.
I recognize the semantic utility (albeit, I've needed it much less than some folks testify). But the obscure characters are accumulating far too fast with this.
Even with PEP 572, I would have preferred 'as' and restrictions on where it's allowed, but ':=' is more familiar from numerous languages and pseudo-code notations. '??' and '?.' and ?[]' are really just marching into APL and Perl territory. Yes, I know such operators exist in other languages, but it feels very unpythonic.
On Wed, Jul 18, 2018, 1:46 PM Steve Dower steve.dower@python.org wrote:
Possibly this is exactly the wrong time to propose the next big syntax change, since we currently have nobody to declare on it, but since we're likely to argue for a while anyway it probably can't hurt (and maybe this will become the test PEP for whoever takes the reins?).
FWIW, Guido had previously indicated that he was generally favourable towards most of this proposal, provided we could figure out coherent semantics. Last time we tried, that didn't happen, so this time I've made the semantics much more precise, have implemented and verified them, and made much stronger statements about why we are proposing these.
Additional thanks to Mark Haase for writing most of the PEP. All the fair and balanced parts are his - all the overly strong opinions are mine.
Also thanks to Nick Coghlan for writing PEPs 531 and 532 last time we went through this - if you're unhappy with "None" being treated as a special kind of value, I recommend reading those before you start repeating them.
There is a formatted version of this PEP at https://www.python.org/dev/peps/pep-0505/
My current implementation is at https://github.com/zooba/cpython/tree/pep-505 (though I'm considering removing some of the new opcodes I added and just generating more complex code - in any case, let's get hung up on the proposal rather than the implementation :) )
Let the discussions begin!
PEP: 505 Title: None-aware operators Version: $Revision$ Last-Modified: $Date$ Author: Mark E. Haase mehaase@gmail.com, Steve Dower steve.dower@python.org Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 18-Sep-2015 Python-Version: 3.8
Abstract
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.)
- The "``null``-aware index access" operator accesses an element of a
collection only if that collection is non-``null``. Otherwise it returns ``null``. (This is another type of "safe navigation" operator.)
This PEP proposes three ``None``-aware operators for Python, based on the definitions and other language's implementations of those above. Specifically:
- The "``None`` coalescing`` binary operator ``??`` returns the left
hand side if it evaluates to a value that is not ``None``, or else it evaluates and returns the right hand side. A coalescing ``??=`` augmented assignment operator is included.
- The "``None``-aware attribute access" operator ``?.`` evaluates the
complete expression if the left hand side evaluates to a value that is not ``None``
- The "``None``-aware indexing" operator ``?[]`` evaluates the complete expression if the left hand site evaluates to a value that is not
``None``
Syntax and Semantics
Specialness of ``None``
The ``None`` object denotes the lack of a value. For the purposes of these operators, the lack of a value indicates that the remainder of the expression also lacks a value and should not be evaluated.
A rejected proposal was to treat any value that evaluates to false in a Boolean context as not having a value. However, the purpose of these operators is to propagate the "lack of value" state, rather that the "false" state.
Some argue that this makes ``None`` special. We contend that ``None`` is already special, and that using it as both the test and the result of these operators does not change the existing semantics in any way.
See the `Rejected Ideas`_ section for discussion on the rejected approaches.
Grammar changes
The following rules of the Python grammar are updated to read::
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' |
'^=' | '<<=' | '>>=' | '**=' | '//=' | '??=')
power: coalesce ['**' factor] coalesce: atom_expr ['??' factor] atom_expr: ['await'] atom trailer* trailer: ('(' [arglist] ')' | '[' subscriptlist ']' | '?[' subscriptlist ']' | '.' NAME | '?.' NAME)
Inserting the ``coalesce`` rule in this location ensures that expressions resulting in ``None`` are natuarlly coalesced before they are used in operations that would typically raise ``TypeError``. Like ``and`` and ``or`` the right-hand expression is not evaluated until the left-hand side is determined to be ``None``. For example::
a, b = None, None def c(): return None def ex(): raise Exception() (a ?? 2 ** b ?? 3) == a ?? (2 ** (b ?? 3)) (a * b ?? c // d) == a * (b ?? c) // d (a ?? True and b ?? False) == (a ?? True) and (b ?? False) (c() ?? c() ?? True) == True (True ?? ex()) == True (c ?? ex)() == c()
Augmented coalescing assignment only rebinds the name if its current value is ``None``. If the target name already has a value, the right-hand side is not evaluated. For example::
a = None b = '' c = 0 a ??= 'value' b ??= undefined_name c ??= shutil.rmtree('/') # don't try this at home, kids assert a == 'value' assert b == '' assert c == '0' and any(os.scandir('/'))
Adding new trailers for the other ``None``-aware operators ensures that they may be used in all valid locations for the existing equivalent operators, including as part of an assignment target (more details below). As the existing evaluation rules are not directly embedded in the grammar, we specify the required changes here.
Assume that the ``atom`` is always successfully evaluated. Each ``trailer`` is then evaluated from left to right, applying its own parameter (either its arguments, subscripts or attribute name) to produce the value for the next ``trailer``. Finally, if present, ``await`` is applied.
For example, ``await a.b(c).d[e]`` is currently parsed as ``['await', 'a', '.b', '(c)', '.d', '[e]']`` and evaluated::
_v = a _v = _v.b _v = _v(c) _v = _v.d _v = _v[e] await _v
When a ``None``-aware operator is present, the left-to-right evaluation may be short-circuited. For example, ``await a?.b(c).d?[e]`` is evaluated::
_v = a if _v is not None: _v = _v.b _v = _v(c) _v = _v.d if _v is not None: _v = _v[e] await _v
.. note:: ``await`` will almost certainly fail in this context, as it would in the case where code attempts ``await None``. We are not proposing to add a ``None``-aware ``await`` keyword here, and merely include it in this example for completeness of the specification, since the ``atom_expr`` grammar rule includes the keyword. If it were in its own rule, we would have never mentioned it.
Parenthesised expressions are handled by the ``atom`` rule (not shown above), which will implicitly terminate the short-circuiting behaviour of the above transformation. For example, ``(a?.b ?? c).d?.e`` is evaluated as::
# a?.b _v = a if _v is not None: _v = _v.b # ... ?? c if _v is None: _v = c # (...).d?.e _v = _v.d if _v is not None: _v = _v.e
When used as an assignment target, the ``None``-aware operations may only be used in a "load" context. That is, ``a?.b = 1`` and ``a?[b] = 1`` will raise ``SyntaxError``. Use earlier in the expression (``a?.b.c = 1``) is permitted, though unlikely to be useful unless combined with a coalescing operation::
(a?.b ?? d).c = 1
Examples
This section presents some examples of common ``None`` patterns and shows what conversion to use ``None``-aware operators may look like.
Standard Library
Using the ``find-pep505.py`` script[3]_ an analysis of the Python 3.7 standard library discovered up to 678 code snippets that could be replaced with use of one of the ``None``-aware operators::
$ find /usr/lib/python3.7 -name '*.py' | xargs python3.7
find-pep505.py <snip> Total None-coalescing `if` blocks: 449 Total [possible] None-coalescing `or`: 120 Total None-coalescing ternaries: 27 Total Safe navigation `and`: 13 Total Safe navigation `if` blocks: 61 Total Safe navigation ternaries: 8
Some of these are shown below as examples before and after converting to use the new operators.
From ``bisect.py``::
def insort_right(a, x, lo=0, hi=None): # ... if hi is None: hi = len(a) # ...
After updating to use the ``??=`` augmented assignment statement::
def insort_right(a, x, lo=0, hi=None): # ... hi ??= len(a) # ...
From ``calendar.py``::
encoding = options.encoding if encoding is None: encoding = sys.getdefaultencoding() optdict = dict(encoding=encoding, css=options.css)
After updating to use the ``??`` operator::
optdict = dict(encoding=encoding ?? sys.getdefaultencoding(), css=options.css)
From ``dis.py``::
def _get_const_info(const_index, const_list): argval = const_index if const_list is not None: argval = const_list[const_index] return argval, repr(argval)
After updating to use the ``?[]`` and ``??`` operators::
def _get_const_info(const_index, const_list): argval = const_list?[const_index] ?? const_index return argval, repr(argval)
From ``inspect.py``::
for base in object.__bases__: for name in getattr(base, "__abstractmethods__", ()): value = getattr(object, name, None) if getattr(value, "__isabstractmethod__", False): return True
After updating to use the ``?.`` operator (and deliberately not converting to use ``any()``)::
for base in object.__bases__: for name in base?.__abstractmethods__ ?? (): if object?.name?.__isabstractmethod__: return True
From ``os.py``::
if entry.is_dir(): dirs.append(name) if entries is not None: entries.append(entry) else: nondirs.append(name)
After updating to use the ``?.`` operator::
if entry.is_dir(): dirs.append(name) entries?.append(entry) else: nondirs.append(name)
jsonify
This example is from a Python web crawler that uses the Flask framework as its 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 does not have a native way to represent a ``datetime``, so the server's contract states that any non-``null`` date is represented as an ISO-8601 string.
Without knowing the exact semantics of the ``first_seen`` and ``last_seen`` attributes, it is impossible to know whether the attribute can be safely or performantly accessed multiple times.
One way to fix this code is to replace each conditional expression with an explicit value assignment and 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_) first_seen_dt = site.first_seen if first_seen_dt is None: first_seen = None else: first_seen = first_seen_dt.isoformat() last_seen_dt = site.last_seen if last_seen_dt is None: last_seen = None else: last_seen = last_seen_dt.isoformat() return jsonify( first_seen=first_seen, id=site.id, is_active=site.is_active, last_seen=last_seen, url=site.url.rstrip('/') )
This adds ten lines of code and four new code paths to the function, dramatically increasing the apparent complexity. Rewriting using the ``None``-aware attribute operator results in shorter code with more clear intent::
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('/') )
Grab
The next example is from a Python scraping library called `Grab < https://github.com/lorien/grab/blob/4c95b18dcb0fa88eeca81f5643c0ebfb114bf728... ab/upload.py https://github.com/lorien/grab/blob/4c95b18dcb0fa88eeca81f5643c0ebfb114bf728/grab/upload.py
`_::
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
This example contains several good examples of needing to provide default values. Rewriting to use conditional expressions reduces the overall lines of code, but does not necessarily improve readability::
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.
Rewriting using the ``None`` coalescing operator::
class BaseUploadObject(object): def find_content_type(self, filename): ctype, encoding = mimetypes.guess_type(filename) return ctype ?? 'application/octet-stream' class UploadContent(BaseUploadObject): def __init__(self, content, filename=None, content_type=None): self.content = content self.filename = filename ?? self.get_random_filename() self.content_type = content_type ??
self.find_content_type(self.filename)
class UploadFile(BaseUploadObject): def __init__(self, path, filename=None, content_type=None): self.path = path self.filename = filename ?? os.path.split(path)[1] self.content_type = content_type ??
self.find_content_type(self.filename)
This syntax has an intuitive ordering of the operands. In ``find_content_type``, for example, the preferred value ``ctype`` appears before the fallback value. The terseness of the syntax also makes for fewer lines of code and less code to visually parse, and reading from left-to-right and top-to-bottom more accurately follows the execution flow.
Rejected Ideas
The first three ideas in this section are oft-proposed alternatives to treating ``None`` as special. For further background on why these are rejected, see their treatment in `PEP 531 https://www.python.org/dev/peps/pep-0531/`_ and `PEP 532 https://www.python.org/dev/peps/pep-0532/`_ and the associated discussions.
No-Value Protocol
The operators could be generalised to user-defined types by defining a protocol to indicate when a value represents "no value". Such a protocol may be a dunder method ``__has_value__(self)` that returns ``True`` if the value should be treated as having a value, and ``False`` if the value should be treated as no value.
With this generalization, ``object`` would implement a dunder method equivalent to this::
def __has_value__(self): return True
``NoneType`` would implement a dunder method equivalent to this::
def __has_value__(self): return False
In the specification section, all uses of ``x is None`` would be replaced with ``not x.__has_value__()``.
This generalization would allow for domain-specific "no-value" objects to be coalesced just like ``None``. For example the ``pyasn1`` package has a type called ``Null`` that represents an ASN.1 ``null``::
>>> from pyasn1.type import univ >>> univ.Null() ?? univ.Integer(123) Integer(123)
Similarly, values such as ``math.nan`` and ``NotImplemented`` could be treated as representing no value.
However, the "no-value" nature of these values is domain-specific, which means they *should* be treated as a value by the language. For example, ``math.nan.imag`` is well defined (it's ``0.0``), and so short-circuiting ``math.nan?.imag`` to return ``math.nan`` would be incorrect.
As ``None`` is already defined by the language as being the value that represents "no value", and the current specification would not preclude switching to a protocol in the future (though changes to built-in objects would not be compatible), this idea is rejected for now.
Boolean-aware operators
This suggestion is fundamentally the same as adding a no-value protocol, and so the discussion above also applies.
Similar behavior to the ``??`` operator can be achieved with an ``or`` expression, however ``or`` checks whether its left operand is false-y and not specifically ``None``. This approach is attractive, as it requires fewer changes to the language, but ultimately does not solve the underlying problem correctly.
Assuming the check is for truthiness rather than ``None``, there is no longer a need for the ``??`` operator. However, applying this check to the ``?.`` and ``?[]`` operators prevents perfectly valid operations applying
Consider the following example, where ``get_log_list()`` may return either a list containing current log messages (potentially empty), or ``None`` if logging is not enabled::
lst = get_log_list() lst?.append('A log message')
If ``?.`` is checking for true values rather than specifically ``None`` and the log has not been initialized with any items, no item will ever be appended. This violates the obvious intent of the code, which is to append an item. The ``append`` method is available on an empty list, as are all other list methods, and there is no reason to assume that these members should not be used because the list is presently empty.
Further, there is no sensible result to use in place of the expression. A normal ``lst.append`` returns ``None``, but under this idea ``lst?.append`` may result in either ``[]`` or ``None``, depending on the value of ``lst``. As with the examples in the previous section, this makes no sense.
As checking for truthiness rather than ``None`` results in apparently valid expressions no longer executing as intended, this idea is rejected.
Exception-aware operators
Arguably, the reason to short-circuit an expression when ``None`` is encountered is to avoid the ``AttributeError`` or ``TypeError`` that would be raised under normal circumstances. As an alternative to testing for ``None``, the ``?.`` and ``?[]`` operators could instead handle ``AttributeError`` and ``TypeError`` raised by the operation and skip the remainder of the expression.
This produces a transformation for ``a?.b.c?.d.e`` similar to this::
_v = a try: _v = _v.b except AttributeError: pass else: _v = _v.c try: _v = _v.d except AttributeError: pass else: _v = _v.e
One open question is which value should be returned as the expression when an exception is handled. The above example simply leaves the partial result, but this is not helpful for replacing with a default value. An alternative would be to force the result to ``None``, which then raises the question as to why ``None`` is special enough to be the result but not special enough to be the test.
Secondly, this approach masks errors within code executed implicitly as part of the expression. For ``?.``, any ``AttributeError`` within a property or ``__getattr__`` implementation would be hidden, and similarly for ``?[]`` and ``__getitem__`` implementations.
Similarly, simple typing errors such as ``{}?.ietms()`` could go unnoticed.
Existing conventions for handling these kinds of errors in the form of the ``getattr`` builtin and the ``.get(key, default)`` method pattern established by ``dict`` show that it is already possible to explicitly use this behaviour.
As this approach would hide errors in code, it is rejected.
``None``-aware Function Call
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. It might be written as ``foo?()``, where ``foo`` is only called if it is not None.
This has been deferred on the basis of the proposed operators being intended to aid traversal of partially populated hierarchical data structures, *not* for traversal of arbitrary class hierarchies. This is reflected in the fact that none of the other mainstream languages that already offer this syntax have found it worthwhile to support a similar syntax for optional function invocations.
A workaround similar to that used by C# would be to write ``maybe_none?.__call__(arguments)``. If the callable is ``None``, the expression will not be evaluated. (The C# equivalent uses ``?.Invoke()`` on its callable type.)
``?`` 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 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``::
class NoneQuestion(): def __call__(self, *args, **kwargs): return self def __getattr__(self, name): return self def __getitem__(self, key): return self
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.
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.
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?::
>>> 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.
Built-in ``maybe``
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".
A Python package called `pymaybe https://pypi.org/p/pymaybe/`_ provides a rough approximation. The documentation shows the following example::
>>> maybe('VALUE').lower() 'value' >>> maybe(None).invalid().method().or_else('unknown') 'unknown'
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.
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.
The idea of adding a builtin ``maybe`` type to enable this scenario is rejected.
Just use a conditional expression
Another common way to initialize default values is to use the ternary operator. Here is an excerpt from the popular `Requests package < https://github.com/kennethreitz/requests/blob/14a555ac716866678bf17e43e23230... a8149f5/requests/models.py#L212 https://github.com/kennethreitz/requests/blob/14a555ac716866678bf17e43e23230d81a8149f5/requests/models.py#L212
`_::
data = [] if data is None else data files = [] if files is None else files headers = {} if headers is None else headers params = {} if params is None else params hooks = {} if hooks is None else hooks
This particular formulation has the undesirable effect of putting the operands in an unintuitive order: the brain thinks, "use ``data`` if possible and use ``[]`` as a fallback," but the code puts the fallback *before* the preferred value.
The author of this package could have written it like this instead::
data = data if data is not None else [] files = files if files is not None else [] headers = headers if headers is not None else {} params = params if params is not None else {} hooks = hooks if hooks is not None else {}
This ordering of the operands is more intuitive, but it requires 4 extra characters (for "not "). It also highlights the repetition of identifiers: ``data if data``, ``files if files``, etc.
When written using the ``None`` coalescing operator, the sample reads::
data = data ?? [] files = files ?? [] headers = headers ?? {} params = params ?? {} hooks = hooks ?? {}
References
.. [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] Associated scripts (https://github.com/python/peps/tree/master/pep-0505/)
Copyright
This document has been placed in the public domain. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

The examples in the PEP strengthen my opinion. I think every one of them read much more clearly in the existing syntax. I can only understand any by mentally inserting 'if Foo is None' everywhere... Basically mentally replacing the "code golf" syntax with the actual intent that is spelled like it is in current Python.
On Wed, Jul 18, 2018, 8:05 PM David Mertz mertz@gnosis.cx wrote:
As before, I'm a strong -1 on this.
I recognize the semantic utility (albeit, I've needed it much less than some folks testify). But the obscure characters are accumulating far too fast with this.
Even with PEP 572, I would have preferred 'as' and restrictions on where it's allowed, but ':=' is more familiar from numerous languages and pseudo-code notations. '??' and '?.' and ?[]' are really just marching into APL and Perl territory. Yes, I know such operators exist in other languages, but it feels very unpythonic.
On Wed, Jul 18, 2018, 1:46 PM Steve Dower steve.dower@python.org wrote:
Possibly this is exactly the wrong time to propose the next big syntax change, since we currently have nobody to declare on it, but since we're likely to argue for a while anyway it probably can't hurt (and maybe this will become the test PEP for whoever takes the reins?).
FWIW, Guido had previously indicated that he was generally favourable towards most of this proposal, provided we could figure out coherent semantics. Last time we tried, that didn't happen, so this time I've made the semantics much more precise, have implemented and verified them, and made much stronger statements about why we are proposing these.
Additional thanks to Mark Haase for writing most of the PEP. All the fair and balanced parts are his - all the overly strong opinions are mine.
Also thanks to Nick Coghlan for writing PEPs 531 and 532 last time we went through this - if you're unhappy with "None" being treated as a special kind of value, I recommend reading those before you start repeating them.
There is a formatted version of this PEP at https://www.python.org/dev/peps/pep-0505/
My current implementation is at https://github.com/zooba/cpython/tree/pep-505 (though I'm considering removing some of the new opcodes I added and just generating more complex code - in any case, let's get hung up on the proposal rather than the implementation :) )
Let the discussions begin!
PEP: 505 Title: None-aware operators Version: $Revision$ Last-Modified: $Date$ Author: Mark E. Haase mehaase@gmail.com, Steve Dower steve.dower@python.org Status: Draft Type: Standards Track Content-Type: text/x-rst Created: 18-Sep-2015 Python-Version: 3.8
Abstract
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.)
- The "``null``-aware index access" operator accesses an element of a
collection only if that collection is non-``null``. Otherwise it returns ``null``. (This is another type of "safe navigation" operator.)
This PEP proposes three ``None``-aware operators for Python, based on the definitions and other language's implementations of those above. Specifically:
- The "``None`` coalescing`` binary operator ``??`` returns the left
hand side if it evaluates to a value that is not ``None``, or else it evaluates and returns the right hand side. A coalescing ``??=`` augmented assignment operator is included.
- The "``None``-aware attribute access" operator ``?.`` evaluates the
complete expression if the left hand side evaluates to a value that is not ``None``
- The "``None``-aware indexing" operator ``?[]`` evaluates the complete expression if the left hand site evaluates to a value that is not
``None``
Syntax and Semantics
Specialness of ``None``
The ``None`` object denotes the lack of a value. For the purposes of these operators, the lack of a value indicates that the remainder of the expression also lacks a value and should not be evaluated.
A rejected proposal was to treat any value that evaluates to false in a Boolean context as not having a value. However, the purpose of these operators is to propagate the "lack of value" state, rather that the "false" state.
Some argue that this makes ``None`` special. We contend that ``None`` is already special, and that using it as both the test and the result of these operators does not change the existing semantics in any way.
See the `Rejected Ideas`_ section for discussion on the rejected approaches.
Grammar changes
The following rules of the Python grammar are updated to read::
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' |
'^=' | '<<=' | '>>=' | '**=' | '//=' | '??=')
power: coalesce ['**' factor] coalesce: atom_expr ['??' factor] atom_expr: ['await'] atom trailer* trailer: ('(' [arglist] ')' | '[' subscriptlist ']' | '?[' subscriptlist ']' | '.' NAME | '?.' NAME)
Inserting the ``coalesce`` rule in this location ensures that expressions resulting in ``None`` are natuarlly coalesced before they are used in operations that would typically raise ``TypeError``. Like ``and`` and ``or`` the right-hand expression is not evaluated until the left-hand side is determined to be ``None``. For example::
a, b = None, None def c(): return None def ex(): raise Exception() (a ?? 2 ** b ?? 3) == a ?? (2 ** (b ?? 3)) (a * b ?? c // d) == a * (b ?? c) // d (a ?? True and b ?? False) == (a ?? True) and (b ?? False) (c() ?? c() ?? True) == True (True ?? ex()) == True (c ?? ex)() == c()
Augmented coalescing assignment only rebinds the name if its current value is ``None``. If the target name already has a value, the right-hand side is not evaluated. For example::
a = None b = '' c = 0 a ??= 'value' b ??= undefined_name c ??= shutil.rmtree('/') # don't try this at home, kids assert a == 'value' assert b == '' assert c == '0' and any(os.scandir('/'))
Adding new trailers for the other ``None``-aware operators ensures that they may be used in all valid locations for the existing equivalent operators, including as part of an assignment target (more details below). As the existing evaluation rules are not directly embedded in the grammar, we specify the required changes here.
Assume that the ``atom`` is always successfully evaluated. Each ``trailer`` is then evaluated from left to right, applying its own parameter (either its arguments, subscripts or attribute name) to produce the value for the next ``trailer``. Finally, if present, ``await`` is applied.
For example, ``await a.b(c).d[e]`` is currently parsed as ``['await', 'a', '.b', '(c)', '.d', '[e]']`` and evaluated::
_v = a _v = _v.b _v = _v(c) _v = _v.d _v = _v[e] await _v
When a ``None``-aware operator is present, the left-to-right evaluation may be short-circuited. For example, ``await a?.b(c).d?[e]`` is evaluated::
_v = a if _v is not None: _v = _v.b _v = _v(c) _v = _v.d if _v is not None: _v = _v[e] await _v
.. note:: ``await`` will almost certainly fail in this context, as it would in the case where code attempts ``await None``. We are not proposing to add a ``None``-aware ``await`` keyword here, and merely include it in this example for completeness of the specification, since the ``atom_expr`` grammar rule includes the keyword. If it were in its own rule, we would have never mentioned it.
Parenthesised expressions are handled by the ``atom`` rule (not shown above), which will implicitly terminate the short-circuiting behaviour of the above transformation. For example, ``(a?.b ?? c).d?.e`` is evaluated as::
# a?.b _v = a if _v is not None: _v = _v.b # ... ?? c if _v is None: _v = c # (...).d?.e _v = _v.d if _v is not None: _v = _v.e
When used as an assignment target, the ``None``-aware operations may only be used in a "load" context. That is, ``a?.b = 1`` and ``a?[b] = 1`` will raise ``SyntaxError``. Use earlier in the expression (``a?.b.c = 1``) is permitted, though unlikely to be useful unless combined with a coalescing operation::
(a?.b ?? d).c = 1
Examples
This section presents some examples of common ``None`` patterns and shows what conversion to use ``None``-aware operators may look like.
Standard Library
Using the ``find-pep505.py`` script[3]_ an analysis of the Python 3.7 standard library discovered up to 678 code snippets that could be replaced with use of one of the ``None``-aware operators::
$ find /usr/lib/python3.7 -name '*.py' | xargs python3.7
find-pep505.py <snip> Total None-coalescing `if` blocks: 449 Total [possible] None-coalescing `or`: 120 Total None-coalescing ternaries: 27 Total Safe navigation `and`: 13 Total Safe navigation `if` blocks: 61 Total Safe navigation ternaries: 8
Some of these are shown below as examples before and after converting to use the new operators.
From ``bisect.py``::
def insort_right(a, x, lo=0, hi=None): # ... if hi is None: hi = len(a) # ...
After updating to use the ``??=`` augmented assignment statement::
def insort_right(a, x, lo=0, hi=None): # ... hi ??= len(a) # ...
From ``calendar.py``::
encoding = options.encoding if encoding is None: encoding = sys.getdefaultencoding() optdict = dict(encoding=encoding, css=options.css)
After updating to use the ``??`` operator::
optdict = dict(encoding=encoding ?? sys.getdefaultencoding(), css=options.css)
From ``dis.py``::
def _get_const_info(const_index, const_list): argval = const_index if const_list is not None: argval = const_list[const_index] return argval, repr(argval)
After updating to use the ``?[]`` and ``??`` operators::
def _get_const_info(const_index, const_list): argval = const_list?[const_index] ?? const_index return argval, repr(argval)
From ``inspect.py``::
for base in object.__bases__: for name in getattr(base, "__abstractmethods__", ()): value = getattr(object, name, None) if getattr(value, "__isabstractmethod__", False): return True
After updating to use the ``?.`` operator (and deliberately not converting to use ``any()``)::
for base in object.__bases__: for name in base?.__abstractmethods__ ?? (): if object?.name?.__isabstractmethod__: return True
From ``os.py``::
if entry.is_dir(): dirs.append(name) if entries is not None: entries.append(entry) else: nondirs.append(name)
After updating to use the ``?.`` operator::
if entry.is_dir(): dirs.append(name) entries?.append(entry) else: nondirs.append(name)
jsonify
This example is from a Python web crawler that uses the Flask framework as its 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 does not have a native way to represent a ``datetime``, so the server's contract states that any non-``null`` date is represented as an ISO-8601 string.
Without knowing the exact semantics of the ``first_seen`` and ``last_seen`` attributes, it is impossible to know whether the attribute can be safely or performantly accessed multiple times.
One way to fix this code is to replace each conditional expression with an explicit value assignment and 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_) first_seen_dt = site.first_seen if first_seen_dt is None: first_seen = None else: first_seen = first_seen_dt.isoformat() last_seen_dt = site.last_seen if last_seen_dt is None: last_seen = None else: last_seen = last_seen_dt.isoformat() return jsonify( first_seen=first_seen, id=site.id, is_active=site.is_active, last_seen=last_seen, url=site.url.rstrip('/') )
This adds ten lines of code and four new code paths to the function, dramatically increasing the apparent complexity. Rewriting using the ``None``-aware attribute operator results in shorter code with more clear intent::
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('/') )
Grab
The next example is from a Python scraping library called `Grab < https://github.com/lorien/grab/blob/4c95b18dcb0fa88eeca81f5643c0ebfb114bf728... ab/upload.py https://github.com/lorien/grab/blob/4c95b18dcb0fa88eeca81f5643c0ebfb114bf728/grab/upload.py
`_::
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
This example contains several good examples of needing to provide default values. Rewriting to use conditional expressions reduces the overall lines of code, but does not necessarily improve readability::
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.
Rewriting using the ``None`` coalescing operator::
class BaseUploadObject(object): def find_content_type(self, filename): ctype, encoding = mimetypes.guess_type(filename) return ctype ?? 'application/octet-stream' class UploadContent(BaseUploadObject): def __init__(self, content, filename=None, content_type=None): self.content = content self.filename = filename ?? self.get_random_filename() self.content_type = content_type ??
self.find_content_type(self.filename)
class UploadFile(BaseUploadObject): def __init__(self, path, filename=None, content_type=None): self.path = path self.filename = filename ?? os.path.split(path)[1] self.content_type = content_type ??
self.find_content_type(self.filename)
This syntax has an intuitive ordering of the operands. In ``find_content_type``, for example, the preferred value ``ctype`` appears before the fallback value. The terseness of the syntax also makes for fewer lines of code and less code to visually parse, and reading from left-to-right and top-to-bottom more accurately follows the execution flow.
Rejected Ideas
The first three ideas in this section are oft-proposed alternatives to treating ``None`` as special. For further background on why these are rejected, see their treatment in `PEP 531 https://www.python.org/dev/peps/pep-0531/`_ and `PEP 532 https://www.python.org/dev/peps/pep-0532/`_ and the associated discussions.
No-Value Protocol
The operators could be generalised to user-defined types by defining a protocol to indicate when a value represents "no value". Such a protocol may be a dunder method ``__has_value__(self)` that returns ``True`` if the value should be treated as having a value, and ``False`` if the value should be treated as no value.
With this generalization, ``object`` would implement a dunder method equivalent to this::
def __has_value__(self): return True
``NoneType`` would implement a dunder method equivalent to this::
def __has_value__(self): return False
In the specification section, all uses of ``x is None`` would be replaced with ``not x.__has_value__()``.
This generalization would allow for domain-specific "no-value" objects to be coalesced just like ``None``. For example the ``pyasn1`` package has a type called ``Null`` that represents an ASN.1 ``null``::
>>> from pyasn1.type import univ >>> univ.Null() ?? univ.Integer(123) Integer(123)
Similarly, values such as ``math.nan`` and ``NotImplemented`` could be treated as representing no value.
However, the "no-value" nature of these values is domain-specific, which means they *should* be treated as a value by the language. For example, ``math.nan.imag`` is well defined (it's ``0.0``), and so short-circuiting ``math.nan?.imag`` to return ``math.nan`` would be incorrect.
As ``None`` is already defined by the language as being the value that represents "no value", and the current specification would not preclude switching to a protocol in the future (though changes to built-in objects would not be compatible), this idea is rejected for now.
Boolean-aware operators
This suggestion is fundamentally the same as adding a no-value protocol, and so the discussion above also applies.
Similar behavior to the ``??`` operator can be achieved with an ``or`` expression, however ``or`` checks whether its left operand is false-y and not specifically ``None``. This approach is attractive, as it requires fewer changes to the language, but ultimately does not solve the underlying problem correctly.
Assuming the check is for truthiness rather than ``None``, there is no longer a need for the ``??`` operator. However, applying this check to the ``?.`` and ``?[]`` operators prevents perfectly valid operations applying
Consider the following example, where ``get_log_list()`` may return either a list containing current log messages (potentially empty), or ``None`` if logging is not enabled::
lst = get_log_list() lst?.append('A log message')
If ``?.`` is checking for true values rather than specifically ``None`` and the log has not been initialized with any items, no item will ever be appended. This violates the obvious intent of the code, which is to append an item. The ``append`` method is available on an empty list, as are all other list methods, and there is no reason to assume that these members should not be used because the list is presently empty.
Further, there is no sensible result to use in place of the expression. A normal ``lst.append`` returns ``None``, but under this idea ``lst?.append`` may result in either ``[]`` or ``None``, depending on the value of ``lst``. As with the examples in the previous section, this makes no sense.
As checking for truthiness rather than ``None`` results in apparently valid expressions no longer executing as intended, this idea is rejected.
Exception-aware operators
Arguably, the reason to short-circuit an expression when ``None`` is encountered is to avoid the ``AttributeError`` or ``TypeError`` that would be raised under normal circumstances. As an alternative to testing for ``None``, the ``?.`` and ``?[]`` operators could instead handle ``AttributeError`` and ``TypeError`` raised by the operation and skip the remainder of the expression.
This produces a transformation for ``a?.b.c?.d.e`` similar to this::
_v = a try: _v = _v.b except AttributeError: pass else: _v = _v.c try: _v = _v.d except AttributeError: pass else: _v = _v.e
One open question is which value should be returned as the expression when an exception is handled. The above example simply leaves the partial result, but this is not helpful for replacing with a default value. An alternative would be to force the result to ``None``, which then raises the question as to why ``None`` is special enough to be the result but not special enough to be the test.
Secondly, this approach masks errors within code executed implicitly as part of the expression. For ``?.``, any ``AttributeError`` within a property or ``__getattr__`` implementation would be hidden, and similarly for ``?[]`` and ``__getitem__`` implementations.
Similarly, simple typing errors such as ``{}?.ietms()`` could go unnoticed.
Existing conventions for handling these kinds of errors in the form of the ``getattr`` builtin and the ``.get(key, default)`` method pattern established by ``dict`` show that it is already possible to explicitly use this behaviour.
As this approach would hide errors in code, it is rejected.
``None``-aware Function Call
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. It might be written as ``foo?()``, where ``foo`` is only called if it is not None.
This has been deferred on the basis of the proposed operators being intended to aid traversal of partially populated hierarchical data structures, *not* for traversal of arbitrary class hierarchies. This is reflected in the fact that none of the other mainstream languages that already offer this syntax have found it worthwhile to support a similar syntax for optional function invocations.
A workaround similar to that used by C# would be to write ``maybe_none?.__call__(arguments)``. If the callable is ``None``, the expression will not be evaluated. (The C# equivalent uses ``?.Invoke()`` on its callable type.)
``?`` 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 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``::
class NoneQuestion(): def __call__(self, *args, **kwargs): return self def __getattr__(self, name): return self def __getitem__(self, key): return self
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.
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.
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?::
>>> 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.
Built-in ``maybe``
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".
A Python package called `pymaybe https://pypi.org/p/pymaybe/`_ provides a rough approximation. The documentation shows the following example::
>>> maybe('VALUE').lower() 'value' >>> maybe(None).invalid().method().or_else('unknown') 'unknown'
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.
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.
The idea of adding a builtin ``maybe`` type to enable this scenario is rejected.
Just use a conditional expression
Another common way to initialize default values is to use the ternary operator. Here is an excerpt from the popular `Requests package < https://github.com/kennethreitz/requests/blob/14a555ac716866678bf17e43e23230... a8149f5/requests/models.py#L212 https://github.com/kennethreitz/requests/blob/14a555ac716866678bf17e43e23230d81a8149f5/requests/models.py#L212
`_::
data = [] if data is None else data files = [] if files is None else files headers = {} if headers is None else headers params = {} if params is None else params hooks = {} if hooks is None else hooks
This particular formulation has the undesirable effect of putting the operands in an unintuitive order: the brain thinks, "use ``data`` if possible and use ``[]`` as a fallback," but the code puts the fallback *before* the preferred value.
The author of this package could have written it like this instead::
data = data if data is not None else [] files = files if files is not None else [] headers = headers if headers is not None else {} params = params if params is not None else {} hooks = hooks if hooks is not None else {}
This ordering of the operands is more intuitive, but it requires 4 extra characters (for "not "). It also highlights the repetition of identifiers: ``data if data``, ``files if files``, etc.
When written using the ``None`` coalescing operator, the sample reads::
data = data ?? [] files = files ?? [] headers = headers ?? {} params = params ?? {} hooks = hooks ?? {}
References
.. [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] Associated scripts (https://github.com/python/peps/tree/master/pep-0505/)
Copyright
This document has been placed in the public domain. _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Wed, Jul 18, 2018 at 08:05:56PM -0400, David Mertz wrote:
'??' and '?.' and ?[]' are really just marching into APL and Perl territory. Yes, I know such operators exist in other languages, but it feels very unpythonic.
If these features exist in "other languages", and *don't* exist in APL or Perl, how are they marching into APL and Perl territory?
Perl territory, like this perhaps?
print "hello world\n"; @days = ("Monday", "Tuesday", "Wednesday"); print $days[0]
Yes, I can see why we use "Perl syntax" as an insult *wink*
Okay, okay, my examples are a bit unfair. I deliberately chose examples where the syntax is almost identical to Python's. Aside from the array and scalar sigils @ and $ the above could be Python.
Tens of thousands of non-English speakers have had to learn the meaning of what might as well be meaningless, random sets of symbols (to them) like "class", "import", "while" and "True". If they can do so, perhaps we English-speakers should stop complaining about how hard it is to memorise the meaning of a couple of symbols like ??. Surely its no more difficult than learning the various meanings of ** and [] which we've already done.
*Its just spelling*. If it is a useful and well-defined feature, we'll get used to the spelling soon enough.
That's not to say that spelling is not important *at all*, or that we should never prefer words to symbols. But if the only objection we have is "this is useful but I don't like the spelling so -1" then that's usually a pretty weak argument against the feature.

"APL and Perl territory" means "use lots of punctuation characters in somewhat cryptic ways, often combining several for a distinct semantics." I did not mean "APL and Perl use those specific characters with the proposed meaning."
On Thu, Jul 19, 2018, 9:39 AM Steven D'Aprano steve@pearwood.info wrote:
On Wed, Jul 18, 2018 at 08:05:56PM -0400, David Mertz wrote:
'??' and '?.' and ?[]' are really just marching into APL and Perl territory. Yes, I know such operators exist in other languages, but it feels very unpythonic.
If these features exist in "other languages", and *don't* exist in APL or Perl, how are they marching into APL and Perl territory?
Perl territory, like this perhaps?
print "hello world\n"; @days = ("Monday", "Tuesday", "Wednesday"); print $days[0]
Yes, I can see why we use "Perl syntax" as an insult *wink*
Okay, okay, my examples are a bit unfair. I deliberately chose examples where the syntax is almost identical to Python's. Aside from the array and scalar sigils @ and $ the above could be Python.
Tens of thousands of non-English speakers have had to learn the meaning of what might as well be meaningless, random sets of symbols (to them) like "class", "import", "while" and "True". If they can do so, perhaps we English-speakers should stop complaining about how hard it is to memorise the meaning of a couple of symbols like ??. Surely its no more difficult than learning the various meanings of ** and [] which we've already done.
*Its just spelling*. If it is a useful and well-defined feature, we'll get used to the spelling soon enough.
That's not to say that spelling is not important *at all*, or that we should never prefer words to symbols. But if the only objection we have is "this is useful but I don't like the spelling so -1" then that's usually a pretty weak argument against the feature.
-- Steve _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

David Mertz wrote:
"APL and Perl territory" means "use lots of punctuation characters in somewhat cryptic ways, often combining several for a distinct semantics."
And APL takes it a step further by freely making up new characters when it runs out of existing ones. At least nobody has suggested doing that in Python yet...

On 2018-07-19 06:38, Steven D'Aprano wrote:
*Its just spelling*. If it is a useful and well-defined feature, we'll get used to the spelling soon enough.
That's not to say that spelling is not important *at all*, or that we should never prefer words to symbols. But if the only objection we have is "this is useful but I don't like the spelling so -1" then that's usually a pretty weak argument against the feature.
But we already have a spelling for the most common case. It is:
x = a if a is not None else b
That is the only use case of any of these operators that is actually common enough for me to care about --- but it's still not common enough to warrant the creation of a new operator, let alone multiple new operators.

On Thu, Jul 19, 2018 at 11:39:50AM -0700, Brendan Barnwell wrote:
On 2018-07-19 06:38, Steven D'Aprano wrote:
*Its just spelling*. If it is a useful and well-defined feature, we'll get used to the spelling soon enough.
That's not to say that spelling is not important *at all*, or that we should never prefer words to symbols. But if the only objection we have is "this is useful but I don't like the spelling so -1" then that's usually a pretty weak argument against the feature.
But we already have a spelling for the most common case. It is:
x = a if a is not None else b
That is the only use case of any of these operators that is actually common enough for me to care about --- but it's still not common enough to warrant the creation of a new operator, let alone multiple new operators.
That's a reasonable argument: "there's no need for this operator because...", albeit it is a subjective argument. (There's no objective rule about how common an operation should be before allowing it to be an operator.)
What's not a reasonable argument is "I see that there could be a need for this operator, but I don't like the spelling so -1 on the entire proposal", which was my point. (One of my points.)

On Thu, Jul 19, 2018, 9:39 AM Steven D'Aprano steve@pearwood.info wrote:
*Its just spelling*. If it is a useful and well-defined feature, we'll get used to the spelling soon enough.
That's not to say that spelling is not important *at all*, or that we should never prefer words to symbols. But if the only objection we have is "this is useful but I don't like the spelling so -1" then that's usually a pretty weak argument against the feature.
I could not disagree more. Spelling is extremely important to readability, and that only goes away partially with familiarity. As a teaching language, moreover, there will always be a late share of Python users who haven't become familiar, and for whom "executable pseudo-code" is a big advantage.
I don't believe Python should be ONLY for those who do not already know it, but neither should it be even half so arcane as this PEP would make it.
Here's are well defined features of the J programming language, for comparison... very compact too:
For example, in the following contrived expression the exclamation point https://en.m.wikipedia.org/wiki/Exclamation_point !refers to three distinct functions:
2!!7!4
The function below can be used to list all of the prime numbers between 1 and R with:
2_&{&/x!/:2_!x}'!R

On Thu, Jul 19, 2018 at 3:39 PM Steven D'Aprano steve@pearwood.info wrote:
Tens of thousands of non-English speakers have had to learn the meaning of what might as well be meaningless, random sets of symbols (to them) like "class", "import", "while" and "True". If they can do so, perhaps we English-speakers should stop complaining about how hard it is to memorise the meaning of a couple of symbols like ??.
"class", "import", "while" and "True" are keywords, not symbols. A symbol is more cryptic than a keyword hence it comes at a higher cost in terms of readability. Which is the reason why conditionals use keywords instead symbols:
- we say "and" instead of "&&" - we say "or" instead of "||" - we say "not" instead of "!" - we say "is" instead of "==="
AFAICT "?" would be the first one breaking this rule for conditionals [1], and not only for them since it can also arbitrarily appear in non-conditionals (x.?y).
[1] I know, we say "==" instead of "equal" and "!=" instead of "different" but the equal sign implies an equality. "?" does not imply "None-ness": it's arbitrary. Furthermore "==" and "!=" are much more common than testing for "None", hence they have more reason to exist.
Surely its no more difficult than learning the various meanings of ** and [] which we've already done.
It is more difficult if you consider that ? has different spellings (?, ??, a?.b, ??=, ...), each spelling with a different meaning.
*Its just spelling*. If it is a useful and well-defined feature, we'll get used to the spelling soon enough.
Such an argument may apply to other languages but not Python. The philosophy of the language is all about readability and beauty since day 1, and the main reason why Python got so successful despite being slow. That's why we have mandatory indentation, to say one. We don't *have to* get used to idioms which can decrease readability. When we do there must be a good reason and the spelling should matter (I remember the debates about decorators' syntax).
-- Giampaolo - http://grodola.blogspot.com

On Sun, Jul 22, 2018 at 01:56:35AM +0200, Giampaolo Rodola' wrote:
On Thu, Jul 19, 2018 at 3:39 PM Steven D'Aprano steve@pearwood.info wrote:
Tens of thousands of non-English speakers have had to learn the meaning of what might as well be meaningless, random sets of symbols (to them) like "class", "import", "while" and "True". If they can do so, perhaps we English-speakers should stop complaining about how hard it is to memorise the meaning of a couple of symbols like ??.
"class", "import", "while" and "True" are keywords, not symbols.
They are only key WORDS if you are an English speaker. If your language doesn't use the Latin script, they don't even look like words. They look like gibberish: ∌≇⊅∇∫
A symbol is more cryptic than a keyword
I was using "symbol" in its most general sense, "a visible sign or representation of an idea". Words, letters, icons, emblems etc are all symbols. Sorry for being unclear.
hence it comes at a higher cost in terms of readability.
It is not clear to me that this is always true even when it comes to non-word symbols. I don't think that "+" is harder to read than "standard_mathematics_operators_numeric_addition" for example.
It is possible to be too verbose as well as too terse. This is why we aren't copying COBOL.
which is the reason why conditionals use keywords instead symbols:
- we say "and" instead of "&&"
[...]
Indeed we do. But we also say:
- we say "+" instead of "add" - we say "//" instead of "floor division" - we say "**" instead of "exponentiation" - we say "&" instead of "bitwise AND" - we say "f( ... )" instead of "call f with arguments ..."
etc. Python has no shortage of non-word symbols:
== != ~ - + * ** / // @ % ^ & | << >> < <= > >= [] ()
[...]
*Its just spelling*. If it is a useful and well-defined feature, we'll get used to the spelling soon enough.
Such an argument may apply to other languages but not Python.
I disagree. I think it applies equally to Python. Python functions and keywords are usually English words, but not always:
def elif
have to be memorised even by English speakers. If we gained a function or even a keyword from Italian, let's say "ripetere", would that really change the nature of Python? I don't think so. English speakers are adaptable, we don't so much borrow words from other languages as chase them down the alley and mug them for new vocabulary.
The same applies to non-word symbols. Look at how quickly and easily people adapted to @ (the commercial "at" sign, a variation of multiplication) as a separator in emails, not to mention #hashtags.
I'll admit that the number and variety of new operators gives me some reason to pause, but for the simplest and most obvious case, the proposed ?? operator, I think that the fears about readability are grossly exaggerated.
The philosophy of the language is all about readability and beauty since day 1, and the main reason why Python got so successful despite being slow.
Let's talk about Python's readability: the first time I saw Python code, I had *no idea* how to read it. This was Python 1.5, long before comprehensions, @ decorator syntax, etc. I could read Pascal, Forth, Hypertalk, BASIC and Fortran, and I looked at Python and couldn't make heads or tails of it. It was full of cryptic idioms like:
for i in range(len(sequence))
which is meaningless until you learn what range does and learn to read it as a for loop:
for i = 0 to len(sequence) - 1
And as for slice notation:
suffix = word[n:]
that might as well have been advanced quantum mechanics transliterated into Korean by a native Navaho speaker for all I could read it. I knew round brackets were used for function calls f() but the only experience I had with square and curly brackets was in mathematics, where they are used as second and third levels of brackets:
x = {[a (b+1)][b (a - 1)] - 1}/2
Now *that* I understood. What on earth was this mysterious {'a': 1} syntax that I keep seeing in Python code?
My initial experience with this bizarre, cryptic language Python was so off-putting that I put it aside for literally three or four years before coming back to it.
Of course I believe that Python is, generally speaking, a beautiful and elegant language, extremely readable. But readability doesn't exist in a vacuum. We still need to learn the symbols of the language, not just operators like ** % and // but words as well. To appreciate the elegance of the language, we need to know the common idioms, as well as the semantics of keywords and functions.
Unfortunately, ?? does have one disadvantage: even though it is used as an operator in a number of languages, Google doesn't seem to have indexed it. Goggling for "??" is unhelpful. But then googling for "@ python" is similarly unhelpful. Clearly decorators was a mistake. /s
That's why we have mandatory indentation, to say one. We don't *have to* get used to idioms which can decrease readability. When we do there must be a good reason and the spelling should matter (I remember the debates about decorators' syntax).
Exactly. Years later, do we still think that @decorator syntax is unreadable and unnecessary?
In my opinion, writing
expression if expression is None else default
is the *opposite* of Pythonic, it is verbose and the DRY violation is inelegant (as well as inefficient). I'd much rather use:
expression ?? default
although with PEP 572 approved, there is an alternative:
temp := expression if temp is None else default
which avoids the DRY violation but is more verbose than even the first version.

On Sun, Jul 22, 2018 at 11:54 AM, Steven D'Aprano steve@pearwood.info wrote:
If we gained a function or even a keyword from Italian, let's say "ripetere", would that really change the nature of Python? I don't think so. English speakers are adaptable, we don't so much borrow words from other languages as chase them down the alley and mug them for new vocabulary.
"lambda" would like to have a word with you. Or maybe a letter. I'm not really sure.
ChrisA

On 22 July 2018 at 02:54, Steven D'Aprano steve@pearwood.info wrote:
I'll admit that the number and variety of new operators gives me some reason to pause, but for the simplest and most obvious case, the proposed ?? operator, I think that the fears about readability are grossly exaggerated.
Certainly *my* concerns about readability are around the other proposed operators (?[ and ?. in particular).
In my opinion, writing
expression if expression is None else default
is the *opposite* of Pythonic, it is verbose and the DRY violation is inelegant (as well as inefficient). I'd much rather use:
expression ?? default
Agreed. But the PEP proposes three other operators, and it's not at all clear to me that those are such clear wins.
Paul

To get rid of the two other ( ?. And ?[] ), we could also define getitem and getattr for None to always return None...;-)
I'm joking, although such an "absorbing" None may have been a good choice when None was introduced, and maybe a way to do an absorbing-None per-statement maybe nice...Nice enough to add more subtleties to python? I don't think so, but it would be readable...

On Sun, Jul 22, 2018 at 3:55 AM Steven D'Aprano steve@pearwood.info wrote:
Indeed we do. But we also say:
- we say "+" instead of "add"
- we say "//" instead of "floor division"
- we say "**" instead of "exponentiation"
- we say "&" instead of "bitwise AND"
- we say "f( ... )" instead of "call f with arguments ..."
[...]
I don't think that "+" is harder to read than
"standard_mathematics_operators_numeric_addition"
Please let's drop the argument that + - * / = and ? are the same. They clearly are not. Anybody learned those symbols at elementary schools, all programming languages have them and using math in programming is common enough to justify a symbol over a keyword. "a + b" is literally just an addition and nothing else. The "?" variants have multiple meanings, spellings and implications:
- "a ?? b" means "b is chosen over a if a is None"
- "a ??= b" means "a is set to b if a is None"
- "a?.b" means "a.b is executed but only if a is not None"
- "a?[2] ?? 3" means "index 2 of list a is picked up if a is not None, else use 3"
"a?.b"and "a?[2]" in particular go way beyond the mere "it's not pretty" argument which, I concur, can be subjective, as you don't know where evaluation stops. Even "a ??= b" goes beyond that as it introduces yet another assignment operator (the third, as we now have = and :=). So again, I don't think it's fair to dismiss the whole thing as "it's just another symbol" or "it's like a + b".
As for bitwise operators: they are kinda obscure and low-levelish and when I bump into them I still have to pause to reason what's going on. The difference with ? though is that you basically have no other way to do the same thing. Also they are much more rare and also are present in many other languages since... forever.

On 22 July 2018 at 11:13, Giampaolo Rodola' g.rodola@gmail.com wrote:
- "a?[2] ?? 3" means "index 2 of list a is picked up if a is not None, else
use 3"
Actually, doesn't it mean
if a is not None, pick up index 2 of the list. If a is None, OR IF a[2] IS NONE, then use 3. If a is None but a[2] is not None, use a[2].
?
Which is subtly different, and probably at least as prone to "accidental" errors as some of the constructs the None-aware operators are intended to replace.
Paul

On Sun, Jul 22, 2018 at 12:26 PM Paul Moore p.f.moore@gmail.com wrote:
On 22 July 2018 at 11:13, Giampaolo Rodola' g.rodola@gmail.com wrote:
- "a?[2] ?? 3" means "index 2 of list a is picked up if a is not None,
else
use 3"
Actually, doesn't it mean
if a is not None, pick up index 2 of the list. If a is None, OR IF a[2] IS NONE, then use 3. If a is None but a[2] is not None, use a[2].
?
Which is subtly different, and probably at least as prone to "accidental" errors as some of the constructs the None-aware operators are intended to replace.
Paul
Yes, I think you're right.

Except that the third possibility is not possible...if a is None, a[2] will throw an exception... For now at least ;-)

On Sun, Jul 22, 2018 at 12:13:04PM +0200, Giampaolo Rodola' wrote:
On Sun, Jul 22, 2018 at 3:55 AM Steven D'Aprano steve@pearwood.info wrote:
[...]
I don't think that "+" is harder to read than "standard_mathematics_operators_numeric_addition"
Please let's drop the argument that + - * / = and ? are the same.
In context, the argument was that non-word symbols are not always worse than words, not that all symbols are "the same".
Obviously symbols that we use regularly will be more familiar and easier to recognise than symbols we use rarely. I still have to make a conscious action to recall which of ∩ and ∪ is set union and intersection, and don't ask me what set symmetric difference is. And even after 20 years of Python I still occasionally write ^ for exponentiation instead of **.
But if we insist that every symbol we use is instantly recognisable and intuitively obvious to every programmer, we're putting the bar for acceptance impossibly high.
They clearly are not. Anybody learned those symbols at elementary schools, all programming languages have them and using math in programming is common enough to justify a symbol over a keyword. "a + b" is literally just an addition and nothing else.
That's not quite correct: '+' in Python is used for both addition and sequence concatenation. And with operator overloading, it can be anything at all. But it is *usually* addition, or concatenation.
I don't know what it is like in your country, but here in Australia, I don't know any school that teaches * for multiplication except in programming classes, which is not a core subject. The usual symbol we have for multiplication is × and sometimes ⋅ (dot operator).
This is a good point: after learning * for multiplication, it becomes so familiar that most of us forget that we haven't been using it forever. It becomes second-nature. In the same way that @ for decorators has become second nature, or slice notation. Both of which are terribly mysterious to people just starting out.
We shouldn't judge proposals on how mysterious they are the first time we see them, because everything is mysterious the first time. We should try to look forward to when we've seen them ten or twenty times. How will the "usefulness versus surprise" trade-off appear when, let's say, 50% of the surprise has been worn away with experience?
As a community, we're risk-adverse. I understand why we should be conservative in what we add to the language (once added, it cannot easily be removed if it turns out to be a mistake) but on Python-Ideas we regularly demand levels of obviousness and "readability" that existing syntax does not reach.
(For example, the dot operator for attribute access fails the "syntax should not look like grit on Tim's monitor" test.)
I believe that it is fine to prefer that new syntax is no harder to learn or use than (for example) // or ** (neither of which is taught in maths class), or slice notation. But I don't think it is fair, or desirable, to demand levels of readability greater than what we already have in the less common corners of the language.
All the obvious operators are already in use. Anything we add now is going to be a little bit niche, a little bit unusual. It's not like we're going to suddenly realise we forgot to include a "subtract" operator. So it is perfectly natural that any new operators won't be as familiar as + or - operators. But it might become as familiar as ** or << operators, or some of the less common regex patterns, and that's okay. Not everything needs to be as recognisable as the plus sign.
The "?" variants have multiple meanings, spellings and implications:
[...]
Indeed. And I think we ought to think carefully about the benefits and costs of all of those variants separately.
To me, the ?? operator seems like a clear and obvious win. The other variants are more complex and the benefit is not as obvious to me, so I haven't decided where I stand on them.

On Sun, Jul 22, 2018 at 10:10 PM, Steven D'Aprano steve@pearwood.info wrote:
As a community, we're risk-adverse. I understand why we should be conservative in what we add to the language (once added, it cannot easily be removed if it turns out to be a mistake) but on Python-Ideas we regularly demand levels of obviousness and "readability" that existing syntax does not reach.
(For example, the dot operator for attribute access fails the "syntax should not look like grit on Tim's monitor" test.)
My understanding of that test is, more or less: "syntax should not be such that grit on Tim's monitor can make it ambiguous". Which would mean that attribute access does pass, since there's no logical meaning for "list sort()" or "random randint" with just a space between them. But otherwise, yes, I absolutely agree.
ChrisA

On Sun, Jul 22, 2018, 8:11 AM Steven D'Aprano steve@pearwood.info wrote:
On Sun, Jul 22, 2018 at 12:13:04PM +0200, Giampaolo Rodola' wrote:
Please let's drop the argument that + - * / = and ? are the same.
We shouldn't judge proposals on how mysterious they are the first time we see them, because everything is mysterious the first time. All the obvious operators are already in use. Anything we add now is going to be a little bit niche, a little bit unusual. It's not like we're going to suddenly realise we forgot to include a "subtract" operator. So it is perfectly natural that any new operators won't be as familiar as + or - operators. But it might become as familiar as ** or << operators, or some of the less common regex patterns,
I'm glad you've moved to better acknowledging the relative familiarity of symbols. This simple can't be brushed away as a non-concern, as you did in prior posts.
We're not promoting or reaching Python to Martians with entirely different systems of writing. Nor, for that matter, even to humans of the 14th entirety CE, before some basic math symbology became universal.
+, -, /, <, >, <=, >= are taught to small children throughout the world, over the last 5 or more generations (the compound inequalities drawn in combined glyphs, but iconically very close). Yes they are characters with no inherent meaning, but they are as well known as the sounds of Latin letters, or probably more. '*' is a bit more computer specific. 'x' or some nicer typographic variation is taught to children. But '*' is close to universal in computer languages. '**' is funny. It's used in many computer languages, but ^ is also a commonplace and probably more iconic for "superscript", the actual middle school notation on exponentiation.
Actually, I have a ten Pythin line command-line calculator that accepts 'x' and '^' in their "obvious" meanings.
The various question mark operators are only reasonable to compare to e.g. @ for either __matmul__ or introducing decorators. Or to bitwise operators like |, ~, ^, <<. But in both of those areas, the programs are ones dealing with specialized domains where "experts" are necessary to understand the semantics, not only the iconography.
Here the question mark operators are very different. They are proposed as general constructs with no particularly special area of use. Programmers or hobbyists who come from different or no programming language would encounter them. Even as a scientific Python export, my linear algebra is weak... if I see code with a bunch of '@' marks I know I'm not really going to understand it, but before I look I'm warned about the domain difficulties by the comments, function names, and so on.
The question marks are basic—though obscure and hard to reason about–flow control.
To me, the ?? operator seems like a clear and obvious win. The other
variants are more complex and the benefit is not as obvious to me, so I haven't decided where I stand on them.
Yes, that is the only one I'm merely -0 on. The others I'm -1000. Even experienced developers in this thread keep tripping over the actual semantics of those others, and the PEP proposer is unsure about what's best for some edge behaviors that are actually really important were they to be adopted.
The most any of these can possibly do is save a few characters in a ternary line, or perhaps a few lines of if/elif that make the intention far more obvious.

The ?? operator is probably the less scary one regarding legibility, and in guessing (or remembering) what it exactly does... Well, at least I think I understand what it does exactly, but if I'm not wrong there, what it does is also quite simple and minimal.
A function returning it's first non-None argument (or None, if all args are None) will provide the same functionality, with not much typing. You have parenthesis for the call, but you will probably need them anyway to group things, for correcting precedence, or helping the reader to parse your expression even if precedence was right. You have an extra call, so ?? may be more efficient...maybe.
Is that a reason enough, together with a few letters saved typing, to introduce ?? ? Not for me...

On Sun, Jul 22, 2018 at 11:22 PM, Grégory Lielens gregory.lielens@gmail.com wrote:
The ?? operator is probably the less scary one regarding legibility, and in guessing (or remembering) what it exactly does... Well, at least I think I understand what it does exactly, but if I'm not wrong there, what it does is also quite simple and minimal.
A function returning it's first non-None argument (or None, if all args are None) will provide the same functionality, with not much typing. You have parenthesis for the call, but you will probably need them anyway to group things, for correcting precedence, or helping the reader to parse your expression even if precedence was right. You have an extra call, so ?? may be more efficient...maybe.
Is that a reason enough, together with a few letters saved typing, to introduce ?? ? Not for me...
You forget that the operator will *short-circuit*. It will not evaluate the second argument if the first argument is None. You cannot do this with a function, other than with a hack like a lambda function.
THAT is reason enough for an operator.
ChrisA

Short circuit if the first argument is NOT None, I guess? ;-)
Yes, so a short circuit is sometimes good. Not often imho, for a default triggered by None, but sometimes... In the case it is, do you want it to be hidden in an expression? Usually it would be better to draw attention, when the default is either costly to compute, or worse, it's evaluation have side effect. I would use an old fashioned if in those cases...

On Sun, Jul 22, 2018 at 11:26:15PM +1000, Chris Angelico wrote:
You forget that the operator will *short-circuit*. It will not evaluate the second argument if the first argument is None. You cannot do this with a function, other than with a hack like a lambda function.
We keep running up to this issue. What if there was a language supported, non-hackish way to officially delay evaluation of expressions until explicitly requested?
That would allow us to write a function:
func(arg, default = delayed-expression)
and avoid new punctuation. Then the only bike-shedding will be where the function should live and what it is called and how many arguments it ought to take...

On 23Jul2018 0151, Steven D'Aprano wrote:
What if there was a language supported, non-hackish way to officially delay evaluation of expressions until explicitly requested?
The current spelling for this is "lambda: delayed-expression" and the way to request the value is "()". :)
(I'm not even being that facetious here. People ask for delayed expressions all the time, and it's only 7 characters, provided the callee knows they're getting it, and the semantics are already well defined and likely match what you want.)
Cheers, Steve

On Mon, Jul 23, 2018 at 10:48:23AM +0100, Steve Dower wrote:
On 23Jul2018 0151, Steven D'Aprano wrote:
What if there was a language supported, non-hackish way to officially delay evaluation of expressions until explicitly requested?
The current spelling for this is "lambda: delayed-expression" and the way to request the value is "()". :)
(I'm not even being that facetious here. People ask for delayed expressions all the time, and it's only 7 characters, provided the callee knows they're getting it, and the semantics are already well defined and likely match what you want.)
I know you not being facetious, and delaying computation through a function call is not an awful solution. But its not a great solution either. Contrast the elegance of syntax with delayed evaluation:
1/x if x != 0 else func(y)
versus the lambda solution:
if_else(lambda: 1/x, x != 0, lambda: func(y))()
For clarity, or perhaps the opposite *wink* I've kept the same order of arguments. It's not just the extra seven characters (plus spaces) per delayed expression, or the extra parentheses at the end to force evaluation, but the ease at which we can forget and write this:
if_else(1/x, x != 0, func(y))
Anyway, it was just an idle thought, not in a state to compete with this PEP. And even if it were, I'd still prefer to see at least ?? as a dedicated operator rather than a function.

On 2018-07-22 09:01:58 -0400, David Mertz wrote:
On Sun, Jul 22, 2018, 8:11 AM Steven D'Aprano steve@pearwood.info wrote: To me, the ?? operator seems like a clear and obvious win. The other variants are more complex and the benefit is not as obvious to me, so I haven't decided where I stand on them.
Yes, that is the only one I'm merely -0 on. The others I'm -1000.
For me it's the opposite. ?? is +0: While I use the equivalent // in Perl quite frequently, the benefit isn't all that great.
But ?. and ?[] are really useful.
Sequences like request.context.user.email occur quite frequently in code, and I find
request?.context?.user?.email
much more readable than
email = None context = request.context if context is not None: user = context.user if user is not None: email = user.email
Note that
request and request.context and request.context.user and request.context.user.email
is not equivalent even if you assume that None is the only possible falsey value in this context. It evaluates request 4 times, request.context 3 times, and request.context.user 2 times.
hp

On Sun, Jul 22, 2018 at 2:10 PM Steven D'Aprano steve@pearwood.info wrote:
On Sun, Jul 22, 2018 at 12:13:04PM +0200, Giampaolo Rodola' wrote:
On Sun, Jul 22, 2018 at 3:55 AM Steven D'Aprano steve@pearwood.info wrote:
[...]
I don't think that "+" is harder to read than "standard_mathematics_operators_numeric_addition"
Please let's drop the argument that + - * / = and ? are the same.
[...] But if we insist that every symbol we use is instantly recognisable and intuitively obvious to every programmer, we're putting the bar for acceptance impossibly high.
I personally don't find "a ?? b" too bad (let's say I'm -0 about it) but idioms such as "a?.b", "a ??= b" and "a?[3] ?? 4" look too Perl-ish to me, non pythonic and overall not explicit, no matter what the chosen symbol is gonna be. It looks like they want to do too much for the sole reason of allowing people to write more compact code and save a few lines. Compact code is not necessarily a good thing, especially when it comes at the expense of readability and explicitness, as I think is this case.
All the obvious operators are already in use. Anything we add now is going to be a little bit niche, a little bit unusual.
That's basically my point. And I know I'll sound very conservative here but to me that is a valid enough reason to not take action or be extremely careful at the very least. Not to state the obvious but it's not that we *have to* use the remaining unused symbols just because they're there.

On Sun, Jul 22, 2018 at 11:35 PM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 2:10 PM Steven D'Aprano steve@pearwood.info wrote:
On Sun, Jul 22, 2018 at 12:13:04PM +0200, Giampaolo Rodola' wrote:
On Sun, Jul 22, 2018 at 3:55 AM Steven D'Aprano steve@pearwood.info wrote:
[...]
I don't think that "+" is harder to read than "standard_mathematics_operators_numeric_addition"
Please let's drop the argument that + - * / = and ? are the same.
[...] But if we insist that every symbol we use is instantly recognisable and intuitively obvious to every programmer, we're putting the bar for acceptance impossibly high.
I personally don't find "a ?? b" too bad (let's say I'm -0 about it) but idioms such as "a?.b", "a ??= b" and "a?[3] ?? 4" look too Perl-ish to me, non pythonic and overall not explicit, no matter what the chosen symbol is gonna be.
Please explain what is not explicit about it. "a?.b" is very simple and perfectly explicit: it means "None if a is None else a.b". What does "not explicit" mean, other than "I don't like this code"?
ChrisA

On Sun, Jul 22, 2018 at 3:38 PM Chris Angelico rosuav@gmail.com wrote:
On Sun, Jul 22, 2018 at 11:35 PM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 2:10 PM Steven D'Aprano steve@pearwood.info wrote:
On Sun, Jul 22, 2018 at 12:13:04PM +0200, Giampaolo Rodola' wrote:
On Sun, Jul 22, 2018 at 3:55 AM Steven D'Aprano steve@pearwood.info wrote:
[...]
I don't think that "+" is harder to read than "standard_mathematics_operators_numeric_addition"
Please let's drop the argument that + - * / = and ? are the same.
[...] But if we insist that every symbol we use is instantly recognisable and intuitively obvious to every programmer, we're putting the bar for acceptance impossibly high.
I personally don't find "a ?? b" too bad (let's say I'm -0 about it) but idioms such as "a?.b", "a ??= b" and "a?[3] ?? 4" look too Perl-ish to me, non pythonic and overall not explicit, no matter what the chosen symbol is gonna be.
Please explain what is not explicit about it. "a?.b" is very simple and perfectly explicit: it means "None if a is None else a.b". What does "not explicit" mean, other than "I don't like this code"?
I find it less explicit mainly because it does 3 things at once: check if attribute is None, use it if it's not None and continue the evaluation from left to right. I find that logic to be more explicit when living on different lines or is clearly delimited by keywords and spaces. ? has no spaces, it's literally "variable names interrupted by question marks" and evaluation can stop at any time while scanning the line from left to right. Multiple "?" can live on the same line so that's incentive to write one-liners, really, and to me one-liners are always less explicit than the same logic split on multiple lines.

On Mon, Jul 23, 2018 at 1:09 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 3:38 PM Chris Angelico rosuav@gmail.com wrote:
On Sun, Jul 22, 2018 at 11:35 PM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 2:10 PM Steven D'Aprano steve@pearwood.info wrote:
On Sun, Jul 22, 2018 at 12:13:04PM +0200, Giampaolo Rodola' wrote:
On Sun, Jul 22, 2018 at 3:55 AM Steven D'Aprano steve@pearwood.info wrote:
[...]
I don't think that "+" is harder to read than "standard_mathematics_operators_numeric_addition"
Please let's drop the argument that + - * / = and ? are the same.
[...] But if we insist that every symbol we use is instantly recognisable and intuitively obvious to every programmer, we're putting the bar for acceptance impossibly high.
I personally don't find "a ?? b" too bad (let's say I'm -0 about it) but idioms such as "a?.b", "a ??= b" and "a?[3] ?? 4" look too Perl-ish to me, non pythonic and overall not explicit, no matter what the chosen symbol is gonna be.
Please explain what is not explicit about it. "a?.b" is very simple and perfectly explicit: it means "None if a is None else a.b". What does "not explicit" mean, other than "I don't like this code"?
I find it less explicit mainly because it does 3 things at once: check if attribute is None, use it if it's not None and continue the evaluation from left to right. I find that logic to be more explicit when living on different lines or is clearly delimited by keywords and spaces. ? has no spaces, it's literally "variable names interrupted by question marks" and evaluation can stop at any time while scanning the line from left to right. Multiple "?" can live on the same line so that's incentive to write one-liners, really, and to me one-liners are always less explicit than the same logic split on multiple lines.
Ah, I see what you mean. Well, think about what actually happens when you write "lst.sort()". In terms of "hidden behaviour", there is far FAR more of it in existing syntax than in the new proposals. Which is back to what Steven said: people demand such a high bar for new syntax that few existing pieces of syntax would pass it.
ChrisA

On Sun, Jul 22, 2018 at 10:01 PM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 1:09 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 3:38 PM Chris Angelico rosuav@gmail.com wrote: I find it less explicit mainly because it does 3 things at once: check if attribute is None, use it if it's not None and continue the evaluation from left to right. I find that logic to be more explicit when living on different lines or is clearly delimited by keywords and spaces. ? has no spaces, it's literally "variable names interrupted by question marks" and evaluation can stop at any time while scanning the line from left to right. Multiple "?" can live on the same line so that's incentive to write one-liners, really, and to me one-liners are always less explicit than the same logic split on multiple lines.
Ah, I see what you mean. Well, think about what actually happens when you write "lst.sort()". In terms of "hidden behaviour", there is far FAR more of it in existing syntax than in the new proposals.
I am not sure I'm following you (what does lst.sort() have to do with "?"?).
Which is back to what Steven said: people demand such a high bar for new syntax that few existing pieces of syntax would pass it.
Probably. That's what happens when a language is mature. Personally I don't think that's a bad thing.

On Sun, 22 Jul 2018 22:43:15 +0200 "Giampaolo Rodola'" g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 10:01 PM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 1:09 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 3:38 PM Chris Angelico rosuav@gmail.com wrote: I find it less explicit mainly because it does 3 things at once: check if attribute is None, use it if it's not None and continue the evaluation from left to right. I find that logic to be more explicit when living on different lines or is clearly delimited by keywords and spaces. ? has no spaces, it's literally "variable names interrupted by question marks" and evaluation can stop at any time while scanning the line from left to right. Multiple "?" can live on the same line so that's incentive to write one-liners, really, and to me one-liners are always less explicit than the same logic split on multiple lines.
Ah, I see what you mean. Well, think about what actually happens when you write "lst.sort()". In terms of "hidden behaviour", there is far FAR more of it in existing syntax than in the new proposals.
I am not sure I'm following you (what does lst.sort() have to do with "?"?).
Which is back to what Steven said: people demand such a high bar for new syntax that few existing pieces of syntax would pass it.
Probably. That's what happens when a language is mature. Personally I don't think that's a bad thing.
Agreed with Giampaolo. The opportunities for syntax additions should become rarer and rarer.
Regards
Antoine.

On Mon, Jul 23, 2018 at 6:43 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 10:01 PM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 1:09 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 3:38 PM Chris Angelico rosuav@gmail.com wrote: I find it less explicit mainly because it does 3 things at once: check if attribute is None, use it if it's not None and continue the evaluation from left to right. I find that logic to be more explicit when living on different lines or is clearly delimited by keywords and spaces. ? has no spaces, it's literally "variable names interrupted by question marks" and evaluation can stop at any time while scanning the line from left to right. Multiple "?" can live on the same line so that's incentive to write one-liners, really, and to me one-liners are always less explicit than the same logic split on multiple lines.
Ah, I see what you mean. Well, think about what actually happens when you write "lst.sort()". In terms of "hidden behaviour", there is far FAR more of it in existing syntax than in the new proposals.
I am not sure I'm following you (what does lst.sort() have to do with "?"?).
The "." in "lst.sort" is an operator. How much hidden behaviour is there in that? Do you actually even know every possible thing that can happen? Don't feel bad if you don't - it's not an indictment of your quality as a programmer, but an acknowledgement that Python's attribute access is incredibly complicated.
Which is back to what Steven said: people demand such a high bar for new syntax that few existing pieces of syntax would pass it.
Probably. That's what happens when a language is mature. Personally I don't think that's a bad thing.
I do. It means people place crazily high demands on new proposals. Imagine if we were talking about people, rather than features in a language; imagine if, to join the Warriors Guild, you had to first slay a red dragon with nothing but a rusty dagger, despite none of the existing members having done so. Is that reasonable to ask? Can you say "well, the guild is mature now, so yeah, it's a good thing"?
ChrisA

On Mon, 23 Jul 2018 06:53:53 +1000 Chris Angelico rosuav@gmail.com wrote:
Which is back to what Steven said: people demand such a high bar for new syntax that few existing pieces of syntax would pass it.
Probably. That's what happens when a language is mature. Personally I don't think that's a bad thing.
I do. It means people place crazily high demands on new proposals.
"Crazy" is just a personal judgement. I do think high (and even very high) demands are entirely justified by the language's maturity.
Imagine if we were talking about people, rather than features in a language; imagine if, to join the Warriors Guild, you had to first slay a red dragon with nothing but a rusty dagger, despite none of the existing members having done so.
This is the silliest analogy I have seen on this list for a very long time.
If someone thinks getting their PEP accepted is like belonging to a « Warriors Guild » (whatever that is in the real world), I'd question their motivations for contributing.
Regards
Antoine.

On Sun, Jul 22, 2018, 4:56 PM Chris Angelico rosuav@gmail.com wrote:
It means people place crazily high demands on new proposals.
I think the bar has been much too low for introducing new features over the last 5 years or so. Internal changes like the new dictionary implementation are fine, but user-facing changes should be exceedingly rare in the base language. This proposal doesn't come remotely close to such a good standard.
I was consistently +0 on the 572 idea, as long as its worst excesses were trimmed, as in the final PEP. But after reading this discussion, I almost reconsider that opinion since its social effect seems to be a move towards accepting wild and unnecessary changes that "might be useful" for a few unusual programming patterns.
Honestly, if you want Perl, and as many different ways to approach each problem as there are programmers (each with their own syntax niche), that language continues to be fully working. I'm not even writing that to be dismissive... There are actually some pretty and interesting ideas over there. But I very much want Python not to be like that, and to do most of my work in a readable language with as few special characters/signils as feasible.

On Sun, Jul 22, 2018 at 10:55 PM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 6:43 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 10:01 PM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 1:09 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 3:38 PM Chris Angelico rosuav@gmail.com wrote: I find it less explicit mainly because it does 3 things at once: check if attribute is None, use it if it's not None and continue the evaluation from left to right. I find that logic to be more explicit when living on different lines or is clearly delimited by keywords and spaces. ? has no spaces, it's literally "variable names interrupted by question marks" and evaluation can stop at any time while scanning the line from left to right. Multiple "?" can live on the same line so that's incentive to write one-liners, really, and to me one-liners are always less explicit than the same logic split on multiple lines.
Ah, I see what you mean. Well, think about what actually happens when you write "lst.sort()". In terms of "hidden behaviour", there is far FAR more of it in existing syntax than in the new proposals.
I am not sure I'm following you (what does lst.sort() have to do with "?"?).
The "." in "lst.sort" is an operator. How much hidden behaviour is there in that? Do you actually even know every possible thing that can happen? Don't feel bad if you don't - it's not an indictment of your quality as a programmer, but an acknowledgement that Python's attribute access is incredibly complicated.
I'm going to engage into a discussion about the analogy between "?" and "." because simply there is none. It doesn't prove anything except that you're not really interested in having a serious discussion about the pros and cons of this PEP: you just want it to happen no matter what.
Imagine if we were talking about people, rather than features in a language; imagine if, to join the Warriors Guild, you had to first slay a red dragon with nothing but a rusty dagger, despite none of the existing members having done so. Is that reasonable to ask? Can you say "well, the guild is mature now, so yeah, it's a good thing"?
Ditto.

On Sun, Jul 22, 2018 at 11:51 PM Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 10:55 PM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 6:43 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 10:01 PM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 1:09 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 3:38 PM Chris Angelico rosuav@gmail.com wrote: I find it less explicit mainly because it does 3 things at once: check if attribute is None, use it if it's not None and continue the evaluation from left to right. I find that logic to be more explicit when living on different lines or is clearly delimited by keywords and spaces. ? has no spaces, it's literally "variable names interrupted by question marks" and evaluation can stop at any time while scanning the line from left to right. Multiple "?" can live on the same line so that's incentive to write one-liners, really, and to me one-liners are always less explicit than the same logic split on multiple lines.
Ah, I see what you mean. Well, think about what actually happens when you write "lst.sort()". In terms of "hidden behaviour", there is far FAR more of it in existing syntax than in the new proposals.
I am not sure I'm following you (what does lst.sort() have to do with "?"?).
The "." in "lst.sort" is an operator. How much hidden behaviour is there in that? Do you actually even know every possible thing that can happen? Don't feel bad if you don't - it's not an indictment of your quality as a programmer, but an acknowledgement that Python's attribute access is incredibly complicated.
I'm going to engage into a discussion [...]
s/I'm going/I'm not going

On Mon, Jul 23, 2018 at 7:51 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 10:55 PM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 6:43 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 10:01 PM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 1:09 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 3:38 PM Chris Angelico rosuav@gmail.com wrote: I find it less explicit mainly because it does 3 things at once: check if attribute is None, use it if it's not None and continue the evaluation from left to right. I find that logic to be more explicit when living on different lines or is clearly delimited by keywords and spaces. ? has no spaces, it's literally "variable names interrupted by question marks" and evaluation can stop at any time while scanning the line from left to right. Multiple "?" can live on the same line so that's incentive to write one-liners, really, and to me one-liners are always less explicit than the same logic split on multiple lines.
Ah, I see what you mean. Well, think about what actually happens when you write "lst.sort()". In terms of "hidden behaviour", there is far FAR more of it in existing syntax than in the new proposals.
I am not sure I'm following you (what does lst.sort() have to do with "?"?).
The "." in "lst.sort" is an operator. How much hidden behaviour is there in that? Do you actually even know every possible thing that can happen? Don't feel bad if you don't - it's not an indictment of your quality as a programmer, but an acknowledgement that Python's attribute access is incredibly complicated.
I'm [not] going to engage into a discussion about the analogy between "?" and "." because simply there is none. It doesn't prove anything except that you're not really interested in having a serious discussion about the pros and cons of this PEP: you just want it to happen no matter what.
That's because the dot already exists in the language, and you have become so accustomed to it that you don't see it any more. You've just proven my point.
ChrisA

On Mon, Jul 23, 2018 at 12:08 AM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 7:51 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 10:55 PM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 6:43 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 10:01 PM Chris Angelico rosuav@gmail.com wrote:
On Mon, Jul 23, 2018 at 1:09 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
On Sun, Jul 22, 2018 at 3:38 PM Chris Angelico rosuav@gmail.com wrote: I find it less explicit mainly because it does 3 things at once: check if attribute is None, use it if it's not None and continue the evaluation from left to right. I find that logic to be more explicit when living on different lines or is clearly delimited by keywords and spaces. ? has no spaces, it's literally "variable names interrupted by question marks" and evaluation can stop at any time while scanning the line from left to right. Multiple "?" can live on the same line so that's incentive to write one-liners, really, and to me one-liners are always less explicit than the same logic split on multiple lines.
Ah, I see what you mean. Well, think about what actually happens when you write "lst.sort()". In terms of "hidden behaviour", there is far FAR more of it in existing syntax than in the new proposals.
I am not sure I'm following you (what does lst.sort() have to do with "?"?).
The "." in "lst.sort" is an operator. How much hidden behaviour is there in that? Do you actually even know every possible thing that can happen? Don't feel bad if you don't - it's not an indictment of your quality as a programmer, but an acknowledgement that Python's attribute access is incredibly complicated.
I'm [not] going to engage into a discussion about the analogy between "?" and "." because simply there is none. It doesn't prove anything except that you're not really interested in having a serious discussion about the pros and cons of this PEP: you just want it to happen no matter what.
That's because the dot already exists in the language, and you have become so accustomed to it that you don't see it any more. You've just proven my point.
You're back at "since we have X that justifies the addition of Y" [1] and AFAICT that's the only argument you have provided so far in a 100+ messages discussion.
[1] https://mail.python.org/pipermail/python-ideas/2018-July/052068.html

On Mon, Jul 23, 2018 at 12:59:20AM +0200, Giampaolo Rodola' wrote:
You're back at "since we have X that justifies the addition of Y" [1] and AFAICT that's the only argument you have provided so far in a 100+ messages discussion.
The PEP itself justifies the addition of Y.
Chris' argument, and mine, is countering *your* arguments in opposition. It is not a positive argument for Y, since the PEP does an admirable job at that. It is a response to the FUD (Fear, Uncertainty, Doubt) that Python is becoming "Perl-like", or even more ludicrously, like APL and J. (That's David Mertz' position.)
To my eyes, your opposition basically comes down to "it is new, and I don't like it because it is new". It looks to me like pure resistance to change simply due to dislike of change. See also David's response that he is against the changes made to Python over the last five years.
The concrete arguments you are making against this change apply equally to existing features. If your (and others') arguments are valid now, they would have been equally valid back in Python 1.5. If punctuation is unreadable and Perlish, so is "." and ":" punctuation. If ?? is bad because it is "implicit", then so is import or sorted.
Arguments by slogan ("explicit is better than implicit") are rarely good arguments -- especially when nobody seems to be able to define implicit and explicit explicitly.
As for the argument that Python is "mature" and so we should resist change, we could have said the same thing going all the way back to Python 1.5 and probably beyond. Have you tried using Python 1.5 recently? Or even Python 2.4. I have. I wonder how I managed to get anything useful done.
Some of us think that Python 3.6 or 3.7 is fantastic and so good that every addition to the language can only make it worse. I suggest that when we have Python 4.5 or 4.6, we will wonder how on earth we managed to get any useful work done with Python 3.7.
The Python community has always been conservative and resistant to change, but the level of conservativeness is now pushing towards fear of change rather than justifiable caution about adding new features that cannot easily be reverted.

On Mon, Jul 23, 2018 at 2:38 AM Steven D'Aprano steve@pearwood.info wrote:
On Mon, Jul 23, 2018 at 12:59:20AM +0200, Giampaolo Rodola' wrote:
You're back at "since we have X that justifies the addition of Y" [1] and AFAICT that's the only argument you have provided so far in a 100+ messages discussion.
The PEP itself justifies the addition of Y.
This thread seems to be unnecessarily heated.
Other languages have these operators, and so they aren't a wild idea. That said, from what I've seen, Swift optionals are very different things, and Python really has nothing like them.
In Python, is None really special enough to need an operator like this?
One issue for me is that the trivial case is already a one-liner:
if a is None: a = 10
And it works for other things too:
if a is -1: a = 10
if not a: a = 10
Again, is None special enough in Python to need this operator? I don't know.
And that leads to a simple question: how many times does this actually occur in real-world by python code? -- i.e. how many times do I want to check the value of an existing label, and, finding it is None (and None specifically), then assign it a value?
What does a scan through the existing core library say?
I'm +0 on this proposal.
Best wishes,
N

On Monday, July 23, 2018 at 8:24:45 AM UTC+2, Nicholas Cole wrote:
And that leads to a simple question: how many times does this actually
occur in real-world by python code? -- i.e. how many times do I want to check the value of an existing label, and, finding it is None (and None specifically), then assign it a value?
The PEP present a few examples.
I think the compactness and clarity is really gained when doing a descent into an "attribute tree", where any (or selected) members can be None, like getting back to the example by Steven (spell-corrected, and expanded):
meal = obj?.spam?.eggs.tomato?.cheese ?? "Mozzarella"
Where, to put the proposal in even better light, I have assumed that eggs instances always have a tomato attributes (i.e. are never None).
This is indeed much more compact that the current version, and arguably more readable (it's easier to parse once you know the syntax...but you have to know the special syntax that will be used for this kind of stuff only). The more you deepen the attribute access, the more you gain, but of course deep attribute access is not so common. The new operators may make deep attribute hierarchies (or deeply nested dicts/lists) slightly more common, and there also I do not think it's a good thing.
For me the issue is that you gain little, each operator is very limited in scope, but at the same it's a operator, and those are introduced very carefully in Python (the resistance to line noise is high, it's one of the defining aspect of Python since forever).
Each of them do not do enough IMHO, it's too special case and do not unlock especially powerfull/elegant/generalization reuse of other part of Python syntax. If you add something, it should play nice with the existing feature and solve nagging issues that broaden its appeal, or be powerful/general enough to justify it's existence on its own, or be so self-evident for a python user that it feels it's solving a bug.
It's not the case for ?? and friends imho, far from it.
Also, what happen if you want to modify your deep nested tree, not only access it?
obj?.spam?.eggs.tomato?.cheese = "Mozzarella"
will not work, and the proposal will not really help there (Or will it? I am not the proposer, I am strongly against it, so I could easily miss capabilities). If it's not possible, you lose a kind of symmetry, and that I do not like (orthogonality and symmetries are really nice in in a computer language, it helps quickly remembering the syntax as you are not cluttered by special cases and exceptions)
Steven wrote:
The Python community has always been conservative and resistant to change, but the level of conservativeness is now pushing towards fear of change rather than justifiable caution about adding new features that cannot easily be reverted.
That's probably because you like this proposal. Really, compared to the 2.0 days, it seems that Python has became much less resistant to change. You have resistance even when proposing changes in standard library, or even the C interpreter internal, unseen in the Python layer, and imho it's often justified (what can be discussed is how arbitrary the actual selection of change is, but a general resistance is not a bad thing) It is a little bit optimistic to expect ?? and friends will be a walk in the park, especially after := ...

On Mon, Jul 23, 2018 at 8:08 AM Grégory Lielens gregory.lielens@gmail.com wrote:
On Monday, July 23, 2018 at 8:24:45 AM UTC+2, Nicholas Cole wrote:
And that leads to a simple question: how many times does this actually occur in real-world by python code? -- i.e. how many times do I want to check the value of an existing label, and, finding it is None (and None specifically), then assign it a value?
The PEP present a few examples.
I think the compactness and clarity is really gained when doing a descent into an "attribute tree", where any (or selected) members can be None, like getting back to the example by Steven (spell-corrected, and expanded):
meal = obj?.spam?.eggs.tomato?.cheese ?? "Mozzarella"
Where, to put the proposal in even better light, I have assumed that eggs instances always have a tomato attributes (i.e. are never None).
This is indeed much more compact that the current version, and arguably more readable (it's easier to parse once you know the syntax...but you have to know the special syntax that will be used for this kind of stuff only). The more you deepen the attribute access, the more you gain, but of course deep attribute access is not so common. The new operators may make deep attribute hierarchies (or deeply nested dicts/lists) slightly more common, and there also I do not think it's a good thing.
That above example looks terrible to read (to me). Yes, that's a subjective statement. This example from the PEP is even worse:
Example:
if entry.is_dir(): dirs.append(name) if entries is not None: entries.append(entry) else: nondirs.append(name)
After updating to use the ?. operator:
if entry.is_dir(): dirs.append(name) entries?.append(entry) else: nondirs.append(name)
In the first, it's totally clear to me when entries would be appended to and when it wouldn't. The second I had to read several times before I could see what was going on. The fact that it is already embedded in an if...else statement perhaps made it harder to understand.
I guess people will say that we will just get used to the new syntax, but I don't see the benefit of making that particular "if" statement more compact, and leaving all the others in place. Or to put it another way, I'm just not convinced that "None" is sufficiently special.
N.

Responding to a few more ideas that have come up here. Again, apologies for not directing them to the original authors, but I want to focus on the ideas that are leading towards a more informed decision, and not getting distracted by providing customised examples for people or getting into side debates.
I'm also going to try and update the PEP text today (or this week at least) to better clarify some of the questions that have come up (and fix that embarrassingly broken example :( )
Cheers, Steve
False: '?.' should be surrounded by spaces ------------------------------------------
It's basically the same as '.'. Spell it 'a?.b', not 'a ?. b' (like 'a.b' rather than 'a + b').
It's an enhancement to attribute access, not a new type of binary operator. The right-hand side cannot be evaluated in isolation.
In my opinion, it can also be read aloud the same as '.' as well (see the next point).
False: 'a?.b' is totally different from 'a.b' ---------------------------------------------
The expression 'a.b' either results in 'a.b' or AttributeError (assuming no descriptors are involved).
The expression 'a?.b' either results in 'a.b' or None (again, assuming no descriptors).
This isn't a crazy new idea, it really just short-circuits a specific error that can only be precisely avoided with "if None" checks (catching AttributeError is not the same).
The trivial case is already a one-liner ---------------------------------------
That may be the case if you have a single character variable, but this proposal is not intended to try and further simplify already simple cases. It is for complex cases, particularly where you do not want to reevaluate the arguments or potentially leak temporary names into a module or class namespace.
(Brief aside: 'a if (a := expr) is not None else None' is going to be the best workaround. The suggested 'a := expr if a is not None else None' is incorrect because the condition is evaluated first and so has to contain the assignment.)
False: ??= is a new form of assignment --------------------------------------
No, it's just augmented assignment for a binary operator. "a ??= b" is identical to "a = a ?? b", just like "+=" and friends.
It has no relationship to assignment expressions. '??=' can only be used as a statement, and is not strictly necessary, but if we add a new binary operator '??' and it does not have an equivalent augmented assignment statement, people will justifiably wonder about the inconsistency.
The PEP author is unsure about how it works -------------------------------------------
I wish this statement had come with some context, because the only thing I'm unsure about is what I'm supposed to be unsure about.
That said, I'm willing to make changes to the PEP based on the feedback and discussion. I haven't come into this with a "my way is 100% right and it will never change" mindset, so if this is a misinterpretation of my willingness to listen to feedback then I'm sorry I wasn't more clear. I *do* care about your opinions (when presented fairly and constructively).
Which is the most important operator? -------------------------------------
Personally, I think '?.' is the most valuable. The value of '??' arises because (unless changing the semantics from None-aware to False-aware) it provides a way of setting the default that is consistent with how we got to the no-value value (e.g. `None?.a ?? b` and `""?.a ?? b` are different, whereas `None?.a or b` and `""?.a or b` are equivalent).
I'm borderline on ?[] right now. Honestly, I think it works best if it also silently handles LookupError (e.g. for traversing a loaded JSON dict), but then it's inconsistent with ?. which I think works best if it handles None but allows AttributeError. Either way, both have the ability to directly handle the exception. For example, (assuming e1, e2 are expressions and not values):
v = e1?[e2]
Could be handled as this example (for None-aware):
_temp1 = (e1) v = _temp1[e2] if _temp1 is not None else None
Or for silent exception handling of the lookup only:
_temp1 = (e1) _temp2 = (e2) try: v = _temp1[_temp2] if _temp1 is not None else None except LookupError: v = None
Note that this second example is _not_ how most people protect against invalid lookups (most people use `.get` when it's available, or they accept that LookupErrors raised from e1 or e2 should also be silently handled). So there would be value in ?[] being able to more precisely handle the exception.
However, with ?. being available, and _most_ lookups being on dicts that have .get(), you can also traverse JSON values fairly easily like this:
d = json.load(f) name = d.get('user')?.get('details')?.get('name') ?? '<no name>'
With ?[] doing the safe lookup as well, this could be:
d = json.load(f) name = d?['user']?['details']?['name'] ?? '<no name>'
Now, my *least* favourite part of this is that (as someone pointed out), it looks very similar to using '??' with a list as the default value. And because of that, I'm okay with removing this part of the proposal if it is unpopular.

On Mon, 23 Jul 2018 10:51:31 +0100 Steve Dower steve.dower@python.org wrote:
Which is the most important operator?
Personally, I think '?.' is the most valuable.
For me, it's the most contentious. The fact that a single '?' added to a regular line of Python code can short-circuit execution silently is a net detriment to readability, IMHO.
In a code review, this means I must be careful about '?' sigils lest I miss important bug magnets.
Regards
Antoine.

On 23Jul2018 1111, Antoine Pitrou wrote:
On Mon, 23 Jul 2018 10:51:31 +0100 Steve Dower steve.dower@python.org wrote:
Which is the most important operator?
Personally, I think '?.' is the most valuable.
For me, it's the most contentious. The fact that a single '?' added to a regular line of Python code can short-circuit execution silently is a net detriment to readability, IMHO.
The only time it would short-circuit is when it would otherwise raise AttributeError for trying to access an attribute from None, which is also going to short-circuit. The difference is that it short-circuits the expression only, and not all statements up until the next except handler.
Cheers, Steve

Le 23/07/2018 à 12:25, Steve Dower a écrit :
On 23Jul2018 1111, Antoine Pitrou wrote:
On Mon, 23 Jul 2018 10:51:31 +0100 Steve Dower steve.dower@python.org wrote:
Which is the most important operator?
Personally, I think '?.' is the most valuable.
For me, it's the most contentious. The fact that a single '?' added to a regular line of Python code can short-circuit execution silently is a net detriment to readability, IMHO.
The only time it would short-circuit is when it would otherwise raise AttributeError for trying to access an attribute from None, which is also going to short-circuit.
But AttributeError is going to bubble up as soon as it's raised, unless it's explicitly handled by an except block. Simply returning None may have silent undesired effects (perhaps even security flaws).
This whole thing reminds of PHP's malicious "@" operator.
Regards
Antoine.

On 23Jul2018 1129, Antoine Pitrou wrote:
Le 23/07/2018 à 12:25, Steve Dower a écrit :
On 23Jul2018 1111, Antoine Pitrou wrote:
On Mon, 23 Jul 2018 10:51:31 +0100 Steve Dower steve.dower@python.org wrote:
Which is the most important operator?
Personally, I think '?.' is the most valuable.
For me, it's the most contentious. The fact that a single '?' added to a regular line of Python code can short-circuit execution silently is a net detriment to readability, IMHO.
The only time it would short-circuit is when it would otherwise raise AttributeError for trying to access an attribute from None, which is also going to short-circuit.
But AttributeError is going to bubble up as soon as it's raised, unless it's explicitly handled by an except block. Simply returning None may have silent undesired effects (perhaps even security flaws).
You're right that the silent/undesired effects would be bad, which is why I'm not proposing silent changes to existing code (such as None.__getattr__ always returning None).
This is a substitute for explicitly checking None before the attribute access, or explicitly handling AttributeError for this case (and unintentionally handling others as well). And "?." may be very small compared to the extra 3+ lines required to do exactly the same thing, but it is still an explicit change that can be reviewed and evaluated as "is None a valid but not-useful value here? or is it an indication of another error and should we fail immediately instead".
Cheers, Steve
This whole thing reminds of PHP's malicious "@" operator.
General comment to everyone (not just Antoine): these arguments have zero value to me. Feel free to keep making them, but I am uninterested. Perhaps whoever gets to decide on the PEP will be swayed by them?

Le 23/07/2018 à 12:38, Steve Dower a écrit :
General comment to everyone (not just Antoine): these arguments have zero value to me. Feel free to keep making them, but I am uninterested.
So you're uninterested in learning from past mistakes?
You sound like a child who thinks their demands should be satisfied because they are the center of the world.
Regards
Antoine.

On 23Jul2018 1145, Antoine Pitrou wrote:
Le 23/07/2018 à 12:38, Steve Dower a écrit :
General comment to everyone (not just Antoine): these arguments have zero value to me. Feel free to keep making them, but I am uninterested.
So you're uninterested in learning from past mistakes?
You sound like a child who thinks their demands should be satisfied because they are the center of the world.
Sorry if it came across like that, it wasn't the intention. A bit of context on why you think it's a mistake would have helped, but if it's a purely subjective "I don't like the look of it" (as most similar arguments have turned out) then it doesn't add anything to enhancing the PEP. As a result, I do not see any reason to engage with this class of argument.
I hope you'll also notice that I've been making very few demands in this thread, and have indicated a number of times that I'm very open to adjusting the proposal in the face of honest and useful feedback.
Cheers, Steve

Maybe it would help if you mention in which context you will benefit the most? If the python sub-community related to this context agree "?? and friends" is a good idea, then it will add weight to the proposal. Else, probably better to forget it.
It seems related to JSON, but as I have never used it, it's a wild guess.
Anyway, it's a special pattern with deep attribute hierarchies, whose traversal is shortcutted when one attribute happen to be None. This is common, with 2 exception: -it's rare it's really deep, or then it is arbitrarily deep and you need a procedural descent, not a fixed expression. -None break the descend, but then so does missing attributes. You address the first with special syntax. not the second, so I suspect in your case the second does not happen. Or rarely happen.
Hope this will help finding why some finds the ? operators add little, while some others think they add enough to overcome python traditional operator-averse nature.
On Monday, July 23, 2018 at 1:10:03 PM UTC+2, Steve Dower wrote:
On 23Jul2018 1145, Antoine Pitrou wrote:
Le 23/07/2018 à 12:38, Steve Dower a écrit :
General comment to everyone (not just Antoine): these arguments have zero value to me. Feel free to keep making them, but I am uninterested.
So you're uninterested in learning from past mistakes?
You sound like a child who thinks their demands should be satisfied because they are the center of the world.
Sorry if it came across like that, it wasn't the intention. A bit of context on why you think it's a mistake would have helped, but if it's a purely subjective "I don't like the look of it" (as most similar arguments have turned out) then it doesn't add anything to enhancing the PEP. As a result, I do not see any reason to engage with this class of argument.
I hope you'll also notice that I've been making very few demands in this thread, and have indicated a number of times that I'm very open to adjusting the proposal in the face of honest and useful feedback.
Cheers, Steve _______________________________________________ Python-ideas mailing list Python...@python.org javascript: https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On 23 July 2018 at 12:39, Grégory Lielens gregory.lielens@gmail.com wrote:
Maybe it would help if you mention in which context you will benefit the most? If the python sub-community related to this context agree "?? and friends" is a good idea, then it will add weight to the proposal. Else, probably better to forget it.
It seems related to JSON, but as I have never used it, it's a wild guess.
This is my impression, as well. It seems like something that's helpful in dealing with unstructured object hierarchies with lots of optional attributes - which is where JSON tends to be used.
But given that, I'm really much more interested in seeing the new operators compared against a well-written "JSON object hierarchy traversal" library than against raw Python code. I'll happily agree that traversing JSON-style data in current Python is pretty unpleasant. But I don't honestly think that anyone has explored how far a well-written library can go in making it easy to handle such data (well, I certainly haven't, and I haven't found any particularly good examples on PyPI). And until that's been tried, I think it's premature to propose a syntax change (if it *has* been tried, adding references to the PEP would be useful).
Again, this is more about ?. and ?[. I can see general uses for ?? (and its augmented assignment form ??=), but the None-aware attribute and item access operators seem to me to be the most domain-specific aspects of the PEP (as well as being the ugliest IMO ;-)). So comparing against domain-specific libraries rather than against "write your own" raw Python code seems reasonable to me.
Paul

Paul Moore wrote:
This is my impression, as well. It seems like something that's helpful in dealing with unstructured object hierarchies with lots of optional attributes - which is where JSON tends to be used.
But given that, I'm really much more interested in seeing the new operators compared against a well-written "JSON object hierarchy traversal" library than against raw Python code.
Good point, I did not think about that when suggesting to give some context, but indeed if it's linked to a library in particular, there is always the possibility to choose another object than None as the "nothing here" marker.
One that will absorb getitems and getattr accesses so that ?. and ?[] behavior is reproduced by plain . and []. Guard/NoValues should be chosen so that typical lib use is easier.
Anyway, addressing partially-populated nodes would need lib support: None-coalescence will not help when you traverse a dict/attribute hierarchy where some nodes implement some attributes/keys but not others. It help only when a node is either regular, or a guard object without any attributes...So an irregular tree, but not too irregular ;-)...

On Mon, Jul 23, 2018 at 7:46 AM, Grégory Lielens gregory.lielens@gmail.com wrote:
Paul Moore wrote:
This is my impression, as well. It seems like something that's helpful in dealing with unstructured object hierarchies with lots of optional attributes - which is where JSON tends to be used.
But given that, I'm really much more interested in seeing the new operators compared against a well-written "JSON object hierarchy traversal" library than against raw Python code.
Good point, I did not think about that when suggesting to give some context, but indeed if it's linked to a library in particular, there is always the possibility to choose another object than None as the "nothing here" marker.
From what I gather, disallowing reassignment all but cements None as the
"nothing here" marker (the originally intent?), _especially_ when "nothing here" is candidate for replacement by a more appropriate, type- or domain- or context-specific "nothing here" marker. Imbuing None with any other meaning brings nothing but headache. It's an attractive nuisance. None is None is None, there can be only one! You don't know what it is, only that it's not anything else. What possible meaning can such an object usefully have in an application built on disparate libraries with their own ideas on the matter?
Every API I've used (apologies for coming up blank on a concrete example!) granting None meaning is awkward to consume. `.get()` interfaces are less useful (must carry your own internal sentinels) or more try/except blocks are required to achieve the same end (not a bad thing per se, but a diminished experience to be sure). Early APIs I wrote in this "well it's None, but but but, with this convenient meaning, see?! SEE??"-style were later regarded -- quite thoroughly -- as a bad idea by myself, my peers, and downstream consumers.
I'd personally use these new operators both frequently and judiciously. They align very well with a "set 'em up and knock 'em down"-style I use: normalized, progressively conditioned input values fast-followed by aggressive short-circuits and clear early returns. IME this pattern generates clean, readable, and performant code. Honestly the short-circuiting capability alone is enough to sell me :-) This PEP would find legitimate use by me every day. I'm not 100% sold on `?[` (then again, attributes are pulled from an object namespace via `?.` and namespaces are containers by definition) but `?.` and `??` deliver immense value.
Not sure if useful, but this discussion reminds me of a pattern prevalent in the Elixir community. They use `?` and `!` in function definitions to denote variants differing only on return behavior (not function clauses! This is by convention only, they're entirely new functions with a symbol in their name). It looks something like this:
# Default function. # Return a tuple {interesting-or-nil, error-or-nil}. def open(path) do ... end
# Maybe variant. # Return a boolean, or less often, interesting-or-nil (replaces `is_` or `can_` methods in Python). def open?(path) do ... end
# Forceful variant. # Return interesting or die trying (inverse of `.get()` methods in Python; raising is not the default expectation in Elixir). def open!(path) do ... end
The `?.`-operator reminds me of this. It offers to perform an extremely common operation (simple attribute access) while short-circuiting on the most frequently triggered guard condition (AttributeError).
I don't think the utility here is restricted to deeply nested JSON `loads()` or one-off scripts. It better aligns the community on semantics, encouraging more idiomatic -- and uniform! -- interactions with None.

I personally do almost only classical programming, and I am somewhat opposed to OOP in general. So here my somewhat outlandish view, (and I am biased becase I will probably never need this feature).
First thoughts after reading the PEP: what is so super-special and fundamental about None value? Is it more special and required to treat more often than "true" or "false" values in similar manner? Is there statistics for occurence of code checking 'None' versus 'not None'?
So:
if x is None: x = 10
converts to:
x ??= 10
But what if one need to do something else than just return a value? E.g. I want to insert `print(x)` in the first example? Or check against other value than 'None'? Supposed to write it so then rewrite it so, then if I need it other way - again rewrite it so.
So from the PEP I understand only two things: 1) de-facto None became special in some contexts. 2) one may need to short-circuit an expression if None pops up.
So it is syntax good only for special kind of applications I suppose. It seems motivating examples come mainly from situations where one has a Python script serving some database framework or something alike. So it smells like specialized usage and design-specific pattern. It looks (from a passer-by POV) like the proposed syntax tries to treat some sticking out parts of something that may not be necessarily present in an application at all. Of course that maybe argued easily because many concepts that does not directly belong to programming are sometimes appearing in form of dedicated syntax. Though such cases not so common in Python.
I don't know whether the feature adressed by '?.' is often in those contexts, but first thing that comes to mind - it regards only experts and NOT intended for reading the code that I am not familiar with.
E.g. this "improved" code in examples:
def find_module(self, fullname, path): return getattr(self, 'find_spec', None)?.__call__(fullname, path)?.loader
I could not decipher this and I have tried for a while.
I could probably understand the motivation better if it were some more or less fundamental or general pattern, but is it? IDK
-----
More important that the syntax part is really worrying. Does everybody realize that there are only 2 or 3 ASCII characters left free? IIRC those are question mark "?", exclamation mark "!" and dollar sign "$".
Imagine that in some time someone comes up with a good general syntax feature that would require new symbol - and there is nothing left. That's not funny actually. This makes decisions about symbols into agony and heats up discussions to extreme because some PEP authors maybe hoping for a better-looking symbol for their 'child' while other proposals and possible future proposals may be more important.
At this time point I think one should first consider exploring the possibility to add non-ASCII characters to syntax. I think it is resonable because Unicode became standard. It should at least help to relax the atmosphere around syntax proposals.
'Niche' features IMO should be better added as functions, or, if non-ASCII symbols could be added, as some '2nd class' symbols (i.e. not so nice looking). And nicely looking symbols should be reserved for future proposals for general syntax features which are potentially useful for wider user groups.

Le 23/07/2018 à 13:09, Steve Dower a écrit :
On 23Jul2018 1145, Antoine Pitrou wrote:
Le 23/07/2018 à 12:38, Steve Dower a écrit :
General comment to everyone (not just Antoine): these arguments have zero value to me. Feel free to keep making them, but I am uninterested.
So you're uninterested in learning from past mistakes?
You sound like a child who thinks their demands should be satisfied because they are the center of the world.
Sorry if it came across like that, it wasn't the intention.
Thanks for your apologies, and I apology for being a bit abrasive too.
Regards
Antoine.

On Mon, Jul 23, 2018 at 12:09:00PM +0100, Steve Dower wrote:
On 23Jul2018 1145, Antoine Pitrou wrote:
Le 23/07/2018 à 12:38, Steve Dower a écrit :
General comment to everyone (not just Antoine): these arguments have zero value to me. Feel free to keep making them, but I am uninterested.
So you're uninterested in learning from past mistakes?
You sound like a child who thinks their demands should be satisfied because they are the center of the world.
Sorry if it came across like that, it wasn't the intention.
Steve, I don't think you should apologise to somebody who has just quoted you out of context and insulted you for no good reason.
In context, your comment about "these arguments" was a perfectly reasonable thing to say. You didn't say you were uninterested in *all* arguments, only certain kinds of low-value arguments that keep getting made over and over again. Antoine snipped the context, unfairly accused you on zero evidence of being "uninterested in learning from past mistakes", and described you as a self-centred child.
I think Antoine should be apologising to you, not the other way around.

On Mon, Jul 23, 2018 at 6:52 AM Steve Dower steve.dower@python.org wrote:
Responding to a few more ideas that have come up here.
Thank you for the clarifications.
I'm trying to wrap my head around the various facets of None aware operators proposal after reading the whole discussion - as well as having read the PEP a few times. Below is a summary of what I gathered from the discussion, with a few added other points that I have not seen addressed.
1. It is an operator present in other languages (e.g. C#, Dart), that uses the same notation
a) This demonstrates that such an operator has been found to be useful and is definitely worth considering.
b) The fact that it uses the same notation is a plus for people that know these other languages but it does not mean that the notation is necessarily the best choice for Python. To wit, Python does not use the combination of ? and : for ternary operator; instead it reuses existing keywords.
c) Some people use languages (e.g. Ruby) that allow ? to be part of an identifier, which means that one could, in principle, write something like
a = b?.c
in Ruby with a completely different meaning than what it would mean in C# or Dart, or as proposed for Python.Thus, I don't think that that language X uses this operator written this way is, **on its own**, a valid justification for using the exact same syntax for Python.
d) Dart is given as an example. Reading from the link mentioned in the PEP, I was lead to https://www.dartlang.org/guides/language/language-tour#classes where the only mention of ?. was the following:
=== // If p is non-null, set its y value to 4. p?.y = 4; ===
However, PEP 505 mentions that something like the above would raise a SyntaxError. Given that Dart's operator has a different semantics than that proposed for Python, I do not think that mentioning Dart in this context without any qualifier is a valid supportive justification for this proposal.
2. On the specific choices of ??, ??=, ?., ?[]
a) Trying to find what Csharp ?? means by doing a quick internet search is not exactly productive. By comparison, if one were to use a new keyword (say ifnone instead of ??) or keywords, it would make it easier to find their meaning. The problem with new keywords is that they may introduce some backwards incompatibility.
b) Admitedly, there are very few remaining symbols in Python that can be used for defining new operators. Introducing operators using ? does not cause problems with existing code.
c) While using a new keyword for ?? and ??= might work, it is less clear, at least to me, how this could extend to ?. and ?[]
d) ? and ?? are already used by IPython with a totally different meaning. I almost never use IPython I have no idea what problems this might cause for IPython users.
3. On some examples given
PEP 505 gives these examples from Request:
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
It then argues that this is undesirable and that it could have been written instead as
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 {}
which, as written in PEP 505, is deemed to be "more intuitive" - but longer.
Having looked at the code in the Request module, I would argue that it could have been written instead as
if data is None: data = [] if files is None: files = [] if headers is None: headers = {} if params is None: params = {} if hooks is None: hooks = {}
which gives the same result and is shorter than the original - but admittedly longer than the proposed notation. I do not think that this specific example as currently written in PEP 505 gives a fair picture of what is currently possible with Python.
4) On comparisons with other "domain specific operators", like @ and the bitwise operators.
I do not remember the exact words that were used in the discussions, but I seem to recall people saying that operators like ??, ?., etc. are no more "mysterious" than @ or the bitwise operators might be: users should not be surprised to have to learn new operators.
Anyone that does numerical work knows that matrix multiplications do not obey the same rules as multiplications of numbers. If you don't do numerical work, you are almost certain not to encounter @ as an operator (but only as a symbol for decorator), so there won't be any mystery to solve. A similar situation exists for bitwise operators. Having dedicated operators to represent special methods on these types of objects makes the code easier to read for people working in these fields. I also note that these operators have corresponding dunder methods.
By contrast, code like
if a is None: a = []
can occur in pretty much any type of program and is, arguably, already very readable. Saying that it could/should be written instead as
a ??= []
where ??= would be an operator without corresponding dunder method is not the same. Unless I am mistaken, it would be the first time that operators are introduced in Python without having corresponding dunder methods. If I am correct in this, I believe that the PEP should at the very least address this situation and provide an explanation as to why new operators should be introduced that break this non-written rule in Python.
= = =
**Personal biases**: I do admit that I prefer code written in indented blocks to show the logic, over trying to cram everything in as few lines as possible. (The only exception to this is for list comprehensions, but this might be because I am very familiar with mathematical notation used for sets.)
Thus, I would likely always prefer to write
if a is None: a = []
over
if a is None: a = []
but I do admit that, after the initial shock, I do find
a ??= []
to be fairly readable. One thing I do find helpful is that it is possible (and desirable) to have spaces around the operator. Still, if such an operator was introduced, I would prefer a new keyword over the proposed symbols, to be consistent with choices made when the ternary operator construct was introduced.
However, even after reading it over many times, I do find code using ?. and ?[] to be much harder to read then using the current notation. Python is often described as executable pseudo-code, which I consider one of its strengths, and I find these proposed operators to look like anything but executable pseudo-code.
Unlike the recently approved new operator, :=, whose meaning can be at least guessed based on the presence of "=", there is nothing intuitive about the choices of operators built from ?. The fact that they would not be easily found by doing an internet search (other than specifically looking for "python operators") compared with a search for keywords based operators (e.g. "python ifnone") is not desirable imo.
André Roberge
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Mon, Jul 23, 2018 at 5:52 AM Steve Dower steve.dower@python.org wrote:
The PEP author is unsure about how it works
I wish this statement had come with some context, because the only thing I'm unsure about is what I'm supposed to be unsure about.
In general—as I haven't been shy of saying—I find the entire idea awful. I recognize you have done sincere and high quality work in arguing for it; it just feels like a very wrong direction for Python.
But bracketing that, I'm probably the person you have in mind in that comment. And it's funny that you write you are unsure what you are supposed to be unsure about, but the very next section is exactly what I had in mind. Of course I don't mean that if implemented the semantics would be ambiguous... rather, the proper "swallowing" of different kinds of exceptions is not intuitively obvious, not even to you, Steve. And if some decision was reached and documented, it would remain unclear to new (or even experienced) users of the feature.
I'm borderline on ?[] right now. Honestly, I think it works best if it
also silently handles LookupError (e.g. for traversing a loaded JSON dict), but then it's inconsistent with ?. which I think works best if it handles None but allows AttributeError.
Moreover, at a couple points in your clarification, you say "ignoring descriptors." But ultimately, the language cannot do that. When a programmer writes `obj?.descriptor?.other_descriptor` SOMETHING has to happen (no matter what actually happens within the code of the property).
This can certainly be specified in some unambiguous way, but I believe that any decision made will be strongly counter-intuitive for certain concrete code.

On 23Jul2018 1530, David Mertz wrote:
Of course I don't mean that if implemented the semantics would be ambiguous... rather, the proper "swallowing" of different kinds of exceptions is not intuitively obvious, not even to you, Steve. And if some decision was reached and documented, it would remain unclear to new (or even experienced) users of the feature.
As written in the PEP, no exceptions are ever swallowed. The translation into existing syntax is very clearly and unambiguously shown, and there is no exception handling at all. All the exception handling discussion in the PEP is under the heading of "rejected ideas".
This email discussion includes some hypotheticals, since that's the point - I want thoughts and counter-proposals for semantics and discussion. I am 100% committed to an unambiguous PEP, and I believe the current proposal is most defensible. However, I don't want to have a "discussion" where I simply assume that I'm right, everyone else is wrong, and I refuse to discuss or consider alternatives.
So sorry for letting you all think that everything I write is actually the PEP. I had assumed that because my emails are not the PEP that people would realise that they are not the PEP. I'm going to duck out of the discussions here now, since they are not as productive as I'd hoped, and once we have a BDFL-replacement I'll reawaken it and see what is required at that point.
Cheers, Steve

On Mon, Jul 23, 2018 at 11:52 AM Steve Dower steve.dower@python.org wrote:
I'm borderline on ?[] right now. Honestly, I think it works best if it also silently handles LookupError (e.g. for traversing a loaded JSON dict), but then it's inconsistent with ?. which I think works best if it handles None but allows AttributeError.
That would easily make typos pass unnoticed:
request.context.user.usernme # raises AttributeError request?.context?.user?.usernme # return None
Same goes for LookupError: if a key or index is missing on 'a?[b]' I do want an exception. If I don't, which should be the exception rather than the rule, I will simply take the risk myself and do:
default = '<no name>' try: name = d['user']['details']['name'] or default except KeyError: name = default
But certainly there should be no native syntax encouraging me to do any of that. Talking about arbitrarily swallowing exceptions is the worst direction this proposal can take as it breaks yet another fundamental Python Zen: "errors should never pass silently". IMO this shows how fundamentally detached from the base philosophy of the language this whole idea is.
-- Giampaolo - http://grodola.blogspot.com

On 2018-07-23 10:51, Steve Dower wrote: [snip]
False: 'a?.b' is totally different from 'a.b'
The expression 'a.b' either results in 'a.b' or AttributeError (assuming no descriptors are involved).
The expression 'a?.b' either results in 'a.b' or None (again, assuming no descriptors).
It could still raise AttributeError.
This isn't a crazy new idea, it really just short-circuits a specific error that can only be precisely avoided with "if None" checks (catching AttributeError is not the same).
[snip]

On Mon, Jul 23, 2018 at 2:23 AM Nicholas Cole nicholas.cole@gmail.com wrote:
One issue for me is that the trivial case is already a one-liner:
if a is None: a = 10
Yes, if you have no indentation and a 1-character name, then it fits on a single line. If you have a longer expression and/or side effects, then it's not a one-liner anymore.
And that leads to a simple question: how many times does this actually occur in real-world by python code? -- i.e. how many times do I want to check the value of an existing label, and, finding it is None (and None specifically), then assign it a value?
What does a scan through the existing core library say?
Please read the PEP before you shoot it down. It answers this _exact_ question.

On Mon, Jul 23, 2018 at 2:37 PM Mark E. Haase mehaase@gmail.com wrote:
What does a scan through the existing core library say?
Please read the PEP before you shoot it down. It answers this _exact_ question.
My apologies. I'd missed the line where it says the examples were taken from the core library. So then a couple of points. Given the size of the core library, 678 seems like a very low number to me for a proposal like this -- but then I don't know quite what the threshold number would be for something like this. I guess I had expected that if None really were used in this way that there would be thousands of places where it might be used in the core libraries. Secondly (and I mean this with no animus at all), in some of the examples given I really struggle to see that the new version is more readable / maintainable / obvious than the old version. I can see the point in a couple of others. You'll say this is subjective as a test, and it is, I suppose.
N.

On Mon, Jul 23, 2018 at 3:12 AM Steven D'Aprano steve@pearwood.info wrote:
? has no spaces, it's literally "variable names interrupted by question marks" and evaluation can stop at any time while scanning the line from left to right.
Just like ordinary attribute access.
This is the point I was making earlier: you accept existing punctuation doing these things:
try: obj.spam.egsg.tomato.cheese # oops a typo except AttributeError: # evaluation can stop at any time ...
while demanding a higher standard for new punctuation.
All of your criticisms of ? punctuation applies to . as well.
I don't think they do. For once, "a.b" does one and one thing only, "a?.b" does two and that's a fundamental difference (explicitness). It does so by introducing a brand new operator ("?") which can be spelled in two forms ("a?.b" and "a?[b]") by using two adjacent symbols not interrupted by any space, which is an absolute first in the Python syntax and that's the second and fundamental difference. I cannot move the same criticism to the "a.b" form: it's simpler, it does one thing and it uses one symbol.
Your argument is basically a revisitation of "it's just another symbol" and "it's like a + b" which you have being pulling different times in this thread already. You want to imply that since symbols are already used in the grammar (and "." in particular) then it's perfectly fine to also have "?" and its spell variants (which are 4 in total). I don't think that's how changes of such importance should be discussed.
Multiple "?" can live on the same line so that's incentive to write one-liners, really, and to me one-liners are always less explicit than the same logic split on multiple lines.
Explicit is not always better.
import this
is much better than:
for location in sys.path: try: for file in os.listdir(location): if os.splitext(file) in ('.pyc', '.py', '.so'): ...
I honestly don't see how this example is related with anything discussed so far.
If punctuation is unreadable and Perlish, so is "." and ":" punctuation. If ?? is bad because it is "implicit", then so is import or sorted.
I'm not even sure how to reply to this.

On Mon, Jul 23, 2018 at 02:04:17PM +0200, Giampaolo Rodola' wrote:
[I wrote this]
This is the point I was making earlier: you accept existing punctuation doing these things:
try: obj.spam.egsg.tomato.cheese # oops a typo except AttributeError: # evaluation can stop at any time ...
while demanding a higher standard for new punctuation.
All of your criticisms of ? punctuation applies to . as well.
[Giampaolo]
I don't think they do. For once, "a.b" does one and one thing only,
Attribute lookup is a bit more complex than just "one thing only", but okay, I'll accept that for a sufficiently complex "thing", dot access does one thing.
"a?.b" does two and that's a fundamental difference (explicitness).
How is "two things" less explicit than "one thing"?
Comments like the above is why I think that "explicit" and "implicit" are used to mean "I like it" and "I don't like it" rather than being objective arguments, or indeed having anything to do with explicitness or implicitness.
If this PEP is approved, then *by definition* the ?? operator will mean
return the first operand if it isn't None otherwise evaluate and return the second
making it just as explicit as:
+ # add or concatenate the two operands
== # return True if the two operands are equal otherwise False
sorted(x) # make a list copy of x and sort it
etc. Just because the spelling is short doesn't make it implicit.
It does so by introducing a brand new operator ("?") which can be spelled in two forms ("a?.b" and "a?[b]") by using two adjacent symbols not interrupted by any space, which is an absolute first in the Python syntax
It isn't a first. Many existing operators use two adjacent symbols not interrupted by a space:
e.g. == <= >= != ** // << >> += -= *= etc.
and that's the second and fundamental difference. I cannot move the same criticism to the "a.b" form: it's simpler, it does one thing and it uses one symbol.
You criticised ?. because it can interupt left-to-right execution:
a?.b?.c?.d
True. But so can a single dot:
a.b.c.d
is no more guaranteed to execute all the way to the right.
Likewise the logical operators "or" and "and" are designed to short-circuit. If ?? and friends are a mistake because they short-circuit, why aren't "or" and "and" mistakes?
I'm not asking this as a rhetorical question. If you think there is a reason why it is okay for or/and to short-circuit, but it is bad for ?? and friends to short-circuit, then please explain why they are different. I will be very happy to listen to your arguments.
Multiple "?" can live on the same line so that's incentive to write one-liners, really, and to me one-liners are always less explicit than the same logic split on multiple lines.
Explicit is not always better.
import this
is much better than:
for location in sys.path: try: for file in os.listdir(location): if os.splitext(file) in ('.pyc', '.py', '.so'): ...
I honestly don't see how this example is related with anything discussed so far.
You keep saying that the proposed ?? etc operators aren't explicit and you criticise them for doing "two things". The import statement does at least ten things:
- searches the cache of modules; - if not found, traverse the search path looking for not one kind of file, but multiple kinds of files that the user has no control over; - if a matching file is found, check for a pre-compiled version; - or compile it; - save the compiled byte-code in a new file; - load the compiled byte-code into a module object; - execute that code; - add the module object to the cache; - create a new name in the local namespace; - bind the module object to the name.
If doing "two things" is bad, then doing "ten things" is five times worse. If ?? is too implicit, then what is importing? Why is it okay for importing to be "implicit" but ?? needs to be written out over two lines?
If behaviour that is acceptable, even desirable in existing features is harmful in these new ? operators, then please tell us how and why.

On Mon, Jul 23, 2018 at 6:53 PM Steven D'Aprano steve@pearwood.info wrote:
On Mon, Jul 23, 2018 at 02:04:17PM +0200, Giampaolo Rodola' wrote:
"a?.b" does two and that's a fundamental difference (explicitness).
How is "two things" less explicit than "one thing"? Comments like the above is why I think that "explicit" and "implicit" are used to mean "I like it" and "I don't like it" rather than being objective arguments, or indeed having anything to do with explicitness or implicitness.
This:
v = a?.b
...*implicitly* checks if value is not None [and continues execution]. This:
v = a if a.b is not None: v = a.b
...*explicitly* checks if value is not None and continues execution. If for some reason '?'[ is also going to swallow LookupError then *that* would further decrease explicitness, because LookupError would be nowhere in sight, the same way "if", "is", "not", "None", ":", "new line" are nowhere in sight in the 'a?.b' example. Some argued "A ?? B" is less explicit than "A if A is not None else B" for the same reason. One may argue that silently returning None instead of raising AttributeError is also less explicit. This - and this only - is my argument about explicitness. It doesn't have to do with how many things are hidden behind an import statement or what happens on sorted() (that's comparing apples and oranges). I hope it's clear now.
It does so by introducing a brand new operator ("?") which can be spelled in two forms ("a?.b" and "a?[b]") by using two adjacent symbols not interrupted by any space, which is an absolute first in the Python syntax
It isn't a first. Many existing operators use two adjacent symbols not interrupted by a space:
e.g. == <= >= != ** // << >> += -= *= etc.
You say 'a == b'. You can't say 'a ?. b' (not that it matters, it would be less intuitive anyway). You can't because '.?' is the only couple of contiguous symbols requiring "something" before and after with no spaces in between, and that's a first in the language. The argument about this is that it's ugly and less readable. My additional argument at the beginning of this thread was that if you add PEP-572 to the mix you dangerously enter into Perl territory:
foo(x=(x := a?.b?[c] ?? d))
and that's the second and fundamental difference. I cannot move the same criticism to the "a.b" form: it's simpler, it does one thing and it uses one symbol.
You criticised ?. because it can interupt left-to-right execution:
a?.b?.c?.d
True. But so can a single dot:
a.b.c.d
is no more guaranteed to execute all the way to the right.
The difference is that 'a.b.c.d' will result in AttributeError as soon as something is None while 'a?.b?.c?.d' will return None instead.
Likewise the logical operators "or" and "and" are designed to short-circuit. If ?? and friends are a mistake because they short-circuit, why aren't "or" and "and" mistakes? I'm not asking this as a rhetorical question. If you think there is a reason why it is okay for or/and to short-circuit, but it is bad for ?? and friends to short-circuit, then please explain why they are different. I will be very happy to listen to your arguments.
The argument about this is that '?.' short-circuits execution *silently*. Instead of AttributeError you get None. You may chain ?. in order to lazily traverse a long tree, inadvertently assign None to a variable, continue code execution and fail later rather than sooner:
email = request?.context?.user?.email # None ... sendmail(subject, body, email)
Some (Antoine) rightly argued this may even have security implications (replace 'email' with 'password').

On Tue, Jul 24, 2018 at 8:05 AM, Giampaolo Rodola' g.rodola@gmail.com wrote:
The argument about this is that '?.' short-circuits execution *silently*. Instead of AttributeError you get None. You may chain ?. in order to lazily traverse a long tree, inadvertently assign None to a variable, continue code execution and fail later rather than sooner:
email = request?.context?.user?.email # None ... sendmail(subject, body, email)
Some (Antoine) rightly argued this may even have security implications (replace 'email' with 'password').
This thread has been long and rambling. Can you elaborate on the security implications? Normally, I would expect that a password of None would always fail to validate.
ChrisA

On 2018-07-23 23:05, Giampaolo Rodola' wrote:
On Mon, Jul 23, 2018 at 6:53 PM Steven D'Aprano steve@pearwood.info wrote:
On Mon, Jul 23, 2018 at 02:04:17PM +0200, Giampaolo Rodola' wrote:
"a?.b" does two and that's a fundamental difference (explicitness).
How is "two things" less explicit than "one thing"? Comments like the above is why I think that "explicit" and "implicit" are used to mean "I like it" and "I don't like it" rather than being objective arguments, or indeed having anything to do with explicitness or implicitness.
This:
v = a?.b
...*implicitly* checks if value is not None [and continues execution].
It's no more implicit than 'or' checking for falseness.
This:
v = a if a.b is not None: v = a.b
...*explicitly* checks if value is not None and continues execution. If for some reason '?'[ is also going to swallow LookupError then *that* would further decrease explicitness, because LookupError would be nowhere in sight, the same way "if", "is", "not", "None", ":", "new line" are nowhere in sight in the 'a?.b' example. Some argued "A ?? B" is less explicit than "A if A is not None else B" for the same reason. One may argue that silently returning None instead of raising AttributeError is also less explicit. This - and this only - is my argument about explicitness. It doesn't have to do with how many things are hidden behind an import statement or what happens on sorted() (that's comparing apples and oranges). I hope it's clear now.
It does so by introducing a brand new operator ("?") which can be spelled in two forms ("a?.b" and "a?[b]") by using two adjacent symbols not interrupted by any space, which is an absolute first in the Python syntax
It isn't a first. Many existing operators use two adjacent symbols not interrupted by a space:
e.g. == <= >= != ** // << >> += -= *= etc.
You say 'a == b'. You can't say 'a ?. b' (not that it matters, it would be less intuitive anyway). You can't because '.?' is the only couple of contiguous symbols requiring "something" before and after with no spaces in between, and that's a first in the language.
You _can_ say 'a ?. b', just as you _can_ say 'a . b'.
Also, it's not a couple of contiguous symbols, it's a single symbol, just as '<=' is a single symbol (and you can't put a space in the middle of that either). [snip]

On Tue, Jul 24, 2018 at 2:22 AM MRAB python@mrabarnett.plus.com wrote:
It does so by introducing a brand new operator ("?") which can be spelled in two forms ("a?.b" and "a?[b]") by using two adjacent symbols not interrupted by any space, which is an absolute first in the Python syntax
It isn't a first. Many existing operators use two adjacent symbols not interrupted by a space:
e.g. == <= >= != ** // << >> += -= *= etc.
You say 'a == b'. You can't say 'a ?. b' (not that it matters, it would be less intuitive anyway). You can't because '.?' is the only couple of contiguous symbols requiring "something" before and after with no spaces in between, and that's a first in the language.
You _can_ say 'a ?. b', just as you _can_ say 'a . b'.
You're right. It's so uncommon I forgot this style was valid. Anyway, as I said 'a ?. b' would be even worse the same way 'a . b' is worse than 'a.b'. The recommended and broadly used spelling would be 'a?.b'.

On Tue, Jul 24, 2018 at 12:05:14AM +0200, Giampaolo Rodola' wrote:
This:
v = a?.b
...*implicitly* checks if value is not None [and continues execution].
Do you agree that:
obj.attribute x + 1 func(arg)
explicitly looks up an attribute on obj, explicitly adds 1 to x, and explicitly calls func with a single argument? I don't think that we have to write COBOL-esque code to be explicit:
GET ATTRIBUTE "attribute" FROM obj ADD 1 TO x CALL FUNCTION func WITH ARGUMENT arg
I don't accept that the use of punctuation makes something implicit. But if you want to argue that anything with punctuation is "implicit", then okay, Python has lots of implicit punctuation.
By definition, ?. checks for None before doing the attribute lookup. That is completely explicit, regardless of how it is spelled:
obj?.attribute
NULL-AWARE GET ATTRIBUTE "attribute" FROM obj
null_aware_getattr(obj, "attribute")
getattr_if_not_none(obj, "attribute")
But what certainly *is* implicity is David Mertz' suggestion for a magical None-aware proxy:
x.attribute
The only way to tell whether that was an ordinary attribute lookup or a none-aware lookup would be to carefully inspect x and find out whether it was an instance of the None-aware proxy class or not.
This
v = a if a.b is not None: v = a.b
...*explicitly* checks if value is not None and continues execution.
If you are trying to match the behaviour of a?.b above, it is also completely buggy and doesn't do what is intended.
# Equivalent of a?.b v = a if v is not None: v = v.b
If for some reason '?'[ is also going to swallow LookupError
What makes you think that ?[...] will swallow LookupError?
Please don't argue against misfeatures that the PEP doesn't propose. Nothing in PEP 505 swallows any exceptions. Swallowing exceptions is explicitly rejected, and swallowing LookupError isn't part of the proposal.
[...]
One may argue that silently returning None instead of raising AttributeError is also less explicit.
And again, you are arguing against a misfeature which PEP 505 does not propose. The ?. operator will not suppress AttributeErrors.
# Wrong! No! This is not what the PEP proposes! obj = 1.234 assert obj?.hexx is None
[...]
It isn't a first. Many existing operators use two adjacent symbols not interrupted by a space:
e.g. == <= >= != ** // << >> += -= *= etc.
You say 'a == b'. You can't say 'a ?. b' (not that it matters, it would be less intuitive anyway). You can't because '.?' is the only couple of contiguous symbols requiring "something" before and after with no spaces in between, and that's a first in the language.
Why do you think spaces aren't allowed? The PEP explicitly says that the new operators can be used wherever the regular operators can be used:
"The maybe-dot and maybe-subscript operators are added as trailers for atoms, so that they may be used in all the same locations as the regular operators"
and explicitly shows the grammar changes required:
trailer: ('(' [arglist] ')' | '[' subscriptlist ']' | '?[' subscriptlist ']' | '.' NAME | '?.' NAME)
That tells me that ?. will be legal anywhere . is legal, so if x . y is legal (and it is) so will x ?. y be legal.
[...]
The difference is that 'a.b.c.d' will result in AttributeError as soon as something is None while 'a?.b?.c?.d' will return None instead.
Correct. Because sometimes you want an AttributeError, and sometimes you want None.
You are criticising the operator for doing what it is designed and intended to do. You might as well criticise getattr(obj, 'spam', None) for returning None.
If you want an AttributeError, then don't use ?. and use ordinary . instead.
Likewise the logical operators "or" and "and" are designed to short-circuit. If ?? and friends are a mistake because they short-circuit, why aren't "or" and "and" mistakes? I'm not asking this as a rhetorical question. If you think there is a reason why it is okay for or/and to short-circuit, but it is bad for ?? and friends to short-circuit, then please explain why they are different. I will be very happy to listen to your arguments.
The argument about this is that '?.' short-circuits execution *silently*.
Other short-circuit operators also short-circuit execution silently. That's what they are designed to do.
# This isn't what actually happens. py> x = 0 py> result = x and 1/x __main__:1: UserWarning: Short-cut operation occurred, the right hand operand was not evaluated!!! Do not panic, this is the expected behaviour!!! py> print(result) 0
Instead of AttributeError you get None. You may chain ?. in order to lazily traverse a long tree,
Correct, that is what it is designed to do.
inadvertently assign None to a variable, continue code execution and fail later rather than sooner:
email = request?.context?.user?.email # None ... sendmail(subject, body, email)
Some (Antoine) rightly argued this may even have security implications (replace 'email' with 'password').
I'll have to remember that. Whenever there's any proposal for a feature I don't like, just claim it "may even have security implications". Who needs evidence when we have Fear, Uncertainty, Doubt?

On Tue, Jul 24, 2018 at 11:50 AM Steven D'Aprano steve@pearwood.info wrote:
On Tue, Jul 24, 2018 at 12:05:14AM +0200, Giampaolo Rodola' wrote:
This:
v = a?.b
...*implicitly* checks if value is not None [and continues execution].
Do you agree that:
obj.attribute x + 1 func(arg)
explicitly looks up an attribute on obj, explicitly adds 1 to x, and explicitly calls func with a single argument? I don't think that we have to write COBOL-esque code to be explicit:
GET ATTRIBUTE "attribute" FROM obj ADD 1 TO x CALL FUNCTION func WITH ARGUMENT arg
I don't accept that the use of punctuation makes something implicit. But if you want to argue that anything with punctuation is "implicit", then okay, Python has lots of implicit punctuation. By definition, ?. checks for None before doing the attribute lookup. That is completely explicit, regardless of how it is spelled:
obj?.attribute
You totally missed my point about explicitness. Nevermind.
This
v = a if a.b is not None: v = a.b
...*explicitly* checks if value is not None and continues execution.
If you are trying to match the behaviour of a?.b above, it is also completely buggy and doesn't do what is intended.
# Equivalent of a?.b v = a if v is not None: v = v.b
If for some reason '?'[ is also going to swallow LookupError
What makes you think that ?[...] will swallow LookupError?
Please don't argue against misfeatures that the PEP doesn't propose. Nothing in PEP 505 swallows any exceptions. Swallowing exceptions is explicitly rejected, and swallowing LookupError isn't part of the proposal.
I know it's not in the PEP. I merely mentioned that as PEP author was questioning that possibility in previous messages. It was an example (for you) on how *that* would make things even less explicit.
[...]
One may argue that silently returning None instead of raising AttributeError is also less explicit.
And again, you are arguing against a misfeature which PEP 505 does not propose. The ?. operator will not suppress AttributeErrors.
# Wrong! No! This is not what the PEP proposes! obj = 1.234 assert obj?.hexx is None
That is not what I meant at all! I seriously question whether you really don't understand or you're just pretending. What I meat in here was 'a?.b?.c?' returning None in case 'b' is None.
[...]
It isn't a first. Many existing operators use two adjacent symbols not interrupted by a space:
e.g. == <= >= != ** // << >> += -= *= etc.
You say 'a == b'. You can't say 'a ?. b' (not that it matters, it would be less intuitive anyway). You can't because '.?' is the only couple of contiguous symbols requiring "something" before and after with no spaces in between, and that's a first in the language.
Why do you think spaces aren't allowed? The PEP explicitly says that the new operators can be used wherever the regular operators can be used. [...] That tells me that ?. will be legal anywhere . is legal, so if x . y is legal (and it is) so will x ?. y be legal.
Yes, I forgot 'a . b' was legal - my bad.
[...]
The difference is that 'a.b.c.d' will result in AttributeError as soon as something is None while 'a?.b?.c?.d' will return None instead.
Correct. Because sometimes you want an AttributeError, and sometimes you want None.
You are criticising the operator for doing what it is designed and intended to do. You might as well criticise getattr(obj, 'spam', None) for returning None.
If you want an AttributeError, then don't use ?. and use ordinary . instead.
Again, you missed my point.
Likewise the logical operators "or" and "and" are designed to short-circuit. If ?? and friends are a mistake because they short-circuit, why aren't "or" and "and" mistakes? I'm not asking this as a rhetorical question. If you think there is a reason why it is okay for or/and to short-circuit, but it is bad for ?? and friends to short-circuit, then please explain why they are different. I will be very happy to listen to your arguments.
The argument about this is that '?.' short-circuits execution *silently*.
Other short-circuit operators also short-circuit execution silently. That's what they are designed to do.
# This isn't what actually happens. py> x = 0 py> result = x and 1/x __main__:1: UserWarning: Short-cut operation occurred, the right hand operand was not evaluated!!! Do not panic, this is the expected behaviour!!! py> print(result) 0
Instead of AttributeError you get None. You may chain ?. in order to lazily traverse a long tree,
Correct, that is what it is designed to do.
inadvertently assign None to a variable, continue code execution and fail later rather than sooner:
email = request?.context?.user?.email # None ... sendmail(subject, body, email)
Some (Antoine) rightly argued this may even have security implications (replace 'email' with 'password').
I'll have to remember that. Whenever there's any proposal for a feature I don't like, just claim it "may even have security implications". Who needs evidence when we have Fear, Uncertainty, Doubt?
OK, I'm done replying to you. I just wish I did it earlier (my bad).
-- Giampaolo - http://grodola.blogspot.com

On Tue, Jul 24, 2018, 5:50 AM Steven D'Aprano steve@pearwood.info wrote:
But what certainly *is* implicity is David Mertz' suggestion for a magical None-aware proxy:
x.attribute
The only way to tell whether that was an ordinary attribute lookup or a none-aware lookup would be to carefully inspect x and find out whether it was an instance of the None-aware proxy class or not.
Every use I've suggested for the magic proxy is similar to:
NullCoalesce(cfg).user.profile.food
Yes, the class is magic. That much more so in the library I published last night that utilizes wrapt.ObjectProxy. But it's also pretty explicit in that an actual *word* announces that funny stuff is going to happen on the same line.
Of course the this could be abused with:
cfg = NoneCoalesce(cfg) ... 1000 lines ... do_something(cfg)
But then, I could also write a property that actually started a computation of the millionth digit of pi while launching a DDoS attack on python.org when a user accessed 'x.attribute'.
NoneCoalesce or GreedyAccess are magic, but in their intended use, they are as little magical as possible to deal with messy nested data.

On 24/07/18 12:02, David Mertz wrote:
Every use I've suggested for the magic proxy is similar to:
NullCoalesce(cfg).user.profile.food
Yes, the class is magic. That much more so in the library I published last night that utilizes wrapt.ObjectProxy. But it's also pretty explicit in that an actual*word* announces that funny stuff is going to happen on the same line.
Foo(cfg).user.profile.food
Is that explicit that funny stuff is going to happen on the same line? I wouldn't generally assume so, I'd just assume the coder created a throwaway object to get at an attribute. You have to know that "NullCoalesce" does magic before it is at all explicit that funny stuff will happen. Thinking about it, NullCoalesce() may be *less* explicit than ?. because at least that doesn't look like ordinary attribute reference.
I'm still of the opinion that both approaches are trying to solve a problem that's too niche to merit them, BTW.

On Tuesday, July 24, 2018 at 1:38:42 PM UTC+2, Rhodri James wrote:
-snip- I'm still of the opinion that both approaches are trying to solve a problem that's too niche to merit them, BTW.
That's also my impression. Hence the second approach: it does not require any change to python, it's just a tool for that niche, so imho it's the right approach. Such a wrapper can even be provided by the lib that produced such nested attributes in the first place. The operator are a tool which seems designed to the same niche issue, but is exposed as a core language feature. It may be interesting if it provides a lot of side benefits, so the tool is so braodly useful it outgrowned it's niche origin. At this point, I do not think it does.

On 24/07/18 12:56, Grégory Lielens wrote:
On Tuesday, July 24, 2018 at 1:38:42 PM UTC+2, Rhodri James wrote:
-snip- I'm still of the opinion that both approaches are trying to solve a problem that's too niche to merit them, BTW.
That's also my impression. Hence the second approach: it does not require any change to python, it's just a tool for that niche, so imho it's the right approach.
OK, I'm confused. Either you agree with me, and think the second approach is also wrong, or you think the second approach is right and disagree with me. Which is it?

Both approaches should not be exposed as core language, but as facilitating tools to be used if, in your application, you have to traverse deep semi-regular attributes hierarchies. The operator approach, by definition, is part of the core, so -1 The wrapper does not need to be included in python, so the discussion is meaningless: people define the tools they want and use them as they please. Personally, I think it would be nice to have that in the standard distrib, especially wrapt or something as powerful. But I don't really care in fact, what good is that now I know about it so I can use it...

On Jul 24, 2018, at 7:37 AM, Rhodri James rhodri@kynesim.co.uk wrote:
On 24/07/18 12:02, David Mertz wrote: Every use I've suggested for the magic proxy is similar to: NullCoalesce(cfg).user.profile.food Yes, the class is magic. That much more so in the library I published last night that utilizes wrapt.ObjectProxy. But it's also pretty explicit in that an actual*word* announces that funny stuff is going to happen on the same line.
Foo(cfg).user.profile.food
Is that explicit that funny stuff is going to happen on the same line? I wouldn't generally assume so, I'd just assume the coder created a throwaway object to get at an attribute. You have to know that "NullCoalesce" does magic before it is at all explicit that funny stuff will happen. Thinking about it, NullCoalesce() may be *less* explicit than ?. because at least that doesn't look like ordinary attribute reference.
I'm still of the opinion that both approaches are trying to solve a problem that's too niche to merit them, BTW.
-- Rhodri James *-* Kynesim Ltd
The fact that you changed NullCoalesce into Foo to show lack of explicitness seems a straw-man. Words are FULL of meaning, while symbols are less so. The biggest issue I see with the use of ? here is that ? does have some meaning, it says we are going to be (or have) asked a question, it doesn’t tell us what the question is. Most of the other symbols used have a long history of meaning (yes = has the problem that historically it has had two possible meanings). To me, ? gives no indication that it is going to ask about Nullness.
?. has some indication that we are doing an attribute access that is in some way conditional, but a?.b could mean that we are conditional on a not being null, or it could be asking to suppress any and all error in getting b, even if a is an int and thus doesn’t have a b. The words carry a lot more meaning.

On 24/07/18 13:07, Richard Damon wrote:
The fact that you changed NullCoalesce into Foo to show lack of explicitness seems a straw-man. Words are FULL of meaning, while symbols are less so. The biggest issue I see with the use of ? here is that ? does have some meaning, it says we are going to be (or have) asked a question, it doesn’t tell us what the question is. Most of the other symbols used have a long history of meaning (yes = has the problem that historically it has had two possible meanings). To me, ? gives no indication that it is going to ask about Nullness.
Oh, I don't disagree with you about ? not giving any much indication that it's about Nullness except for the (relatively short) history of using it to mean exactly that in C# etc. However I don't think that a class of whatever name doing something magic is any better.

On Tue, Jul 24, 2018 at 08:07:36AM -0400, Richard Damon wrote:
The fact that you changed NullCoalesce into Foo to show lack of explicitness seems a straw-man.
I understood Rhodri as making the point that if you don't know what NullCoalesce means or does, it might as well be called Foo. There's no hint in the syntax that something magical is happening:
MyClass(obj).spam.eggs # ordinary attribute lookup
YourClass(obj).spam.eggs # still ordinary attribute lookup
Mxyzptlk(obj).spam.eggs # looks like ordinary attribute lookup
NullCoalesce(obj).spam.eggs # IT'S A TRAP!
Words are FULL of meaning, while symbols are less so.
It isn't a competition to squeeze as much meaning as possible into a single word or symbol. Precision is more important than fullness.
A case in point: the word "add" has six meanings listed by WordNet, and the Mobi Thesaurus gives 102 synonyms for it. A function or method called "add" could do anything:
- add users to a database; - add pages to a chapter; - add elements to a set; - add items to a queue;
etc. In contrast, the + symbol in Python has two standard meanings:
- numeric addition; - sequence concatenation;
and while it is true that with operator overloading a class could make the + operator do anything, that is usually taken as an argument to avoid operator overloading, or at least use it cautiously.
Surely you don't think that the + operator is a mistake because the word "add" is more full of meaning than the symbol? If not, what point were you trying to make?
The biggest issue I see with the use of ? here is that ? does have some meaning, it says we are going to be (or have) asked a question, it doesn’t tell us what the question is.
Sometimes you just have to learn the meanings of words or symbols.
What does NullCoalesce mean? Until this PEP was proposed, I had no idea this was even a thing, and in truth I actually had to look up "coalesce" in a dictionary to be sure I understood it correctly, because the context doesn't seem quite right.
(It still doesn't -- it might be the standard comp sci term for this feature, but I don't think it quite matches the ordinary usage of the word.)
Ask a dozen programming beginners what "NullCoalesce" does, and I expect every one of them will say "No idea".
Strangely enough, nobody complains about having to learn what "import" does, or slicing syntax x[:], or string backslash escapes "\n", or "property". Because we're used to them, we just accept that you have to learn the meaning of things the first time you see them. We aren't born knowing what "class" does, or the difference between x[a] and x(a).
Every single one of us, without exception, had to learn what . means at some point or another.
And yet, its wailing and gnashing of teeth and panic in the streets over the idea that people might have to learn what ?. means in the same way they learned what **kwargs or mylist[1:] or obj.attr means.
"It's a question, but what is the question? How will I ever find out? If only there was a website where I might look up Python operators and learn what they do!!!"
?. has some indication that we are doing an attribute access that is in some way conditional, but a?.b could mean that we are conditional on a not being null, or it could be asking to suppress any and all error in getting b, even if a is an int and thus doesn’t have a b. The words carry a lot more meaning.
Yeah, because words are always obvious.
import ast # Who can remember all these damned TLAs? import bdm # Births Deaths Marriages? import csv # Court Services Victoria? import mailcap # what the postman wears on his head? import pickle # something to do with condiments? import turtle # somebody is taking the micky import curses # good thing I'm not superstitious
"But Steve, you don't understand. Having to learn the meaning of words you haven't learned before is right and proper and unavoidable. But all new punctuation symbols must be intuitively obvious to newborn babies, or else they are the Devil's Mark, or Perl, not sure which is worse."
(Its called sarcasm, not "strawman". Just sayin'.)

On Tue, Jul 24, 2018, 7:38 AM Rhodri James rhodri@kynesim.co.uk wrote:
On 24/07/18 12:02, David Mertz wrote:
Every use I've suggested for the magic proxy is similar to:
NullCoalesce(cfg).user.profile.food
Yes, the class is magic. That much more so in the library I published
last
night that utilizes wrapt.ObjectProxy. But it's also pretty explicit in that an actual*word* announces that funny stuff is going to happen on
the
same line.
Foo(cfg).user.profile.food
Is that explicit that funny stuff is going to happen on the same line? I wouldn't generally assume so, I'd just assume the coder created a throwaway object to get at an attribute.
Foo isn't a very indicative name. NoneCoalesce (or NullCoalesce, or GreedyAccess) is. But if I had any doubt, I could read the docstring for Foo to find out how magical it was, and in what way. I definitely know *something* is happening by creating that new instance in the line.
I'm still of the opinion that both approaches are trying to solve a
problem that's too niche to merit them, BTW.
That doesn't make sense to me. You think my little library shouldn't be allowed on PyPI? I don't force the couple classes on anyone, but if they happen to help someone (or PyMaybe, or some other library, does) they don't change anything about Python itself. Syntax is a very different matter.

On 24/07/18 14:02, David Mertz wrote:
On Tue, Jul 24, 2018, 7:38 AM Rhodri James rhodri@kynesim.co.uk wrote:
I'm still of the opinion that both approaches are trying to solve a problem that's too niche to merit them, BTW.
That doesn't make sense to me. You think my little library shouldn't be allowed on PyPI? I don't force the couple classes on anyone, but if they happen to help someone (or PyMaybe, or some other library, does) they don't change anything about Python itself. Syntax is a very different matter.
I have no objection to anyone putting anything on PyPI. Putting it (or an equivalent) in the standard library is much more problematical, and you were talking about that. I think your little library is a much richer source of bugs than you think it is, and dealing with messy data access isn't a good enough reason to put that much temptation in front of naive users.

I *definitely* don't think a little tool I wrote in a couple hours last night belongs in the standard library (with most of the heavy lifting actually done by wrapt—which is really well designed, and is also not in the standard library). I also don't think PyMaybe belongs there, even though it's a couple years old.
Now, perhaps, if 'coalescing' is widely used for a year or two. And bugs are fixed. And the API is tweaked based on experience with it's use. And so on... At that point, *maybe* something derived from it might be appropriate.
On Tue, Jul 24, 2018, 9:08 AM Rhodri James rhodri@kynesim.co.uk wrote:
On 24/07/18 14:02, David Mertz wrote:
On Tue, Jul 24, 2018, 7:38 AM Rhodri James rhodri@kynesim.co.uk wrote:
I'm still of the opinion that both approaches are trying to solve a problem that's too niche to merit them, BTW.
That doesn't make sense to me. You think my little library shouldn't be allowed on PyPI? I don't force the couple classes on anyone, but if they happen to help someone (or PyMaybe, or some other library, does) they
don't
change anything about Python itself. Syntax is a very different matter.
I have no objection to anyone putting anything on PyPI. Putting it (or an equivalent) in the standard library is much more problematical, and you were talking about that. I think your little library is a much richer source of bugs than you think it is, and dealing with messy data access isn't a good enough reason to put that much temptation in front of naive users.
-- Rhodri James *-* Kynesim Ltd

On Tue, Jul 24, 2018 at 11:02 PM, David Mertz mertz@gnosis.cx wrote:
On Tue, Jul 24, 2018, 7:38 AM Rhodri James rhodri@kynesim.co.uk wrote:
On 24/07/18 12:02, David Mertz wrote:
Every use I've suggested for the magic proxy is similar to:
NullCoalesce(cfg).user.profile.food
Yes, the class is magic. That much more so in the library I published last night that utilizes wrapt.ObjectProxy. But it's also pretty explicit in that an actual*word* announces that funny stuff is going to happen on the same line.
Foo(cfg).user.profile.food
Is that explicit that funny stuff is going to happen on the same line? I wouldn't generally assume so, I'd just assume the coder created a throwaway object to get at an attribute.
Foo isn't a very indicative name. NoneCoalesce (or NullCoalesce, or GreedyAccess) is. But if I had any doubt, I could read the docstring for Foo to find out how magical it was, and in what way. I definitely know *something* is happening by creating that new instance in the line.
Okay. Check this out, then:
x = Foo(cfg).user.profile x.food
Remember, these lines could be a very long way apart. You could pass 'x' to a function unrelated to the line of code that created it. And 'x.food' still has to have the magic. You can't mandate that the call to Coalesce be on the same line as the attribute access - Python doesn't work that way.
So your perfectly ordinary dot operator now does magic in addition to normal attribute access. See why it's a dangerous thing?
ChrisA

On Tue, Jul 24, 2018, 9:09 AM Chris Angelico rosuav@gmail.com wrote:
x = Foo(cfg).user.profile x.food
Remember, these lines could be a very long way apart. You could pass 'x' to a function unrelated to the line of code that created it. And 'x.food' still has to have the magic. You can't mandate that the call to Coalesce be on the same line as the attribute access - Python doesn't work that way.
So your perfectly ordinary dot operator now does magic in addition to normal attribute access. See why it's a dangerous thing?
Yes, of course. That's why I would recommend best practice is to unbox or otherwise use the conditional value as close to the magic code as feasible.
Likewise, as I noted a little while ago, 'x.food' could equally well be a property that executed arbitrarily slow, magical, obscure, or even malicious operations. Equally, 'x + y' could do absolutely anything if we define .__add__() or .__radd__() methods.
Everything in Python is magical in that sense, but we should deliberately keep the magic constrained to the amount needed.

On Tue, Jul 24, 2018 at 07:02:54AM -0400, David Mertz wrote:
On Tue, Jul 24, 2018, 5:50 AM Steven D'Aprano steve@pearwood.info wrote:
But what certainly *is* implicity is David Mertz' suggestion for a magical None-aware proxy:
x.attribute
The only way to tell whether that was an ordinary attribute lookup or a none-aware lookup would be to carefully inspect x and find out whether it was an instance of the None-aware proxy class or not.
Every use I've suggested for the magic proxy is similar to:
NullCoalesce(cfg).user.profile.food
I trust that you don't expect everyone to religiously follow the exact letter of your examples. Being able to extract out subexpressions into variables is an important programming technique, and we ought to be able to do things like this:
config = NullCoalesce(cfg) process(config)
# later... user = config.user user_records[user.id] = user food = get_preferred_food(user)
Now there are at least four potentially magic proxy objects floating around: config, user, the user.id key, and food.
It is naive (if not a little disingenuous) to suggest that this NullCoalesce proxy object will always be used in a single expression and never passed as argument to a function, inserted in a list or dict, or otherwise processed any distance away from the call to NullCoalesce.
Consequently, the moment we see
from module import NullCoalesce
in a module, *every dot attribute access* becomes questionable until we've satisfied ourselves that the object in question isn't magic.
spam.attribute eggs.attribute
One of those is ordinary attribute access that can raise AttributeError, and the other is magic attribute access that silently suppresses AttributeErrors. Which is which?
spam?.attribute eggs.attribute
One of those is ordinary attribute access that can raise AttributeError if the object is None, and the other is None-aware "maybe dot" attribute access that returns None if the object is None.
I wish there was a way to tell which is which... if only they used different syntax or something... *wink*
Yes, the class is magic. That much more so in the library I published last night that utilizes wrapt.ObjectProxy. But it's also pretty explicit in that an actual *word* announces that funny stuff is going to happen on the same line.
Of course the this could be abused with:
cfg = NoneCoalesce(cfg) ... 1000 lines ... do_something(cfg)
Why does Python have named variables if it is "abuse" to use them?

On Wed, Jul 25, 2018 at 8:00 PM Steven D'Aprano steve@pearwood.info wrote:
Every use I've suggested for the magic proxy is similar to: NullCoalesce(cfg).user.profile.food
Thanks Steven! Several things you suggest urge some minor improvements to my slightly-more-than-toy library `coalescing` ( https://pypi.org/project/coalescing/).
I trust that you don't expect everyone to religiously follow the exact letter of your examples. Being able to extract out subexpressions into variables is an important programming technique, and we ought to be able to do things like this:
config = NullCoalesce(cfg) process(config) # later... user = config.user user_records[user.id] = user food = get_preferred_food(user)
Yes, absolutely! In many cases this will JUST WORK because of the magic in wrapt.ObjectProxy. But it also makes me realize that I should provide a plain old function `unbox()` as well as the method. In particular, `unbox(obj)` can and will figure out whether the object is really a proxy at all, and if not simply return `obj` itself. Then you do no harm by scattering unbox() calls in places where they may or may not be needed.
I also think that even though it was a typo at first, the name NullCoalesce is better than NoneCoalesce. I'll provide an alias.
spam?.attribute eggs.attribute
One of those is ordinary attribute access that can raise AttributeError if the object is None, and the other is None-aware "maybe dot" attribute access that returns None if the object is None.
That is disingenuous, I think. Can this raise an AttributeError?
spam?.eggs?.bacon
Of course it can! And this is exactly the pattern used in many examples in the PEP and the discussion. So the PEP would create a situation where code will raise AttributeError in a slightly—and subtly—different set of circumstances than plain attribute access will.
The fact that half the readers of this thread won't immediately see just when that AttributeError might occur (based on lots of prior misunderstandings by smart posters) makes the operator that much more magical.

On Thu, Jul 26, 2018 at 11:02 AM, David Mertz mertz@gnosis.cx wrote:
That is disingenuous, I think. Can this raise an AttributeError?
spam?.eggs?.bacon
Of course it can! And this is exactly the pattern used in many examples in the PEP and the discussion. So the PEP would create a situation where code will raise AttributeError in a slightly—and subtly—different set of circumstances than plain attribute access will.
I don't understand. If it were to raise AttributeError, it would be because spam (or spam.eggs) isn't None, but doesn't have an attribute eggs (or bacon). Exactly the same as regular attribute access. How is it slightly different? Have I missed something?
ChrisA

On Wed, Jul 25, 2018 at 9:20 PM Chris Angelico rosuav@gmail.com wrote:
On Thu, Jul 26, 2018 at 11:02 AM, David Mertz mertz@gnosis.cx wrote:
That is disingenuous, I think. Can this raise an AttributeError?
spam?.eggs?.bacon
Of course it can! And this is exactly the pattern used in many examples
in
the PEP and the discussion. So the PEP would create a situation where
code
will raise AttributeError in a slightly—and subtly—different set of circumstances than plain attribute access will.
I don't understand. If it were to raise AttributeError, it would be because spam (or spam.eggs) isn't None, but doesn't have an attribute eggs (or bacon). Exactly the same as regular attribute access. How is it slightly different? Have I missed something?
That was my reaction, too.
food = spam?.eggs?.bacon
Can be rewritten as:
food = spam if spam is not None and spam.eggs is not None: food = spam.eggs.bacon
They both behave identically, no? Maybe I missed the point David was trying to make.

On Thu, Jul 26, 2018 at 11:45 AM, Nicholas Chammas nicholas.chammas@gmail.com wrote:
On Wed, Jul 25, 2018 at 9:20 PM Chris Angelico rosuav@gmail.com wrote:
On Thu, Jul 26, 2018 at 11:02 AM, David Mertz mertz@gnosis.cx wrote:
That is disingenuous, I think. Can this raise an AttributeError?
spam?.eggs?.bacon
Of course it can! And this is exactly the pattern used in many examples in the PEP and the discussion. So the PEP would create a situation where code will raise AttributeError in a slightly—and subtly—different set of circumstances than plain attribute access will.
I don't understand. If it were to raise AttributeError, it would be because spam (or spam.eggs) isn't None, but doesn't have an attribute eggs (or bacon). Exactly the same as regular attribute access. How is it slightly different? Have I missed something?
That was my reaction, too.
food = spam?.eggs?.bacon
Can be rewritten as:
food = spam if spam is not None and spam.eggs is not None: food = spam.eggs.bacon
They both behave identically, no? Maybe I missed the point David was trying to make.
Aside from questions of repeated evaluation/assignment, yes. The broad semantics should be the same.
(If you want to get technical, "spam" gets evaluated exactly once, "spam.eggs" a maximum of once, and "food" gets assigned exactly once. Your equivalent may evaluate and assign multiple times.)
ChrisA

So now at least TWO proponents of 505 cannot successfully translate a very simple example taken almost directly from the PEP!
Is that REALLY a good argument for it being helpful, and not being a bug magnet?!
On Wed, Jul 25, 2018 at 9:57 PM Chris Angelico rosuav@gmail.com wrote:
On Thu, Jul 26, 2018 at 11:45 AM, Nicholas Chammas nicholas.chammas@gmail.com wrote:
On Wed, Jul 25, 2018 at 9:20 PM Chris Angelico rosuav@gmail.com wrote:
On Thu, Jul 26, 2018 at 11:02 AM, David Mertz mertz@gnosis.cx wrote:
That is disingenuous, I think. Can this raise an AttributeError?
spam?.eggs?.bacon
Of course it can! And this is exactly the pattern used in many
examples
in the PEP and the discussion. So the PEP would create a situation where code will raise AttributeError in a slightly—and subtly—different set of circumstances than plain attribute access will.
I don't understand. If it were to raise AttributeError, it would be because spam (or spam.eggs) isn't None, but doesn't have an attribute eggs (or bacon). Exactly the same as regular attribute access. How is it slightly different? Have I missed something?
That was my reaction, too.
food = spam?.eggs?.bacon
Can be rewritten as:
food = spam if spam is not None and spam.eggs is not None: food = spam.eggs.bacon
They both behave identically, no? Maybe I missed the point David was
trying
to make.
Aside from questions of repeated evaluation/assignment, yes. The broad semantics should be the same.
(If you want to get technical, "spam" gets evaluated exactly once, "spam.eggs" a maximum of once, and "food" gets assigned exactly once. Your equivalent may evaluate and assign multiple times.)
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Thu, Jul 26, 2018 at 12:14 PM, David Mertz mertz@gnosis.cx wrote:
So now at least TWO proponents of 505 cannot successfully translate a very simple example taken almost directly from the PEP!
Is that REALLY a good argument for it being helpful, and not being a bug magnet?!
Okay. I'll give you the ACTUAL transformation.
food = spam?.eggs?.bacon
can be rewritten as
_tmp = spam if _tmp is not None: _tmp = _tmp.eggs if _tmp is not None: _tmp = _tmp.bacon food = _tmp
Except for the fact that _tmp doesn't exist. Do you see why we accept "mostly-correct" transformations? It is *actually impossible* to perfectly represent short-circuiting semantics in Python! And before you go "well that proves my point, this suggestion is bad", let's apply the same test to a few other pieces of syntax. Rewrite the following statements without using the syntactic feature named in the comment:
# 1) Decorators @deco def func(): ...
# 2) "yield from" def chain(*iters): for iter in iters: yield from iter
# 3) and the big one: generator expressions # yes, I'm deliberately using x multiple ways here def f(x): return x*x x = range(10) x = (f(x) for x in x if x % 2)
I can pretty much guarantee you that you'll get these at least slightly wrong. Even experts will get genexps wrong. The most pedantic will put caveats on their equivalencies (like where I said "_tmp doesn't exist"), but chances are you won't even notice the discrepancies.
So if that makes ?. bad, it makes decorators, yield from, and genexps far FAR worse. We'd better go back to Python 2.3, before they all existed.
ChrisA

On Wed, Jul 25, 2018 at 10:29 PM Chris Angelico rosuav@gmail.com wrote:
food = spam?.eggs?.bacon
can be rewritten as
_tmp = spam if _tmp is not None: _tmp = _tmp.eggs if _tmp is not None: _tmp = _tmp.bacon food = _tmp
Yes, that looks right. Well, you need a `del _tmp` at the end; but it's almost right. My point was that both you and Nicholas Chammas failed to recognize that the other translation was wrong... I recognize it does something "kinda similar." But the semantics of the operators are just plain hard to grok, even by their strongest advocates.
I can write lots of things that are "mostly correct" already in Python. Most easily, I can write:
try: food = spam.eggs.bacon except: food = None
That does what is actually needed about 95% of the time. It's also clear and easy to understand.
It is *actually impossible* to
perfectly represent short-circuiting semantics in Python!
It's INCREDIBLY EASY to represent short-circuiting semantics in Python! What on earth are you talking about? That's what the if/elif/else blocks do.
And before you go "well that proves my point, this suggestion is bad", let's
apply the same test to a few other pieces of syntax. Rewrite the following statements without using the syntactic feature named in the comment:
This is childishly simple:
# 1) Decorators @deco def func(): ...
def func(): ... func = deco(func)
OK, this one is harder. The "mostly correct" version is easy. But the actual full version is nuanced (see https://www.python.org/dev/peps/pep-0380/ for details).
# 2) "yield from"
def chain(*iters): for iter in iters: yield from iter
# The simple approximation: for iter in iters:
for _ in iter:
yield iter
# 3) and the big one: generator expressions # yes, I'm deliberately using x multiple ways here def f(x): return x*x x = range(10) x = (f(x) for x in x if x % 2)
I'm not going to bother with that. I'd fire anyone who wrote it, after code review. Minus the abuse of names, it's just:
def gen(xs):
for x in xs:
if x % 2:
yield f(x)
x = gen(xs)

On Thu, Jul 26, 2018 at 12:45 PM, David Mertz mertz@gnosis.cx wrote:
On Wed, Jul 25, 2018 at 10:29 PM Chris Angelico rosuav@gmail.com wrote:
It is *actually impossible* to perfectly represent short-circuiting semantics in Python!
It's INCREDIBLY EASY to represent short-circuiting semantics in Python! What on earth are you talking about? That's what the if/elif/else blocks do.
Except for the aforementioned "single lookup, single assignment" semantics. You can't achieve that with an if/else block.
And before you go "well that proves my point, this suggestion is bad", let's apply the same test to a few other pieces of syntax. Rewrite the following statements without using the syntactic feature named in the comment:
This is childishly simple:
# 1) Decorators @deco def func(): ...
def func(): ... func = deco(func)
Okay. Try this then:
def deco(f): print(globals()[f.__name__]) return f func = "funky" @deco def func(): pass
Childishly simple? Or does it have its own subtleties? You might think this is trivial and pointless, but consider the case of a writable property:
class X: @property def spam(self): return 42 @spam.setter def spam(self, val): print("Setting spam to", val)
This version won't work:
class X: def spam(self): return 42 spam = property(spam) def spam(self, val): print("Setting spam to", val) spam = spam.setter(spam)
See how easy it is to create an imperfect representation?
# 3) and the big one: generator expressions # yes, I'm deliberately using x multiple ways here def f(x): return x*x x = range(10) x = (f(x) for x in x if x % 2)
I'm not going to bother with that. I'd fire anyone who wrote it, after code review. Minus the abuse of names, it's just:
def gen(xs):
for x in xs: if x % 2: yield f(x)
x = gen(xs)
Modulo a TON of subtleties about exactly what gets evaluated when. Again, you've made an imperfect equivalence. So if you're going to complain about people making ?. equivalences that aren't perfect, make sure you're just as pedantic about existing syntax. You scored one out of three, and only by punting to the existing PEP.
ChrisA

Btw. Here's a way of spelling the proposed syntax that gets the semantics right:
# pip install coalescing NullCoalesce(spam).eggs.bacon
On Wed, Jul 25, 2018 at 10:14 PM David Mertz mertz@gnosis.cx wrote:
So now at least TWO proponents of 505 cannot successfully translate a very simple example taken almost directly from the PEP!
Is that REALLY a good argument for it being helpful, and not being a bug magnet?!
On Wed, Jul 25, 2018 at 9:57 PM Chris Angelico rosuav@gmail.com wrote:
On Thu, Jul 26, 2018 at 11:45 AM, Nicholas Chammas nicholas.chammas@gmail.com wrote:
On Wed, Jul 25, 2018 at 9:20 PM Chris Angelico rosuav@gmail.com
wrote:
On Thu, Jul 26, 2018 at 11:02 AM, David Mertz mertz@gnosis.cx wrote:
That is disingenuous, I think. Can this raise an AttributeError?
spam?.eggs?.bacon
Of course it can! And this is exactly the pattern used in many
examples
in the PEP and the discussion. So the PEP would create a situation where code will raise AttributeError in a slightly—and subtly—different set of circumstances than plain attribute access will.
I don't understand. If it were to raise AttributeError, it would be because spam (or spam.eggs) isn't None, but doesn't have an attribute eggs (or bacon). Exactly the same as regular attribute access. How is it slightly different? Have I missed something?
That was my reaction, too.
food = spam?.eggs?.bacon
Can be rewritten as:
food = spam if spam is not None and spam.eggs is not None: food = spam.eggs.bacon
They both behave identically, no? Maybe I missed the point David was
trying
to make.
Aside from questions of repeated evaluation/assignment, yes. The broad semantics should be the same.
(If you want to get technical, "spam" gets evaluated exactly once, "spam.eggs" a maximum of once, and "food" gets assigned exactly once. Your equivalent may evaluate and assign multiple times.)
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/
-- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.

On Thu, Jul 26, 2018 at 12:30 PM, David Mertz mertz@gnosis.cx wrote:
Btw. Here's a way of spelling the proposed syntax that gets the semantics right:
# pip install coalescing NullCoalesce(spam).eggs.bacon
Let's try it.
rosuav@sikorsky:~$ sudo python3 -m pip install coalescing Collecting coalescing Downloading https://files.pythonhosted.org/packages/f3/f4/120f04cc59f9fa8c55c711b67f1c9c... Installing collected packages: coalescing Running setup.py install for coalescing ... done Successfully installed coalescing-0.1.1
rosuav@sikorsky:~$ python3 Python 3.8.0a0 (heads/literal_eval-exception:ddcb2eb331, Feb 21 2018, 04:32:23) [GCC 6.3.0 20170516] on linux Type "help", "copyright", "credits" or "license" for more information.
from coalescing import NullCoalesce
Traceback (most recent call last): File "<stdin>", line 1, in <module> ModuleNotFoundError: No module named 'coalescing'
from coalesce import NullCoalesce
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.8/site-packages/coalesce.py", line 56, in <module> import wrapt ModuleNotFoundError: No module named 'wrapt'
A bit problematic. But after (a) figuring out that your module is named "coalesce" even though I installed "coalescing" AND (b) going and separately installing wrapt, and finally (c) doing the import that you didn't mention, we still have this fundamental problem:
rosuav@sikorsky:~$ python3 Python 3.8.0a0 (heads/literal_eval-exception:ddcb2eb331, Feb 21 2018, 04:32:23) [GCC 6.3.0 20170516] on linux Type "help", "copyright", "credits" or "license" for more information.
from coalesce import NullCoalesce from types import SimpleNamespace spam, spam.eggs, spam.eggs.bacon = SimpleNamespace(), SimpleNamespace(), 42 NullCoalesce(spam).eggs.bacon
<NullCoalesce proxy for 42>
That isn't 42. That's a thing that, forever afterwards, will be a proxy. And look at this:
spam.nil = None print(NullCoalesce(spam).nil)
<NullCoalesce proxy for None>
print(NullCoalesce(spam).nil.nil)
None
print(NullCoalesce(spam).nil.nil.nil)
Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'NoneType' object has no attribute 'nil'
Whoooooops.
So, no, this is most definitely NOT equivalent to the proposed semantics.
ChrisA

On Wed, Jul 25, 2018 at 10:41 PM Chris Angelico rosuav@gmail.com wrote:
A bit problematic. But after (a) figuring out that your module is named "coalesce" even though I installed "coalescing" AND (b) going and separately installing wrapt, and finally (c) doing the import that you didn't mention, we still have this fundamental problem:
Yeah, yeah. I know it's alpha software I wrote two nights ago, and slightly patched 5 minutes before that post. You fixed those concerns; I'll happily take PRs on fixing them better.
from coalesce import NullCoalesce from types import SimpleNamespace spam, spam.eggs, spam.eggs.bacon = SimpleNamespace(),
SimpleNamespace(), 42
NullCoalesce(spam).eggs.bacon
<NullCoalesce proxy for 42>
That isn't 42. That's a thing that, forever afterwards, will be a proxy.
Yeah. That's a thing it does. It's less of an issue than you think since, e.g.:
from coalesce import NullCoalesce from types import SimpleNamespace spam, spam.eggs, spam.eggs.bacon = SimpleNamespace(),
SimpleNamespace(), 42
NullCoalesce(spam).eggs.bacon + 1
43
NullCoalesce(spam).eggs.bacon * 1
42
Most things you actually do with the proxy wind up getting the value back once it is used. However, this seems to be a bug that I inherit from wrapt.ObjectProxy:
NullCoalesce(spam).eggs.bacon + 0
ValueError: wrapper has not been initialized
If you do an operation that combines the proxy value with "Falsey" values, it doesn't do the implicit unboxing. Same with e.g. `proxyval + ""` for strings, unfortunately. I'm not sure how to fix that.
spam.nil = None
print(NullCoalesce(spam).nil.nil)
None
print(NullCoalesce(spam).nil.nil.nil)
Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'NoneType' object has no attribute 'nil'
Why is this wrong? This is EXACTLY the same behavior as the `?.` operator would have in the last case. Do you not recognize the behavior you are advocating in PEP 505?
I recognize that the proxy value not always "auto-unboxing" is a limitation that I don't like.

On Thu, Jul 26, 2018 at 12:56 PM, David Mertz mertz@gnosis.cx wrote:
On Wed, Jul 25, 2018 at 10:41 PM Chris Angelico rosuav@gmail.com wrote:
A bit problematic. But after (a) figuring out that your module is named "coalesce" even though I installed "coalescing" AND (b) going and separately installing wrapt, and finally (c) doing the import that you didn't mention, we still have this fundamental problem:
Yeah, yeah. I know it's alpha software I wrote two nights ago, and slightly patched 5 minutes before that post. You fixed those concerns; I'll happily take PRs on fixing them better.
PRs? Nope. I don't think it's possible to do this with correct semantics without language support.
spam.nil = None print(NullCoalesce(spam).nil.nil)
None
print(NullCoalesce(spam).nil.nil.nil)
Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'NoneType' object has no attribute 'nil'
Why is this wrong? This is EXACTLY the same behavior as the `?.` operator would have in the last case. Do you not recognize the behavior you are advocating in PEP 505?
With PEP 505, I could write this:
spam = SimpleNamespace() spam.nil = None print(spam?.nil)
None
print(spam?.nil?.nil)
None
print(spam?.nil?.nil?.nil)
None
print(spam?.nil?.nil?.nil?.nil)
None
Can you do that with your proxy?
ChrisA

On Wed, Jul 25, 2018 at 11:08 PM Chris Angelico rosuav@gmail.com wrote:
Yeah, yeah. I know it's alpha software I wrote two nights ago, and
slightly
patched 5 minutes before that post. You fixed those concerns; I'll
happily
take PRs on fixing them better.
PRs? Nope. I don't think it's possible to do this with correct semantics without language support.
With PEP 505, I could write this:
print(spam?.nil?.nil?.nil?.nil)
None
Can you do that with your proxy?
Oh yeah. You are right! Thank you.
That's a bug in my proxy too. I'll figure out how to fix it in 0.1.2 soon. This is early alpha, and the things you're noting are valuable bug reports. But none of this is fundamentally unfixable in a library, nor even especially difficult.
I doubt I'll ever even use my own software. It's just a proof-of-concept that we can achieve the ACTUAL purpose of PEP 505 with no language changes. I don't very often have a need to solve the problem PEP 505 does... even though I very often work in the very domain it is intended to address (semi-structured nested data). Even if the PEP could be a little bit more elegant for a very few circumstances, it's just not anywhere close to deserving syntax... especially not syntax that even proponents tend to misunderstand the semantics of.

On Thu, Jul 26, 2018 at 1:19 PM, David Mertz mertz@gnosis.cx wrote:
On Wed, Jul 25, 2018 at 11:08 PM Chris Angelico rosuav@gmail.com wrote:
Yeah, yeah. I know it's alpha software I wrote two nights ago, and slightly patched 5 minutes before that post. You fixed those concerns; I'll happily take PRs on fixing them better.
PRs? Nope. I don't think it's possible to do this with correct semantics without language support.
With PEP 505, I could write this:
print(spam?.nil?.nil?.nil?.nil)
None
Can you do that with your proxy?
Oh yeah. You are right! Thank you.
That's a bug in my proxy too. I'll figure out how to fix it in 0.1.2 soon. This is early alpha, and the things you're noting are valuable bug reports. But none of this is fundamentally unfixable in a library, nor even especially difficult.
If you're going to make that work, then by definition you would be wrapping up the None, right? Which would mean that every one of the prints would say "<NullCoalesce proxy for None>". Which, in turn, means that you cannot do this:
NullCoalesce(spam).nil is None
or rather, it will always be False. With PEP 505, you most certainly *can* do this check, because it would really truly be None.
This IS fundamentally unfixable in a library.
ChrisA

On Wed, Jul 25, 2018, 11:27 PM Chris Angelico rosuav@gmail.com wrote:
That's a bug in my proxy too. I'll figure out how to fix it in 0.1.2
soon.
This is early alpha, and the things you're noting are valuable bug
reports.
But none of this is fundamentally unfixable in a library, nor even especially difficult.
If you're going to make that work, then by definition you would be wrapping up the None, right? Which would mean that every one of the prints would say "<NullCoalesce proxy for None>". Which, in turn, means that you cannot do this:
NullCoalesce(spam).nil is None
or rather, it will always be False. With PEP 505, you most certainly *can* do this check, because it would really truly be None.
This IS fundamentally unfixable in a library.
If your meaning of "fix" is simply "add new syntax", of course that's true. If it's "solve the actual problem that motivates the PEP" I can definitely fix it.
Right now, you can still always call .unbox() at the end to get the underlying value. I agree that's a little ugly, hence why I added the wrapt proxy stuff. Most operations trigger unboxing, but indeed not simply echoing to the shell.
I think I'll add a property spelled '_' to make it less busy (but more cryptic, I know). E.g.
NullCoalesce(spam).nil.nil.nil._ is None'
I also added an unbox() function that will pass through non-proxy objects untouched. So it's safe to unbox anything if you are unsure what it is.
Of course I'm not claiming any library is without a need to work with it's quirks and limits. But they are very few even in this 4-hours-of-work alpha.

BTW, even for the "compare to None" issue, the situation in 0.1.1 isn't as bad as you might think. Definitely a "<NullCoalesce proxy for None>" cannot be compared as "is None". But even there, this works:
NullCoalesce(spam).bacon
<NullCoalesce proxy for None>
NullCoalesce(spam).bacon == None
True
NullCoalesce(spam).bacon == 0
False
NullCoalesce(spam).bacon is None
False
So yeah, the docs should say "If you are using the `coalescing` library, use `==` rather than `is` to compare values with None".
Yes, that's contrary to best style in general Python... but it's a pretty small change to make in that specialized code that needs to deal with "hierarchical semi-structured data that uses None (rather than missing attributes or other sentinels) to mark unreachable branches."
On Wed, Jul 25, 2018 at 11:53 PM David Mertz david.mertz@gmail.com wrote:
On Wed, Jul 25, 2018, 11:27 PM Chris Angelico rosuav@gmail.com wrote:
That's a bug in my proxy too. I'll figure out how to fix it in 0.1.2
soon.
This is early alpha, and the things you're noting are valuable bug
reports.
But none of this is fundamentally unfixable in a library, nor even especially difficult.
If you're going to make that work, then by definition you would be wrapping up the None, right? Which would mean that every one of the prints would say "<NullCoalesce proxy for None>". Which, in turn, means that you cannot do this:
NullCoalesce(spam).nil is None
or rather, it will always be False. With PEP 505, you most certainly *can* do this check, because it would really truly be None.
This IS fundamentally unfixable in a library.
If your meaning of "fix" is simply "add new syntax", of course that's true. If it's "solve the actual problem that motivates the PEP" I can definitely fix it.
Right now, you can still always call .unbox() at the end to get the underlying value. I agree that's a little ugly, hence why I added the wrapt proxy stuff. Most operations trigger unboxing, but indeed not simply echoing to the shell.
I think I'll add a property spelled '_' to make it less busy (but more cryptic, I know). E.g.
NullCoalesce(spam).nil.nil.nil._ is None'
I also added an unbox() function that will pass through non-proxy objects untouched. So it's safe to unbox anything if you are unsure what it is.
Of course I'm not claiming any library is without a need to work with it's quirks and limits. But they are very few even in this 4-hours-of-work alpha.

On 2018-07-25 23:53, David Mertz wrote:
On Wed, Jul 25, 2018, 11:27 PM Chris Angelico <rosuav@gmail.com mailto:rosuav@gmail.com> wrote:
means that you cannot do this: >>> NullCoalesce(spam).nil is None This IS fundamentally unfixable in a library.
Right now, you can still always call .unbox() at the end to get the underlying value. I agree that's a little ugly, hence why I added the wrapt proxy stuff. Most operations trigger unboxing, but indeed not simply echoing to the shell.
Chris is correct to point out this problem with comparing to None. I have that problem with my own version of the proxy library, similar to what David is building: I use the proxy heavily; to the point where almost any line may be touching a proxy rather than a real value. To avoid bugs, I disallow "is None" comparisons, mandating "== None" instead.
Using unbox() is an alternative, but it is uglier than than swapping "as" for "==".

Do you know what helps readability? *Learning to read*
Do you know what helps? *leveraging intuition* If you're going to throw a question mark in there, which by the way, is far more visually intrusive than a '.', then it makes more sense to at least try to use it in a familiar way:
v = (a.b)?
## equivalent to
v = None try: v = a.b except AttributeError as e: if not e.args[0].startswith("'NonezType'"): raise e
Once you have learned to read ?. and friends, they will be as readable as .
and slicing is now.
That's an assumption that's difficult to prove. If ?. gets added to Python and proves to be a readability nightmare (as I believe it will be), it's too late to go back. It's a gamble. The question is: is the potential benefit worth the risk? and is there, perhaps, a better solution? I, personally am not convinced of either. I think the 'maybe' solution seems like a better alternative.
On Wed, Jul 25, 2018 at 9:40 PM, Chris Angelico rosuav@gmail.com wrote:
On Thu, Jul 26, 2018 at 12:30 PM, David Mertz mertz@gnosis.cx wrote:
Btw. Here's a way of spelling the proposed syntax that gets the semantics right:
# pip install coalescing NullCoalesce(spam).eggs.bacon
Let's try it.
rosuav@sikorsky:~$ sudo python3 -m pip install coalescing Collecting coalescing Downloading https://files.pythonhosted.org/packages/f3/f4/ 120f04cc59f9fa8c55c711b67f1c9c34d8a59c34cd69249e6ff61b098987 /coalescing-0.1.1.tar.gz Installing collected packages: coalescing Running setup.py install for coalescing ... done Successfully installed coalescing-0.1.1
rosuav@sikorsky:~$ python3 Python 3.8.0a0 (heads/literal_eval-exception:ddcb2eb331, Feb 21 2018, 04:32:23) [GCC 6.3.0 20170516] on linux Type "help", "copyright", "credits" or "license" for more information.
from coalescing import NullCoalesce
Traceback (most recent call last): File "<stdin>", line 1, in <module> ModuleNotFoundError: No module named 'coalescing'
from coalesce import NullCoalesce
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.8/site-packages/coalesce.py", line 56, in <module> import wrapt ModuleNotFoundError: No module named 'wrapt'
A bit problematic. But after (a) figuring out that your module is named "coalesce" even though I installed "coalescing" AND (b) going and separately installing wrapt, and finally (c) doing the import that you didn't mention, we still have this fundamental problem:
rosuav@sikorsky:~$ python3 Python 3.8.0a0 (heads/literal_eval-exception:ddcb2eb331, Feb 21 2018, 04:32:23) [GCC 6.3.0 20170516] on linux Type "help", "copyright", "credits" or "license" for more information.
from coalesce import NullCoalesce from types import SimpleNamespace spam, spam.eggs, spam.eggs.bacon = SimpleNamespace(),
SimpleNamespace(), 42
NullCoalesce(spam).eggs.bacon
<NullCoalesce proxy for 42>
That isn't 42. That's a thing that, forever afterwards, will be a proxy. And look at this:
spam.nil = None print(NullCoalesce(spam).nil)
<NullCoalesce proxy for None> >>> print(NullCoalesce(spam).nil.nil) None >>> print(NullCoalesce(spam).nil.nil.nil) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'NoneType' object has no attribute 'nil' >>>
Whoooooops.
So, no, this is most definitely NOT equivalent to the proposed semantics.
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Wed, Jul 25, 2018 at 9:47 PM Nicholas Chammas nicholas.chammas@gmail.com wrote:
That is disingenuous, I think. Can this raise an AttributeError?
spam?.eggs?.bacon
Of course it can! And this is exactly the pattern used in many examples
in
the PEP and the discussion. So the PEP would create a situation where
code
will raise AttributeError in a slightly—and subtly—different set of circumstances than plain attribute access will.
food = spam?.eggs?.bacon
Can be rewritten as: food = spam if spam is not None and spam.eggs is not None: food = spam.eggs.bacon They both behave identically, no? Maybe I missed the point David was trying to make.
No, you illustrate it perfectly! I had to stare at your translation for a while to decide if it was really identical to the proposed `spam?.eggs?.bacon`. The fact I have to think so hard makes the syntax feel non-obvious.
Plus, there's the fact that your best effort at translating the proposed syntax is WRONG. Even a strong proponent cannot explain the behavior on a first try. And indeed, it behaves subtly different from plain attribute access in where it raises AttributeError.
spam = SimpleNamespace() spam.eggs = None spam.eggs.bacon
AttributeError: 'NoneType' object has no attribute 'bacon'
# spam?.eggs?.bacon # Should be: None
"Translation" does something different food = spam if spam is not None and spam.eggs is not None:
... food = spam.eggs.bacon
food
namespace(eggs=None)

On Wed, Jul 25, 2018 at 10:12 PM David Mertz mertz@gnosis.cx wrote:
On Wed, Jul 25, 2018 at 9:47 PM Nicholas Chammas < nicholas.chammas@gmail.com> wrote:
That is disingenuous, I think. Can this raise an AttributeError?
spam?.eggs?.bacon
Of course it can! And this is exactly the pattern used in many
examples in
the PEP and the discussion. So the PEP would create a situation where
code
will raise AttributeError in a slightly—and subtly—different set of circumstances than plain attribute access will.
food = spam?.eggs?.bacon
Can be rewritten as: food = spam if spam is not None and spam.eggs is not None: food = spam.eggs.bacon They both behave identically, no? Maybe I missed the point David was trying to make.
No, you illustrate it perfectly! I had to stare at your translation for a while to decide if it was really identical to the proposed `spam?.eggs?.bacon`. The fact I have to think so hard makes the syntax feel non-obvious.
Plus, there's the fact that your best effort at translating the proposed syntax is WRONG. Even a strong proponent cannot explain the behavior on a first try. And indeed, it behaves subtly different from plain attribute access in where it raises AttributeError.
spam = SimpleNamespace() spam.eggs = None spam.eggs.bacon
AttributeError: 'NoneType' object has no attribute 'bacon'
# spam?.eggs?.bacon # Should be: None
"Translation" does something different food = spam if spam is not None and spam.eggs is not None:
... food = spam.eggs.bacon
food
namespace(eggs=None)
Indeed. Thanks for the counter-example. I think the correct translation is as follows:
food = spam?.eggs?.bacon
Becomes:
food = None if spam is not None and spam.eggs is not None: food = spam.eggs.bacon
Did I get it right now? :)
What misled me was this example from the PEP https://www.python.org/dev/peps/pep-0505/#the-maybe-dot-and-maybe-subscript-operators showing how atoms are evaluated. The breakdown begins with `_v = a`, so I copied that pattern incorrectly when trying to explain how an example assignment would work, instead of translating directly from what I understood the PEP 505 variant should do.
So, shame on me. I think this particular mistake reflects more on me than on PEP 505, but I see how this kind of mistake reflects badly on the folks advocating for the PEP (or at least, playing devil's advocate).

On Wed, Jul 25, 2018 at 10:50 PM Nicholas Chammas < nicholas.chammas@gmail.com> wrote:
Indeed. Thanks for the counter-example. I think the correct translation is as follows: food = spam?.eggs?.bacon Becomes: food = None if spam is not None and spam.eggs is not None: food = spam.eggs.bacon
Did I get it right now? :)
Nope, still not right, I'm afraid!
Chris Angelica provided a more accurate translation. Do you not see that the fact that your *second* try at understanding the actual behavior is still wrong suggest that this operator is a HUGE bug magnet?!
So, shame on me. I think this particular mistake reflects more on me than on PEP 505, but I see how this kind of mistake reflects badly on the folks advocating for the PEP (or at least, playing devil's advocate).
I really, really don't. I think you see an intuitive behavior that would be nice and useful in a certain area. That behavior just isn't what the PEP proposes though... it's kinda-sorta close enough to be lured into thinking it's a good idea.
Honestly, I think the behavior of GreedyAccess in my little library I wrote over the last couple nights is FAR more often what programmers ACTUALLY want than NullCoalesce is. Even Steve Dower—in the PEP and in this discussion—acknowledges the appeal and utility of the GreedyAccess behavior. It's in the "Rejected Ideas" section, which is fair enough.
But in a library like mine... or indeed, in a much better library that you or someone else writes... it's perfectly easy to have both classes, and choose which behavior is more useful for your case. A new syntax feature can't let user decide which behavior (or maybe some other behavior altogether) is most useful for their specific case. A library does that easily[*].
[*] In version 0.1.1 of coalescing—changed from 0.1—I added the option to use a sentinel other than None if you want. I'm not sure how useful that is, but that idea was in some old PEPs, and I think in the Rejected Ideas of 505. With a library, I have a parameter that need not be used to switch that[**]. E.g.:
NullCoalesce(foo, sentinel=float('nan')).bar.baz.blam
[**] Yes, I even handle NaN's in a special way because they are non-equal even to themselves. You could use empty string, or 0, or my_null = object(), or whatever.

On Wed, Jul 25, 2018 at 11:09 PM David Mertz mertz@gnosis.cx wrote:
On Wed, Jul 25, 2018 at 10:50 PM Nicholas Chammas < nicholas.chammas@gmail.com> wrote:
Indeed. Thanks for the counter-example. I think the correct translation is as follows: food = spam?.eggs?.bacon Becomes: food = None if spam is not None and spam.eggs is not None: food = spam.eggs.bacon
Did I get it right now? :)
Nope, still not right, I'm afraid!
Chris Angelica provided a more accurate translation.
Forgive me for being slow. I'm missing what's different in semantics between the translation above and Chris's translation below:
_tmp = spam
if _tmp is not None: _tmp = _tmp.eggs if _tmp is not None: _tmp = _tmp.bacon food = _tmp
What's a case where they would do something different?
* If spam is None, they work the same -> None * If spam is not None, but spam.eggs exists and is None, they work the same -> None * If spam is not None, but spam.eggs doesn't exist, they work the same -> AttributeError * If spam is not None, and spam.eggs is not None, but spam.eggs.bacon is None, they work the same -> None * If spam is not None, and spam.eggs is not None, but spam.eggs.bacon doesn't exist, they work the same -> AttributeError * If spam is not None, and spam.eggs is not None, and spam.eggs.bacon is not None, they work the same -> bacon

On Thu, Jul 26, 2018 at 12:00 AM Nicholas Chammas < nicholas.chammas@gmail.com> wrote:
Forgive me for being slow. I'm missing what's different in semantics between the translation above and Chris's translation below:
You are VERY close now. You have more SPAM, so yours is better:
In [1]: class Spam: ...: @property ...: def eggs(self): ...: print("SPAM SPAM SPAM") ...: return "eggs" ...: In [2]: spam = Spam() In [3]: _tmp = spam In [4]: if _tmp is not None: ...: _tmp = _tmp.eggs ...: if _tmp is not None: ...: _tmp = _tmp.bacon ...: food = _tmp ...: del _tmp ...: food ...: SPAM SPAM SPAM --------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-4-9a2f963239f8> in <module>() 2 _tmp = _tmp.eggs 3 if _tmp is not None: ----> 4 _tmp = _tmp.bacon 5 food = _tmp 6 del _tmp
AttributeError: 'str' object has no attribute 'bacon' In [5]: if spam is not None and spam.eggs is not None: ...: food = spam.eggs.bacon ...: SPAM SPAM SPAM SPAM SPAM SPAM --------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-5-a24834cdb92b> in <module>() 1 if spam is not None and spam.eggs is not None: ----> 2 food = spam.eggs.bacon 3
AttributeError: 'str' object has no attribute 'bacon'

On Thu, Jul 26, 2018 at 12:17 AM David Mertz mertz@gnosis.cx wrote:
On Thu, Jul 26, 2018 at 12:00 AM Nicholas Chammas < nicholas.chammas@gmail.com> wrote:
Forgive me for being slow. I'm missing what's different in semantics between the translation above and Chris's translation below:
You are VERY close now. You have more SPAM, so yours is better:
<snipped>
Understood. Thanks for clarifying. I think Chris was referring to this in an earlier message (when I made my first attempt at translating) when he said:
Aside from questions of repeated evaluation/assignment, yes. The broad semantics should be the same.
So the meaning of my translation matches the "intention" of the original PEP 505 expression, but I see the danger in offering up such loose translations. Getting the meaning right but missing important details (like repeated evaluation) does not inspire confidence.

On Wed, Jul 25, 2018 at 11:09:35PM -0400, David Mertz wrote:
Chris Angelica provided a more accurate translation. Do you not see that the fact that your *second* try at understanding the actual behavior is still wrong suggest that this operator is a HUGE bug magnet?!
Oh what a load of FUD.
The whole point of having an operator do this means that we don't have to duplicate the semantics of the operator in plain Python.
Very few people get the semantics of x + y right. If (generic) you think that it is:
return x.__add__(y)
you're wrong. If you think it is:
try: return x.__add__(y) except AttributeError: return y.__radd__(x)
you're still wrong. If you think it is something like this:
try: adder = type(x).__add__ operands = (x, y) except AttributeError: try: adder = type(y).__radd__ operands = (y, x) except AttributeError: raise TypeError from None return adder(*operands)
you're still wrong. But *nobody cares* because outside of some incredibly rare and specialised uses, nobody needs to emulate the semantics of + in pure Python. They just call the + operator, or at worst call the operator.add function.
We have the syntax "yield from" because it is *really complicated* for one generator to delegate to another generator. Emulating the yield from call isn't necessary, because we have yield from.
(If you think that "yield from" is equivalent to "for x in subgenerator: yield x", you're wrong too.)
And likewise, if we have these new operators, that means that those who need them won't have to emulate them in pure Python. They can just use the operators.
The idea that using the operators will be "a bug magnet" because the implementation is (allegedly) complex is ludicrous.

The readability issue isn't just a matter of a new set of symbols to learn. It isn't even that the symbols are used in a non-intuitive way (though that doesn't help). It's not even that the question mark, unlike the dot, is very visually obtrusive (which also doesn't help). It also screws with the grammar in an unexpected way. I would be fine with something like:
def func(data=None): data ?= [] ...
If there were some corresponding dunder method to implement that behavior, but it's not clear to me that such a thing is possible. If it isn't, that would make `?=` (or '??=' though I don't see the need for double '?'s) a special statement, a special case that someone who's familiar with how other in-place operators work, would be thrown for a loop. The other use cases look like they break down in a different manner than they actually do:
person?.name
looks like it would break down similar to
person().name
but it doesn't because '?' by itself is not an operator. similarly:
person?[0]
looks like it would break down similar to
person()[0]
but again, the operator is '?[...]' not '?'
This also raises the question of other operators that could be made null aware. Why not:
func?()
?-num
?~num
num ?+ other
Why not other reflective operators?
num ?+= other
I think this could be helped by simply making '?' an operator with a very high order of operations, so that pretty-much any expression on the LHS is guarded for 'NoneType' AttributeErrors:
val = person[0]? val = person.name?
That has the double benefit of making multiple '?.' or '?[]' operations unnecessary. I mean the following expression would almost never make sense under the current proposal:
instead of:
val = person?.name?[0]?.lower()
you could write:
val = person.name[0].lower()?
That also aligns a little better with common usage of '?' in natural language.
But again; there doesn't seem to be a reasonable way to implement a corresponding dunder method for those operators because they're special. I also disagree with the idea that None is special enough to warrant such special behavior. There are other special None-like objects (as David Mertz pointed out) like NaN and custom place-holders for cases where None is a valid argument.
Yet another possibility is to make '?' signify a parameter who's default value is an expression that should be evaluated when necessary:
def func(data: List = []?): ...
That gets closer to another often requested feature: delayed expression evaluation (as Steve D'Aprano pointed out). So there are other, possibly better uses for the '?' symbol. It's something that needs to be carefully considered.
On Wed, Jul 25, 2018 at 10:09 PM, David Mertz mertz@gnosis.cx wrote:
On Wed, Jul 25, 2018 at 10:50 PM Nicholas Chammas < nicholas.chammas@gmail.com> wrote:
Indeed. Thanks for the counter-example. I think the correct translation is as follows: food = spam?.eggs?.bacon Becomes: food = None if spam is not None and spam.eggs is not None: food = spam.eggs.bacon
Did I get it right now? :)
Nope, still not right, I'm afraid!
Chris Angelica provided a more accurate translation. Do you not see that the fact that your *second* try at understanding the actual behavior is still wrong suggest that this operator is a HUGE bug magnet?!
So, shame on me. I think this particular mistake reflects more on me than on PEP 505, but I see how this kind of mistake reflects badly on the folks advocating for the PEP (or at least, playing devil's advocate).
I really, really don't. I think you see an intuitive behavior that would be nice and useful in a certain area. That behavior just isn't what the PEP proposes though... it's kinda-sorta close enough to be lured into thinking it's a good idea.
Honestly, I think the behavior of GreedyAccess in my little library I wrote over the last couple nights is FAR more often what programmers ACTUALLY want than NullCoalesce is. Even Steve Dower—in the PEP and in this discussion—acknowledges the appeal and utility of the GreedyAccess behavior. It's in the "Rejected Ideas" section, which is fair enough.
But in a library like mine... or indeed, in a much better library that you or someone else writes... it's perfectly easy to have both classes, and choose which behavior is more useful for your case. A new syntax feature can't let user decide which behavior (or maybe some other behavior altogether) is most useful for their specific case. A library does that easily[*].
[*] In version 0.1.1 of coalescing—changed from 0.1—I added the option to use a sentinel other than None if you want. I'm not sure how useful that is, but that idea was in some old PEPs, and I think in the Rejected Ideas of 505. With a library, I have a parameter that need not be used to switch that[**]. E.g.:
NullCoalesce(foo, sentinel=float('nan')).bar.baz.blam
[**] Yes, I even handle NaN's in a special way because they are non-equal even to themselves. You could use empty string, or 0, or my_null = object(), or whatever. -- Keeping medicines from the bloodstreams of the sick; food from the bellies of the hungry; books from the hands of the uneducated; technology from the underdeveloped; and putting advocates of freedom in prisons. Intellectual property is to the 21st century what the slave trade was to the 16th.
Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On 26/07/18 22:28, Abe Dillon wrote:
The readability issue isn't just a matter of a new set of symbols to learn. It isn't even that the symbols are used in a non-intuitive way (though that doesn't help).
We could argue about how intuitive or not these operators are, but they are used in other languages, so they clearly aren't completely unintuitive.
It's not even that the question mark, unlike the dot, is very visually obtrusive (which also doesn't help).
Being visually obtrusive would help a great deal. Unfortunately I don't think it is visually obtrusive in the ?. and ?[] operators.
It also screws with the grammar in an unexpected way. I would be fine with something like:
def func(data=None): data ?= [] ...
If there were some corresponding dunder method to implement that behavior, but it's not clear to me that such a thing is possible.
We could conceivably have a dunder method that None implemented as returning the other argument and object implemented as returning itself. I'm not really convinced that it's worth it, though.
If it isn't, that would make `?=` (or '??=' though I don't see the need for double '?'s)
Because it's the in-place version of "??", obviously.
a special statement, a special case that someone who's familiar with how other in-place operators work, would be thrown for a loop.
Really? Quick straw poll: how often do people care how in-place operators are implemented?
The other use cases look like they break down in a different manner than they actually do:
person?.name
looks like it would break down similar to
person().name
but it doesn't because '?' by itself is not an operator. similarly:
person?[0]
looks like it would break down similar to
person()[0]
but again, the operator is '?[...]' not '?'
Sorry, not buying this one. As you said, "?" is not an operator, so "a?.b" clearly can't break down into "a? .b". This all rather feels like you are trying to find justifications for not liking these operators rather just saying "I don't like them," and frankly I'd respect you more if you just said "I don't like them."
(For the record, I don't like most of them. There are clear use cases for "??" and "??=", but the use cases I've seen for "?." and "?[]" aren't convincing to me.)
This also raises the question of other operators that could be made null aware. Why not:
func?()
?-num
?~num
num ?+ other
Why not other reflective operators?
num ?+= other
Why not indeed. I haven't seen any convincing use cases (and nor has the PEP, though I doubt Steve was looking for them), but they aren't inherently any more weird than "?." and "?[]".
I think this could be helped by simply making '?' an operator with a very high order of operations, so that pretty-much any expression on the LHS is guarded for 'NoneType' AttributeErrors:
val = person[0]? val = person.name?
Um. How is that supposed to parse? "person[0]" has already been evaluated by the time the "?" operator gets anywhere near it. Unless you are proposing some major surgery to the parser (which I'm pretty sure isn't going to happen), this has no chance of getting through. Good thing too, because it's a lot more surprising than the alternatives.
I also disagree with the idea that None is special enough to warrant such special behavior. There are other special None-like objects (as David Mertz pointed out) like NaN and custom place-holders for cases where None is a valid argument.
NaN isn't used like None, though, and custom sentinels aren't that common.
Yet another possibility is to make '?' signify a parameter who's default value is an expression that should be evaluated when necessary:
def func(data: List = []?): ...
That gets closer to another often requested feature: delayed expression evaluation (as Steve D'Aprano pointed out). So there are other, possibly better uses for the '?' symbol. It's something that needs to be carefully considered.
While my immediate reaction is "yuk", your use of "?" does not conflict with any use in the PEP, so this is a red herring.

We could argue about how intuitive or not these operators are, but they are used in other languages, so they clearly aren't completely unintuitive.
Other languages are other languages. Other languages use the "<condition> ? <expr_a> : <expr_b>" form of the the ternary operator. That doesn't mean we should adopt that into Python.
Being visually obtrusive would help a great deal. Unfortunately I don't
think it is visually obtrusive in the ?. and ?[] operators.
My argument about visual obtrusiveness is in the context of me claiming that python attempts to mimic natural language to achieve readability, others countering that `person.name` is not how periods are used in natural languages, so using other symbols in unintuitive ways is perfectly fine. My point is that '.' is unobtrusive, so it's not *that* different from `person name' which isn't *that* different from `person's name`. The '?' symbol is most definitely more visually obtrusive than a simple '.' because it takes up more space.
We could conceivably have a dunder method that None implemented as
returning the other argument and object implemented as returning itself. I'm not really convinced that it's worth it, though.
Yes, I thought of that and came to the same conclusion. It's my understanding that None may not be an actual object, but a special memory location. I'm not sure though and didn't look it up.
Really? Quick straw poll: how often do people care how in-place operators
are implemented?
The point I was trying to make is that it doesn't behave or compose like other expressions.
As you said, "?" is not an operator, so "a?.b" clearly can't break down
into "a? .b".
The problem is that '.' IS a stand-alone operator, so it's natural to visually parse `<expr>.b` as `<expr> .b`, but adding '?.' causes double takes, more mental load, general interruption of the flow of reading. It also sets up the later discussion of other possible uses of the '?' symbol that may or may not have more merit.
Um. How is that supposed to parse? "person[0]" has already been evaluated
by the time the "?" operator gets anywhere near it.
It parses by placing the '?' operator high in the order of operations; like I said. Do I really need to explain how order of operations works?
Why not indeed. I haven't seen any convincing use cases (and nor has the
PEP, though I doubt Steve was looking for them), but they aren't inherently any more weird than "?." and "?[]".
It seems like a natural extension. It also seems like a lot to add for something as trivial as None handling.
While my immediate reaction is "yuk"
Is that more legit than my reaction to '?.' and '?[]'? At least in this case, the '?' comes at the end of a clause...
your use of "?" does not conflict with any use in the PEP, so this is a red
herring.
I suppose it doesn't *technically* conflict, but it would lead to some beautiful code, like:
lambda data=a?.b?[0]?: data?.d
On Fri, Jul 27, 2018 at 7:00 AM, Rhodri James rhodri@kynesim.co.uk wrote:
On 26/07/18 22:28, Abe Dillon wrote:
The readability issue isn't just a matter of a new set of symbols to learn. It isn't even that the symbols are used in a non-intuitive way (though that doesn't help).
We could argue about how intuitive or not these operators are, but they are used in other languages, so they clearly aren't completely unintuitive.
It's not even that the question mark, unlike the dot, is
very visually obtrusive (which also doesn't help).
Being visually obtrusive would help a great deal. Unfortunately I don't think it is visually obtrusive in the ?. and ?[] operators.
It also screws with the
grammar in an unexpected way. I would be fine with something like:
def func(data=None): data ?= [] ...
If there were some corresponding dunder method to implement that behavior, but it's not clear to me that such a thing is possible.
We could conceivably have a dunder method that None implemented as returning the other argument and object implemented as returning itself. I'm not really convinced that it's worth it, though.
If it isn't, that
would make `?=` (or '??=' though I don't see the need for double '?'s)
Because it's the in-place version of "??", obviously.
a
special statement, a special case that someone who's familiar with how other in-place operators work, would be thrown for a loop.
Really? Quick straw poll: how often do people care how in-place operators are implemented?
The other use
cases look like they break down in a different manner than they actually do:
person?.name
looks like it would break down similar to
person().name
but it doesn't because '?' by itself is not an operator. similarly:
person?[0]
looks like it would break down similar to
person()[0]
but again, the operator is '?[...]' not '?'
Sorry, not buying this one. As you said, "?" is not an operator, so "a?.b" clearly can't break down into "a? .b". This all rather feels like you are trying to find justifications for not liking these operators rather just saying "I don't like them," and frankly I'd respect you more if you just said "I don't like them."
(For the record, I don't like most of them. There are clear use cases for "??" and "??=", but the use cases I've seen for "?." and "?[]" aren't convincing to me.)
This also raises the question of other operators that could be made null
aware. Why not:
func?()
?-num
?~num
num ?+ other
Why not other reflective operators?
num ?+= other
Why not indeed. I haven't seen any convincing use cases (and nor has the PEP, though I doubt Steve was looking for them), but they aren't inherently any more weird than "?." and "?[]".
I think this could be helped by simply making '?' an operator with a very
high order of operations, so that pretty-much any expression on the LHS is guarded for 'NoneType' AttributeErrors:
val = person[0]? val = person.name?
Um. How is that supposed to parse? "person[0]" has already been evaluated by the time the "?" operator gets anywhere near it. Unless you are proposing some major surgery to the parser (which I'm pretty sure isn't going to happen), this has no chance of getting through. Good thing too, because it's a lot more surprising than the alternatives.
I also disagree with the idea that None is special enough to warrant such
special behavior. There are other special None-like objects (as David Mertz pointed out) like NaN and custom place-holders for cases where None is a valid argument.
NaN isn't used like None, though, and custom sentinels aren't that common.
Yet another possibility is to make '?' signify a parameter who's default
value is an expression that should be evaluated when necessary:
def func(data: List = []?): ...
That gets closer to another often requested feature: delayed expression evaluation (as Steve D'Aprano pointed out). So there are other, possibly better uses for the '?' symbol. It's something that needs to be carefully considered.
While my immediate reaction is "yuk", your use of "?" does not conflict with any use in the PEP, so this is a red herring.
-- Rhodri James *-* Kynesim Ltd _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Sun, Jul 29, 2018 at 6:07 AM, Abe Dillon abedillon@gmail.com wrote:
Yes, I thought of that and came to the same conclusion. It's my understanding that None may not be an actual object, but a special memory location. I'm not sure though and didn't look it up.
Python does not have memory locations. None *is* an actual object. It has attributes, it has standard behaviours, it fits in the same object model as everything else in Python does.
As you said, "?" is not an operator, so "a?.b" clearly can't break down into "a? .b".
The problem is that '.' IS a stand-alone operator, so it's natural to visually parse `<expr>.b` as `<expr> .b`, but adding '?.' causes double takes, more mental load, general interruption of the flow of reading. It also sets up the later discussion of other possible uses of the '?' symbol that may or may not have more merit.
This is utter nonsense on par with trying to claim that "x <= y" should be parsed as if it's a modified form of assignment since "x = y" would be assignment.
Do I really need to explain how two-character operators work?
ChrisA

Python does not have memory locations.
CPython does, form the documentation on the `id` function:
*CPython implementation detail:* This is the address of the object in
memory.
I'm not sure what performance implications there would be for adding a __none_check__ or whatever method to None.
None *is* an actual object. It
has attributes, it has standard behaviours, it fits in the same object model as everything else in Python does.
Cool. Thanks for the info. I wasn't sure because it's considered un-kosher (according to PyCharm) to use "if x == None:" instead of "if x is None".
This is utter nonsense on par with trying to claim that "x <= y"
should be parsed as if it's a modified form of assignment since "x = y" would be assignment.
This is getting tedious. It's only comparable to '<=' if you disregard every other point I've made. Python is supposed to emphasize readability and learnability. To that end, expressions should strive to resemble natural language. Otherwise they should try to resemble common mathematical notation. Failing that, they should try to resemble common programming idioms. There's no easy way to do subscripts, so we use square brackets for indexing. That's a reasonable and pragmatic compromise. `<=` is a fine compromise too because there's no easy way to type the ≤ symbol on most keyboards and it's a perfectly familiar symbol from grade-school math. Sure, someone could confuse '<=' for an assignment operator, I suppose; but there's enough going for '<=' that it's easy to justify its existence for pragmatism's sake. The same can not be said for '?.'. It's not even remotely close to the same thing.
"?." doesn't resemble anything in natural language. It doesn't resemble anything in common mathematics. It isn't a well established pattern in programming that has decades of precedent (like square bracket indexing). It's not hard to check for None with a ternary statement. Adding a '?' operator that goes at the end of an expression could do the same thing more elegantly and resemble natural language at the same time. For all of those reasons: I'm against this proposal.
Do I really need to explain how two-character operators work?
Point taken, I'll watch my tone. Rhodri James, I'm sorry for that flippant remark about order of operations. It was uncalled for.
On Sat, Jul 28, 2018 at 3:14 PM, Chris Angelico rosuav@gmail.com wrote:
On Sun, Jul 29, 2018 at 6:07 AM, Abe Dillon abedillon@gmail.com wrote:
Yes, I thought of that and came to the same conclusion. It's my understanding that None may not be an actual object, but a special memory location. I'm not sure though and didn't look it up.
Python does not have memory locations. None *is* an actual object. It has attributes, it has standard behaviours, it fits in the same object model as everything else in Python does.
As you said, "?" is not an operator, so "a?.b" clearly can't break down into "a? .b".
The problem is that '.' IS a stand-alone operator, so it's natural to visually parse `<expr>.b` as `<expr> .b`, but adding '?.' causes double takes, more mental load, general interruption of the flow of reading. It also sets up the later discussion of other possible uses of the '?'
symbol
that may or may not have more merit.
This is utter nonsense on par with trying to claim that "x <= y" should be parsed as if it's a modified form of assignment since "x = y" would be assignment.
Do I really need to explain how two-character operators work?
ChrisA _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/

On Sun, Jul 29, 2018 at 1:56 PM, Abe Dillon abedillon@gmail.com wrote:
Python does not have memory locations.
CPython does, form the documentation on the `id` function:
CPython implementation detail: This is the address of the object in memory.
Right, which is an important distinction; CPython, being a concrete implementation, does involve memory. For instance, you can ask "how many bytes of memory does this object take up?" and CPython can answer that (sys.getsizeof). You can't ask that in Python generally, because the abstract language doesn't have memory or any such concept.
The "None" object exists in the abstract sense. It will be present in ANY Python implementation. In theory, a Python could use a null pointer to represent None, just as long as you can't tell the difference between that behaviour and any other.
I'm not sure what performance implications there would be for adding a __none_check__ or whatever method to None.
Considering that None is a singleton, there's no need to create a protocol for asking "are you None?". The obvious way to say "are you None?" is to inquire if some object and None are the same object, which is spelled "x is None".
ChrisA

On Sat, Jul 28, 2018 at 10:56:13PM -0500, Abe Dillon wrote:
Python does not have memory locations.
CPython does, form the documentation on the `id` function:
No, that is the same id() function as the id() provided by Jython, and IronPython, and Stackless. Like all Python implementations, it returns an opaque integer ID number.
That's why it is called "id" rather than "memory_location" or "address".
*CPython implementation detail:* This is the address of the object in
memory.
An irrelevant implementation detail. You can't use the id() as a memory address, and even if you could, that wouldn't be portable Python code, it would be an implementation-dependent hack that is unsafe or unreliable to use.
Being an implementation detail, CPython is free to change it at any time, without warning or notice, even in a bug-fix release. If CPython ever gets a memory manager that can move objects around, as they can move in Jython and IronPython, CPython will also have to change id().
id() may, sometimes, *use* the memory address, but the semantics are not that of a memory address.
I'm not sure what performance implications there would be for adding a __none_check__ or whatever method to None.
The point of testing for None is because you want None, and absolutely no other object but None. A __none_check__ method would allow other objects to lie about being None-like. The point of using "is" is to avoid that.
If you want any arbitrary object which happens to be a bit None-like, then call "x == None" and let x decide for itself. But be prepared to have experienced Python programmers correct your "mistake" unless you carefully document why you are doing this.
Python is supposed to emphasize readability and learnability.
You're thinking of Scratch and other languages aimed at school kids.
Python emphasizes many things. Its not merely a kiddies' language, or a beginners' language, or an educational language. It is a language used by professionals, and frankly I'm getting sick to the back teeth of people saying "Mustn't do that useful thing because it will make Python harder for beginners to learn".
Fine. So it takes them an extra day to learn one more operator. Big deal. It is commonly believed to take ten years to master a field or language. Amortize that one day over ten years and its virtually nothing.
To that end, expressions should strive to resemble natural language.
If you want a programming language that looks like natural language, you want something like Hypertalk:
put the second word of field "Address" after line 3 of field "Record"
or Inform7. Or the granddaddy of "natural language" programming, COBOL.
Python is very little like natural language. Your earlier idea that attribute access spam.eggs is like natural language (in English) because its only a few characters different from "spam's eggs" really doesn't hold up. Apart from the word order, there's no similarity.
Otherwise they should try to resemble common mathematical notation. Failing that, they should try to resemble common programming idioms.
Yes, common programming idioms like null-coalescing and null-aware operators, which are used by a good half dozen popular, modern languages.
"?." doesn't resemble anything in natural language.
That's okay. Neither does spam.eggs, or spam[eggs], or anything to do with async, or function call notation func(a, b, c, keyword=d).
It's not hard to check for None with a ternary statement.
You're thinking in terms of trivial cases like
default if x is None else x
but replace x with a long, potentially expensive expression and you may think differently:
default if spam(a, b, c, kw=d).eggs[cheese]() is None else spam(a, b c, kw=d).eggs[cheese]()
or a chain of tests:
if spam(a, b, c, kw=d) is not None: if spam(a, b, c, kw=d).eggs is not None: if spam(a, b, c, kw=d).eggs[cheese] is not None: result = spam(a, b, kw=d).eggs[cheese]()
Adding a '?' operator that goes at the end of an expression could do the same thing more elegantly
I don't know that Python has any binary operators which are split into an infix part and a postfix part. I don't know any language with an operator like that. There is the ternary operator, of course, but this would be a binary operator.
So you're against:
spam?.eggs?.cheese?.aardvark
but you prefer:
spam.eggs?.cheese?.aardvark?
instead.

On Sun, Jul 29, 2018 at 3:54 PM, Steven D'Aprano steve@pearwood.info wrote:
Being an implementation detail, CPython is free to change it at any time, without warning or notice, even in a bug-fix release. If CPython ever gets a memory manager that can move objects around, as they can move in Jython and IronPython, CPython will also have to change id().
To clarify: If that were to happen, CPython would change *the description of the id() function*, not the IDs returned. The IDs returned must be constant for the lifetime of the object, as that's what Python-the-language demands and guarantees.
ChrisA

First of all: Thanks to everyone for explaining everything about the id function. You are all so smart...
[Greg Ewing]
Dots have been used for attribute access in so many languages for so long that it has become the normal and expected syntax to use. ?. is much more recent. Maybe in another 30 years, if it has stood the test of time, it could be argued for on the basis of familiarity, but not now.
My point exactly.
[Steve D'Aprano]
You're thinking of Scratch and other languages aimed at school kids.
Nope. I'm thinking of Python.
[Steve D'Aprano]
frankly I'm getting sick to the back teeth of people saying "Mustn't do that useful thing because it will make Python harder for beginners to learn".
Fine. So it takes them an extra day to learn one more operator. Big deal.
It's not just about learning it. It's about the mental load of reading code. little things add up.
If "sick through your back teeth" of people being cautious, that sounds like a personal problem. I'm worried about the pace people are adding functionality to Python. You start learning about concurrent.futures, then asyncio gets added with "yield from", then async def and await and all that gets added and it all seems to be a mess. Now there are how many ways to format strings?
Should we just shovel features from other popular languages into Python because they might be a little useful? I wouldn't wan't your back teeth to rot from my prudishness...
Python is very little like natural language. Your earlier idea that
attribute access spam.eggs is like natural language (in English) because its only a few characters different from "spam's eggs" really doesn't hold up. Apart from the word order, there's no similarity.
I feel like you're being willfully dense about this. There is a difference between:
if any(thing.is_metalic for thing in pockets): raise Alarm("metal
detected!")
and
if any(thing$!<>~.s_metal for thing in pockets): raise Alarm("metal
detected!")
The syntax of this proposal is almost objectively disgusting. It's truly horrid. I don't know how many ways to say it.
[Steve D'Aprano]
I don't know that Python has any binary operators which are split into an infix part and a postfix part.
That's not at all what I proposed.
[Steve D'Aprano]
So you're against: spam?.eggs?.cheese?.aardvark but you prefer: spam.eggs?.cheese?.aardvark? instead.
NO! I'm proposing:
spam.eggs.cheese.aardvark?
A single POSTFIX operator that has a high priority in the order of operations.
On Sun, Jul 29, 2018 at 1:03 AM, Chris Angelico rosuav@gmail.c