
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.

On 2018-07-18 18:43, Steve Dower wrote:
[snip]
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".
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:
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:
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.)
Thanks.
Correct, another typo. Cheers, Steve

On Wed, Jul 18, 2018 at 7:46 PM Steve Dower <steve.dower@python.org> wrote:
or these they the proposed that the preferred
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 -- Giampaolo - http://grodola.blogspot.com -- Giampaolo - http://grodola.blogspot.com

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 ]
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:
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. -- Giampaolo - http://grodola.blogspot.com

On Thu, Jul 19, 2018 at 4:06 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> 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. ChrisA

On Thu, 19 Jul 2018 19:11:33 +1000 Chris Angelico <rosuav@gmail.com> wrote:
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:
^ 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. -- Giampaolo - http://grodola.blogspot.com

On Thu, Jul 19, 2018, 5:12 AM Chris Angelico <rosuav@gmail.com> wrote:
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:
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 |. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

On 2018-07-19 11:43, Elazar wrote:
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. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

Let me just address this point: 2018-07-19 20:36 GMT+02:00 Brendan Barnwell <brenbarn@brenbarn.net>:
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:
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:
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:
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. :-/
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:
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

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:
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 Thu, Jul 19, 2018 at 2:36 PM Brendan Barnwell <brenbarn@brenbarn.net> wrote:
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

Rhodri James wrote:
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. -- Greg

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 Fri, Jul 20, 2018 at 10:24 PM, Paul Moore <p.f.moore@gmail.com> wrote:
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:

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:

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:

On Wed, Jul 18, 2018 at 08:05:56PM -0400, David Mertz wrote:
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

"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 2018-07-19 06:38, Steven D'Aprano wrote:
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. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

On Thu, Jul 19, 2018 at 11:39:50AM -0700, Brendan Barnwell wrote:
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.) -- Steve

On Thu, Jul 19, 2018, 9:39 AM Steven D'Aprano <steve@pearwood.info> wrote:
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:
"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.
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:
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.
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: == != ~ - + * ** / // @ % ^ & | << >> < <= > >= [] () [...]
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.
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
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. -- Steve

On 22 July 2018 at 02:54, Steven D'Aprano <steve@pearwood.info> wrote:
Certainly *my* concerns about readability are around the other proposed operators (?[ and ?. in particular).
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:
[...] 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. -- Giampaolo - http://grodola.blogspot.com

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:
Yes, I think you're right. -- Giampaolo - http://grodola.blogspot.com

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:
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.
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. -- Steve

On Sun, Jul 22, 2018 at 10:10 PM, Steven D'Aprano <steve@pearwood.info> wrote:
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:
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:
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:
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... -- Steve

On 23Jul2018 0151, Steven D'Aprano wrote:
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:
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. -- Steve

On 2018-07-22 09:01:58 -0400, David Mertz wrote:
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 -- _ | Peter J. Holzer | we build much bigger, better disasters now |_|_) | | because we have much more sophisticated | | | hjp@hjp.at | management tools. __/ | http://www.hjp.at/ | -- Ross Anderson <https://www.edge.org/>

On Sun, Jul 22, 2018 at 2:10 PM Steven D'Aprano <steve@pearwood.info> wrote:
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. -- Giampaolo - http://grodola.blogspot.com

On Sun, Jul 22, 2018 at 11:35 PM, Giampaolo Rodola' <g.rodola@gmail.com> wrote:
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:
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. -- Giampaolo - http://grodola.blogspot.com

On Mon, Jul 23, 2018 at 1:09 AM, Giampaolo Rodola' <g.rodola@gmail.com> wrote:
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:
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. -- Giampaolo - http://grodola.blogspot.com

On Mon, Jul 23, 2018 at 6:43 AM, Giampaolo Rodola' <g.rodola@gmail.com> wrote:
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 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:
"Crazy" is just a personal judgement. I do think high (and even very high) demands are entirely justified by the language's maturity.
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:
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.
Ditto. -- Giampaolo - http://grodola.blogspot.com

On Sun, Jul 22, 2018 at 11:51 PM Giampaolo Rodola' <g.rodola@gmail.com> wrote:
s/I'm going/I'm not going -- Giampaolo - http://grodola.blogspot.com

On Mon, Jul 23, 2018 at 12:08 AM Chris Angelico <rosuav@gmail.com> 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. [1] https://mail.python.org/pipermail/python-ideas/2018-July/052068.html -- Giampaolo - http://grodola.blogspot.com

On Mon, Jul 23, 2018 at 12:59:20AM +0200, Giampaolo Rodola' wrote:
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. -- Steve

On Mon, Jul 23, 2018 at 2:38 AM Steven D'Aprano <steve@pearwood.info> wrote:
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
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:
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:
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:
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 :
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:
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:
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 23 July 2018 at 12:39, Grégory Lielens <gregory.lielens@gmail.com> 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. 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:
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: 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. -- C Anthony

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.

On Mon, Jul 23, 2018 at 12:09:00PM +0100, Steve Dower wrote:
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. -- Steve

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

On Mon, Jul 23, 2018 at 5:52 AM Steve Dower <steve.dower@python.org> wrote:
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
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. -- 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 23Jul2018 1530, David Mertz wrote:
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:
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 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.
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:
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.
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. -- Giampaolo - http://grodola.blogspot.com

On Mon, Jul 23, 2018 at 02:04:17PM +0200, Giampaolo Rodola' wrote: [I wrote this]
[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 isn't a first. Many existing operators use two adjacent symbols not interrupted by a space: e.g. == <= >= != ** // << >> += -= *= etc.
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.
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. -- Steve

On Mon, Jul 23, 2018 at 6:53 PM Steven D'Aprano <steve@pearwood.info> wrote:
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.
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))
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.
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'). -- Giampaolo - http://grodola.blogspot.com

On 2018-07-23 23:05, Giampaolo Rodola' wrote:
It's no more implicit than 'or' checking for falseness.
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:
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'. -- Giampaolo - http://grodola.blogspot.com

On Tue, Jul 24, 2018 at 12:05:14AM +0200, Giampaolo Rodola' wrote:
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.
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 [...]
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.
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.
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? -- Steve

On Tue, Jul 24, 2018 at 11:50 AM Steven D'Aprano <steve@pearwood.info> wrote:
You totally missed my point about explicitness. Nevermind.
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.
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.
Yes, I forgot 'a . b' was legal - my bad.
Again, you missed my point.
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:
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:
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

On Tuesday, July 24, 2018 at 1:38:42 PM UTC+2, Rhodri James wrote:
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.

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

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. -- Rhodri James *-* Kynesim Ltd

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?
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!!!"
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'.) -- Steve

On Tue, Jul 24, 2018, 7:38 AM Rhodri James <rhodri@kynesim.co.uk> wrote:
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:
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

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 Tue, Jul 24, 2018 at 11:02 PM, David Mertz <mertz@gnosis.cx> wrote:
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:
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:
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*
Why does Python have named variables if it is "abuse" to use them? -- Steve

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/).
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.
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. -- 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 11:02 AM, David Mertz <mertz@gnosis.cx> wrote:
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:
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:
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:
-- 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:14 PM, David Mertz <mertz@gnosis.cx> wrote:
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:
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
This is childishly simple:
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"
# The simple approximation: for iter in iters: for _ in iter: yield iter
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) -- 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:45 PM, David Mertz <mertz@gnosis.cx> wrote:
Except for the aforementioned "single lookup, single assignment" semantics. You can't achieve that with an if/else block.
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?
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:
-- 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:
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.
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.
That isn't 42. That's a thing that, forever afterwards, will be a proxy. And look at this:
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:
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.
Yeah. That's a thing it does. It's less of an issue than you think since, e.g.:
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.
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. -- 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 Wed, Jul 25, 2018 at 11:08 PM Chris Angelico <rosuav@gmail.com> wrote:
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. -- 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 1:19 PM, David Mertz <mertz@gnosis.cx> wrote:
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:
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:
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:
-- 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 2018-07-25 23:53, David Mertz wrote:
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 Wed, Jul 25, 2018 at 9:47 PM Nicholas Chammas <nicholas.chammas@gmail.com> wrote:
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?.eggs?.bacon # Should be: None
-- 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 Wed, Jul 25, 2018 at 10:12 PM David Mertz <mertz@gnosis.cx> 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? :) What misled me was this example from the PEP <https://www.python.org/dev/peps/pep-0505/#the-maybe-dot-and-maybe-subscript-...> 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:
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?!
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.

On Wed, Jul 25, 2018 at 11:09 PM David Mertz <mertz@gnosis.cx> wrote:
Forgive me for being slow. I'm missing what's different in semantics between the translation above and Chris's translation below: _tmp = spam
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' -- 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:17 AM David Mertz <mertz@gnosis.cx> wrote:
<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:
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. -- Steve

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 26/07/18 22:28, Abe Dillon wrote:
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.
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.
Really? Quick straw poll: how often do people care how in-place operators are implemented?
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.)
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 "?[]".
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.
NaN isn't used like None, though, and custom sentinels aren't that common.
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

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 Sun, Jul 29, 2018 at 6:07 AM, Abe Dillon <abedillon@gmail.com> wrote:
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.
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 1:56 PM, Abe Dillon <abedillon@gmail.com> wrote:
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]()
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. -- Steve

On Sun, Jul 29, 2018 at 3:54 PM, Steven D'Aprano <steve@pearwood.info> wrote:
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]
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]
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
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]
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.com> wrote:

On Sun, Jul 29, 2018 at 5:12 PM, Abe Dillon <abedillon@gmail.com> wrote:
The syntax of this proposal is almost objectively disgusting. It's truly horrid. I don't know how many ways to say it.
Almost. Except for the problem that "disgusting" is a subjective term. If you want to say that this is *objectively disgusting*, you're going to need some actual justification. If you just mean "I find this disgusting", then that's fine, but we already heard that argument. It's a statement of opinion, not of fact. ChrisA

Le 29/07/2018 à 09:12, Abe Dillon a écrit :
[...]
I don't believe we need spam?.eggs.cheese?.aardvark, because I don't think it is justified by the small benefits it gets us. For the same reason, I don't believe we need spam.eggs.cheese.aardvark? (there is exactly the same number of use cases). We win a bit in readability as it's closer to most spoken languages, but we lose in granularity as we're hiding any error that would raise if spam.eggs returned None, so it's not a full win on this side either...

On Sun, Jul 29, 2018 at 02:12:22AM -0500, Abe Dillon wrote: [I said this]
Am I? Your exact words were: My point is that '.' is unobtrusive, so it's not *that* different from `person name' which isn't *that* different from `person's name`. so you absolutely are claiming that dot access "isn't *that* different" from the possessive in English. The dot might be small, but the last thing we want if for the dot to be unobtrusive. We want it to stand out and be obvious, so we can never mistake 1.5 for 15 or (say) "appbuild" for "app.build". (The first is a variable called "appbuild", the second is a build method or attribute invoked on a variable called "app". Very different things indeed. We really don't want to mistake one for the other.) This is one important reason that most programmers use monospaced fonts for code, so that the dot takes up as much space and any other character and stands out more. The ?. syntax is obviously different from regular dot, but surely that is an *advantage* not a disadvantage? Things which do something different should look different. One of the problems with David Mertz's counter-proposal for a magic None-aware proxy is that we can't tell the difference between spam.eggs and spam.eggs where one of them is magic and the other is not. So we have a nice, clear, obvious difference in syntax so that at a glance the reader can instantly distinguish between dot and maybe-dot, and you are complaining that it is too clear, obvious and easily distinguishable compared to a regular dot.
That's exactly what you proposed, you just didn't realise it. See below. Your syntax is foo.spam? where the dot and the question mark together make a new binary operator. They aren't really delimiters like the parentheses in func( ... ) or the square brackets in seq[ ... ] but two halves of a single operator (or operator like construct) with an infix part (the dot) and a postfix part (the question mark). They certainly can't be two separate operators, a dot and a question mark. I don't know how the Python parser itself will deal with this, but consider the syntax from the perspective of a reader. You suggest:
I'm proposing:
spam.eggs.cheese.aardvark?
Given that, we read the code as: spam normal dot, so get the attribute eggs normal dot, so get the attribute cheese normal dot, so get the attribute aardvark question mark, rewind to the beginning and start again: spam maybe-dot, so maybe get eggs, or None maybe-dot, so maybe get cheese, or None maybe-dot, so maybe get aardvark I'm not saying that's what the parser will have to do (although I suspect it will need some sort of look-ahead, or backtracking, to parse this) but that's certainly how I'll read this.
A single POSTFIX operator that has a high priority in the order of operations.
I don't think that will do what you think it will do. If ? is a normal unary postfix operator then its operand here: spam.eggs.cheese.aardvark? will be (spam.eggs.cheese.aardvark), which defeats the purpose of having a maybe-dot operator. By the time the ? operator is called, the regular dot operators will have already been called. It may be possible to get the syntax you prefer, but readability-wise, I think it is magical and confusing to have a postfix symbol at the end of an expression *retroactively* change the meaning of symbols occuring before it in the expression. I expect it will also be a bug-magnet when we need to mix regular dot and maybe-dot in the same expression: spam.eggs?.cheese # eggs might be None, but spam won't be would need to be written as (spam.eggs).cheese? but who is going to remember those parentheses? Instead I expect people will consistently write: spam.eggs.cheese? thinking the ? applies only to *one* dot, not all of them, and wrongly suppress exceptions when spam is None (which ought to be treated as a bug). -- Steve

On Sun, Jul 29, 2018, 7:46 AM Steven D'Aprano <steve@pearwood.info> wrote:
That's exactly the strongest advantage in what I suggest! The special domain where you want attribute access (or dictionary lookup) to be None coalescing is something marked and different from most of what you want in general attribute access. But once you're in that special world of "semi-structured hierarchical data with leaves marked with None" you don't want to be reminded in every operator. It's just what you do with that kind of object. Naming variable might be a good way to remind readers of the domain. Or comments in the source code. Or putting the special behavior in an isolated function. But effectively, this is a domain you want to enter for a while, then return to the "real world" after some operations are done. For a pretty good analogy, you cannot tell the difference between 'a+b' and 'a+b' easily either, in isolation. But one of those is arithmetic over the domain of integers, while the other is arithmetic over the domain of a Z(65537) modular ring. Many operations are the same in both domains, but I'd really hate to need a special operator to perform modular arithmetic rather than integer arithmetic. E.g. 'a + b' vs 'a ⊕ b'. It is MUCH more elegant and clear in both examples to put the work on the *object* that has special behavior, not on the operator. I can see that if you did a very special kind of programming where you frequently mixed modular and integer arithmetic, you might want the reminder. But that's a rare need, and the language itself should not grow that syntax (perhaps something specialized like Sage or Octave might want a different balance, very reasonably). "Semi-structured hierarchical data with leafs marked with None" is just altogether too special to impose the burden all Python programmers. A None-aware proxy isn't the best possible thing for that special use by any means. But it's pretty good without imposing any burden on the large majority who will never need or use it. Another similar case is the '@' operator. This is exactly a special domain where an operator indicates special behavior. In principle, you could make objects that did anything whatsoever when they see that, but the name .__matmul__ is pretty suggestive of recommended use. However, nothing in the builtins or standard library define any behavior at all. You can happily program with Python your whole life and never think about this operator. The only folks who will see it are folks who use NumPy and *also* do linear algebra with it. By definition, the objects if this domain are already special, as it should be. Well, footnote. I think I've seen some other libraries that create a DSL around email that use '@' also. That also feels fine. It's a very different, but equally special, domain where the objects themselves define the special behavior. If PEP 505 were proposing a regular new protocol for .__none_attr__() or some better dunder name, I would oppose it far less. Specifically, if the behavior of this dunder operator were left to library authors who created such special objects, the danger and bug-magneticism would be much less. I'm well aware that dunders can't get you shortcutting, but modulo that, such a proposal would be relatively innocuous (I'd still be -0, but not really care).

[Steve D'Aprano]
so you absolutely are claiming that dot access "isn't *that* different" from the possessive in English.
Only in the context that I gave: `person.name`. In other contexts (e.g. thing.is_metal) the possessive analogy is obviously inaccurate, but I didn't make the broad claim that '.' means possessive. [Steve D'Aprano]
Unobtrusive doesn't mean invisible. *ob·tru·sive adjective: *noticeable or prominent *in an unwelcome or
intrusive way*.
I don't think '.' is invisible, nor that it should be. It's fairly elegant because it communicates information without breaking the flow of reading. It's as noticeable as it needs to be without getting in the way. [Steve D'Aprano]
They can be two separate operators. The '?' operator would say, "evaluate the preceding expression as though it was in a try-except block and if a NoneType error is thrown, ignore it and evaluate to None." I imagine it would work similarly to how ternary operators work: <expr_1> if <condition_expr> else <expr_2> where neither expr_1 nor expr_2 are evaluated before <condition_expr>. [Steve D'Aprano]
look-ahead and back-tracing is how people read punctuation like '?' and '!' in natural language, and there are plenty of examples of Python following this pattern in other expressionized syntax like in comprehensions and ternary expressions. I actually think this makes sense for expressionized syntax because it puts the "meat" before the "potatoes". When you're reading code like: initials = [person.name[0] ... You can usually guess what goes in the `...` from context so it's redundant, so it makes sense that it comes later. I made the argument a while back that this same pattern should have been followed for lambda expressions for the same reason: the argument list is almost always redundant: hand = sorted(cards, key=(<expression_based_on_individual_card> with <parameters>)) Similarly, If I want to guard against None errors, that's something that can come after the fact because obviously I'm not trying to access attributes or index into None objects: initial = person.name[0]? [Steve D'Aprano]
1) That's the way it works in natural language 2) That's the way it should work in the given context because it's largely a redundant check (from the reader's perspective) because you obviously don't want to access or index into a None object. That can be inferred from context by most humans, but needs to be explicit for the interpreter's sake. 3) there's plenty of precedent for behavior in which the order of execution isn't left to right. [Steve D'Aprano]
It may very well be a bug magnet. I'm not sure how often that usage will come up. I don't find anything all that objectionable about the second form. At least it looks like Python. As you've pointed out many times before: people would have to learn it to use it correctly. There's no way around that. I don't know why you'd all of a sudden abandon that line of reasoning. I believe the want for this functionality is symptomatic of a want for more general functionality like deferred expression evaluation or an expressionized try-except. I would rather try to crack those nuts than implement a overly specific solution that would become cruft later on when deffered evaluation or expressionized try-except are implemented.

On Sun, Jul 29, 2018 at 12:08:46PM -0500, Abe Dillon wrote:
look-ahead and back-tracing is how people read punctuation like '?' and '!' in natural language
I really wish you would stop talking about "natural language" as if there were only one (and it were English). Many natural languages don't even have punctuation like '?', or if they do, they are a very recent innovation. Spanish uses a bracketing pair of marks: ¿Qué Mr Fawlty? while many other European languages make do with just one at the end. And not all of them use the same symbol. (Greek uses the semi-colon, which must make reading C-like languages a remarkable experience.) But even in English, that isn't how people read questions in general, which is why we can still comprehend punctuation-less sentences what's for dinner where are you who was the German professor in Lord Palmerston's quote about the Schleswig-Holstein business as questions, despite the lack of a question mark. Questions really only need the question mark if they would otherwise be ambiguously either a statement or a question. You may be able to find examples of English questions which do require back-tracking, but that is generally considered to be a bad thing, increasing surprise (not a good thing unless you are attempting humour) and reducing the speed and ease of comprehension. See my earlier link about garden path sentences. In other words, reducing not increasing readability.
But not binary operators which take only two arguments, in this case a name and an attribute name.
Can you? In that example I can't even guess if that's the start of a list comprehension or a list display. If its a comprehension, I sure as hell can't guess the important question of what it being iterated over.
so it's redundant, so it makes sense that it comes later.
If it's redundant, why don't we just leave it out? -- Steve

[Steven D'Aprano]
really wish you would stop talking about "natural language" as if there were only one (and it were English).
I'm fine with that. I've just had someone jump down my throat before about being overly English-centric when talking about readability. [Steven D'Aprano]
even in English, that isn't how people read questions in general, which is why we can still comprehend punctuation-less sentences
Yes because you can make use of other context clues. So if I wrote: def f(person=None): initial = person.name[0]? ... You can anticipate the '?' at the end of the expression. Otherwise I would be clearly missing an important edge case. None checking is often done in very close proximity to some code that might yield a None, so this should rarely be a surprise unless the coder is writing the equivalent of a garden-path sentence. [Steven D'Aprano]
You may be able to find examples of English questions which do require back-tracking
It's easier, I'll admit, to find example's of exclamations that require back-tracking, but it's not unheard of! [Steven D'Aprano]
Again, my proposal was never for a binary operator. [Steven D'Aprano]
Maybe I should have fleshed out my example a little more. Usually there are plenty of context clues such that the latter part of a comprehension is more a formality for the computer's sake because computers, unlike humans, can't deal with ambiguity very well. So when you see: def func(people): initials = [person.name[0] ... You can usually guess that, since `person` isn't a local variable, we're probably dealing with a generator so there's probably a `for` clause down the line where `person` is declared and it probably iterates over, I don't know, how about `people` and maybe there's a filter clause too. The point is that the most important information, the "meat" of the expression is up front. In the same vein, In the spirit of EAFP, it's better to get the meat of your business logic up front and do all the tedious-yet-necessary edge-case checking later just like: def func(person=None): initial = person.name[0] ? ... The business logic comes first and the edge-case checking follows.
so it's redundant, so it makes sense that it comes later. If it's redundant, why don't we just leave it out?
Because redundancy from the perspective of the reader doesn't imply redundancy from the perspective of the computer. People can make sense of ambiguous language in a way that computers cant. The sentence "time flies like an arrow" has over 11 valid interpretations, yet few humans would think "oh, you're commanding me to use a stop watch to measure some behavior of common flying insects in the same way that I would use the same instrument to measure the behavior of an arrow!". If I type: def func(people): initials = [person.name[0] for person in people return Counter(initals).most_common(1)[0] You, as a human, can probably guess with extremely high certainty that I meant to put a ']' at the end of the second line. In that sense, it's redundant to you, but not to the computer which doesn't know how to handle that situation. Does that make sense? On Sun, Jul 29, 2018 at 7:48 PM, Steven D'Aprano <steve@pearwood.info> wrote:

Typo: *You can usually guess that, since `person` isn't a local variable, we're probably dealing with a generator *comprehension* On Sun, Jul 29, 2018 at 11:00 PM, Abe Dillon <abedillon@gmail.com> wrote:

On Sun, Jul 29, 2018, 2:00 AM Steven D'Aprano <steve@pearwood.info> wrote:
This is where being wrong matters. The experience in this thread of most supporters failing to get the semantics right shows that this isn't an extra day to learn. It's something that experienced programmers would continue to get won't in edge cases after years of use. The proposed operators don't match how most experienced developers think about code. Being glaringly ugly as written, and using an unintuitive or counterintuitive symbol alternates that, of course. I can hardly imagine a stronger bug magnet than PEP 505.

On Sun, Jul 29, 2018 at 8:32 PM, David Mertz <mertz@gnosis.cx> wrote:
I can hardly imagine a stronger bug magnet than PEP 505.
The hyperbole in this thread is impressive. Not just "I can hardly imagine anyone PROPOSING a stronger bug magnet". You cannot imagine one even existing. And this is after people have proposed that "if x = 1:" become the spelling for assignment expressions - something so well known as a bug magnet that C compilers specifically warn against it, and style guides recommend always writing "if (1 == x)" instead. Claiming that "?." is *the worst possible bug magnet* rather weakens your credibility here. ChrisA

[Chris Angelico]
Almost. Except for the problem that "disgusting" is a subjective term.
Yes. Thank you for explaining the joke. [Brice Parent]
First, I mostly proposed that alternative to serve my argument that there may be a better syntax that PEP 505 would make impossible down the road. It's a half-baked proposal at best and I agree with your over-all sentiment that it's probably not something we should consider either. That being said; in the normal usage of '?.' and '?[]' it would make little sense to only check the beginning of the expression: spam?.eggs.cheese.aardvark # why would you ever do this? It makes some sense to do this: spam.eggs.cheese?.aardvark But in my proposal, you could force any part of the expression to evaluate before the '?' with parentheses and expose whatever errors that might throw: (spam.eggs.cheese).aardvark? On Sun, Jul 29, 2018 at 7:15 AM, Chris Angelico <rosuav@gmail.com> wrote:

On 29/07/18 16:12, Abe Dillon wrote:
spam?.eggs.cheese.aardvark # why would you ever do this?
If you knew that if you really have something in "spam", your program guarantees it will have an "eggs" attribute with a "cheese" attribute, etc, you just don't know if "spam" is not None. It's the same insider knowledge you would use if you wrote it out as spam.eggs.cheese.aardvark if spam is not None else None The same sort of knowledge of your program structure could lead to you to use "?." instead of "." in any place or places in the chain. If your program gives you strong enough guarantees, it is the sort of thing you can use to reduce the code's workload. By way of example, I was working on some C code last night that was expanding an internal buffer, possibly from non-existence. It did lot of taking the difference between various pointers to determine how big the new buffer needed to be, how much to copy from the old buffer, etc. It looked rather hairy until you realised that the code gave a strong guarantee that either all the pointers were meaningful or they were all NULL, so the pointer differences always made sense. I rewrote that code so that it didn't take differences of NULL pointers since that was what the bug specified, but honestly it looks lumpier and less clear now. A big comment explaining what was going on would probably be better. -- Rhodri James *-* Kynesim Ltd

[Rhodri James]
No. The fact that other languages use it doesn't mean that "it isn't unintuitive". The fact that other languages use it doesn't mean that it doesn't harm readability. The fact that other languages use it has pretty much no bearing on any of that. What it actually means is: other languages are trying it out so we have the luxury of seeing if they regret it in the future or if it becomes a popular and well loved feature before integrating it into Python. I believe it's counter-intuitive because '?' always comes at the end of an english sentence, so it reads like other clause-ending operators like the ")" on a function call or the "]" at the end of a index operation, then the "." reads like a follow-on. And no; you can't say the same thing about "." always coming at the end of a sentence because: a) that's not true (e.g. i.e, e.g., etc.) b) "." attribute access has many decades of precedence c) a "." is less visually obtrusive than a "?". [Rhodri James]
Pardon? It composes exactly like any other in-place operator.
Yes. I'll concede. I was wrong about that. [Rhodri James]
yes, you do need to explain how merely being high precedence helps here. Let's start small; please explain programmatically what
name?
does as an expression. Once we have that down, we can move on to person.(name?) Sure. First, I would suggest we define a new class NoneException(BaseException), and derive new Exception multi-classes like: class NoneAttributeError(AttributeError, NoneException). Then I would make those the exceptions thrown by attribute errors on the None object. That way you can conceptualize the '?' operator is to the try-except statement as the ternary operator is to if-else statements EXCEPT that the "except" part of the try-except block is implicitly filled in with "except NoneException: return None" so in general: <expression>? Would parse to: try: result = eval(expression) except NoneException: result = None Similar to how: <expression_1> if <condition_expression> else <expression_2> Parses to: if eval(condition_expression): result = eval(expression_1) else: result = eval(expression_2) More specifically: name? Wouldn't do much: try: result = name except NoneException: result = None And: person.(name?) would throw a SyntaxError because person.( is not valid syntax regardless of my proposal. Perhaps you meant something like: (person.name).first? In that case; person.name would evaluate to some result or throw an un-guarded AttributeError, in the former case the <expression> to the left of the '?' is an attribute access on the result of (person.name). In the latter case; an AttributeError is thrown. That seems pretty straight forward. No? On 29/07/18 16:12, Abe Dillon wrote:
spam?.eggs.cheese.aardvark # why would you ever do this?
spam.eggs.cheese.aardvark if spam is not None else None
That's not the equivalent as described in PEP 505. If "spam" is None, "spam?.eggs" evaluates to None without throwing an AttributeError, but the rest of the expression will throw an AttributeError from trying to access "None.cheese". On Mon, Jul 30, 2018 at 9:10 AM, Rhodri James <rhodri@kynesim.co.uk> wrote:

On Mon, Jul 30, 2018 at 12:41:20PM -0500, Abe Dillon wrote: [Rhodri James]
Yes it is. Rhodri is correct, although I admit that I hadn't realised this point myself until he pointed it out. (That is why until now I've been writing examples like "spam?.eggs?.cheese?.aardvark" which is redundant.) The abstract says: The "None-aware attribute access" operator ?. ("maybe dot") evaluates the complete expression if the left hand side evaluates to a value that is not None and later on the PEP says: 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 I admit the example isn't the clearest. The inclusion of "await" doesn't seem helpful to me (if anything, the opposite, since "await None" will fail) and perhaps it would be clearer to write it as: _v = a if _v is not None: _v = _v.b(c).d if _v is not None: _v = _v[e] The PEP also says: Parenthesised expressions are handled by the atom rule (not shown above), which will implicitly terminate the short- circuiting behaviour of the above transformation. which again isn't too clear, and the example given seems complicated enough to obfuscate the point rather than illustrate it, but if I'm reading it correctly, the behaviour you expected would have to be written as (spam?.eggs).cheese.aardvark which would likely fail since None has no cheese attribute. The bottom line is that in any unparenthesized chain of maybe-dot and maybe-subscript operations, only the first needs to be "maybe" as that will short-circuit the rest of the expression. Figuratively speaking, it is as if we had: spam?(.eggs.cheese.aardvark) where the () are used for grouping, not function call. -- Steve

On Tue, Jul 31, 2018, 1:47 PM Steven D'Aprano <steve@pearwood.info> wrote:
Again, one of the most vocal advocates of the PEP gets the semantics wrong! `spam?.eggs?.cheese?.aardvark` is NOT redundant for ` spam?.eggs.cheese.aardvark`. The two expressions simply do different things, but in a way guaranteed to assure that also everyone gets the actual behaviors wrong. Hint, run this before each expression: spam.eggs.cheese = None

David Mertz wrote:
I agree, assuming ?. is a binary operator. Given this, in Python (+ PEP 505) one can write tmp = spam ?. eggs val1 = tmp ?. cheese ?. aardvark # For spam?.eggs?.cheese?.aardvark val2 = tmp . cheese . aardvark # For spam?.eggs.cheese.aardvark No special knowledge of PEP 505 is needed. If val1 is always equal to val2, then the dot and None-dot operators must be the same. From the assumptions, this is something that can be mathematically proved. By the way, there's a widely used programming language in which val = a.method() and tmp = a.method val = tmp() are not always equivalent. Can you guess which language it is? The answer is in: https://www.slideshare.net/jonathanfine/javascript-the-easiest-quiz-in-the-w... (question 6: Dot binds). I'll now go back to following the example of Steve Bower and Raymond Hettinger, which in my words is to wait until we have proper cover for the BDFL's vacation. -- Jonathan

Op di 31 jul. 2018 20:49 schreef Jonathan Fine <jfine2358@gmail.com>:
It isn't. Given this, in Python (+
Nope, the introduction of the tmp variable changed the semantics. It isn't a "chain" anymore so it breaks shortcutting. To be honest I didn't get this either until it was pointed out to me
And false.
Javascript. I suppose in the same way as x+2 and x*2 are " not always" equivalent. Stephan

Stephan Houben wrote:
Nope, the introduction of the tmp variable changed the semantics. It isn't a "chain" anymore so it breaks shortcutting.
I'm confused. Assume 'a' is not defined. With Python's dot (attribute access) we have
I'd expect, after PEP 505 is added to Python that we'd get
If this is not correct, please some do tell me what we would get. Now assume 'a' is defined. I'd also expect, for any value for 'a', that
If this is not so, then can the second val be computed from tmp? And if so, how? -- Jonathan

On 2018-07-31 20:53, Jonathan Fine wrote:
Correct.
Also correct. On the first point, Steven said that _figuratively speaking_ (his words) the series of attribute accesses would be _as though_ it was grouped a?.(b.c), so if a is None, then remainder of the attributes accessed would be short-circuited. He _wasn't_ suggesting that that was _actual_ syntax. On the second point, you'd said: tmp = spam ?. eggs val2 = tmp . cheese . aardvark # For spam?.eggs.cheese.aardvark i.e. that: spam?.eggs.cheese.aardvark could be rewritten as: tmp = spam ?. eggs val2 = tmp . cheese . aardvark Steven pointed out that that was wrong. In the first, if spam is None, then the remainder is short-circuited, and so the result of spam?.eggs.cheese.aardvark is None. In the second, if spam is None, then tmp would be None, and tmp.cheese would fail.

Hi All I have two further questions. I'm keen to clarify what is the behaviour specified by PEP 505. I'm not, at this time, interested in why and how PEP 505 specifies behaviour. I just wish, through explicit examples, to clarify the behaviour that is specified. Here 'a' is an identifier. Consider the following 12 expressions (I use the word loosely). 1) a . b . c 2) (a . b) . c 3) a . (b . c) 4) a ?. b . c 5) (a ?. b) . c 6) a ?. (b . c) 7) a . b ?. c 8) (a . b) ?. c 9) a . (b ?. c) 10) a ?. b ?. c 11) (a .? b) ?. c 12) a ?. (b ?. c) Question A: Which of the expressions are NOT valid (Python + PEP 505) syntax? Question B: Which of the valid (Python + PEP 505) expressions are equivalent, for all possible values of 'a'. The answer depends, of course, on the exact text of PEP 505. I've not read PEP 505 that closely. My expectations, based on my Python experience, are that PEP 505 would be written so that: Answer A: Expressions 3, 6, 9 and 12 are invalid. The others are valid. Answer B: 1 and 2 are equivalent. Similarly 4 and 5. Similarly 7 and 8. Similarly 10 and 11. There are no other equivalences (for all values of 'a'). I would like to know if my answers to my questions are correct, and if not please may I be given correct answers. Thank you in advance. -- Jonathan

On Wed, Aug 1, 2018 at 5:29 PM, Jonathan Fine <jfine2358@gmail.com> wrote:
Correct. After a dot (whether ?. or plain . ), you need a NAME, not any form of expression (test, expr, etc).
Incorrect. The short-circuiting behaviour ends at any sort of grouping. It's like how "a < b < c" is not equivalent to "(a < b) < c", nor to "a < (b < c)". ChrisA

Hi Chris You wrote:
Oh. The equivalent ones are #1 and #2, and #7 and #8, where this proposal doesn't change anything. Otherwise, they're not equivalent.
Are you sure. I'd also expect #10 and #11 to be equivalent. By the way, there's a typo in my examples: 11) (a .? b) ?. c -- Jonathan

On Wed, Aug 1, 2018 at 6:00 PM, Jonathan Fine <jfine2358@gmail.com> wrote:
Hmm.
10) a ?. b ?. c 11) (a ?. b) ?. c
I would parse those differently, but you may be right that they'll always have the same final result. Technically they should result in different code, though. ChrisA

Hi Chris Thank you for your reply. I think we're making good progress. You wrote
I'd like to get some certainty on this. I'm not aware of any value of 'a' for which #10 and #11 give different values. Can you (or anyone else) think of any such value?
Technically they should result in different code, though.
Maybe. We need to think. Should can be a difficult word. Elsewhere you have, as I recall, pointed out that if None: do_something() generates no code. Perhaps the compiler should collapse #11 to #10, if they are equivalent. But this is a side issue. So, are there any values of 'a' for which #10 and #11 don't give the same result? -- Jonathan

Hi Chris We're discussing.
10) a ?. b ?. c 11) (a ?. b) ?. c
I asked
So, are there any values of 'a' for which #10 and #11 don't give the same result?
You replied
I am willing to put my neck out and say a.b.c and (a.b).c are equivalent. And my understanding for PEP 505 is that #10 and #11 is that they are equivalent. You're right to be cautious. My understanding of PEP 505 is that #13. a ?. b ?. __str__ #14. (a ?. b) ?. __str__ are not equivalent. The first is None, and the other is None.__str__. That looks like a gotcha. (By the way, it was not my intention to catch you out. I'm simply looking for clarity. I wasn't aware of the gotcha until I started answering myself the question I had asked you.) However, the None object is somewhat special, in that all it's methods and double-under (dunder) methods. And one of them is __bool__. And we can't add or change the attributes of None. Chris, you don't have to reply to this. But I would be pleased if an expert could either tell me that my neck is safe, or produce a value of 'a' that cuts it off (so to speak). -- Jonathan

Op wo 1 aug. 2018 10:50 schreef Chris Angelico <rosuav@gmail.com>:
Let me stand up and claim that if a chain consists *only* of None-coalescing operations, then breaking up the chain by adding parentheses does not matter, ever. So a?.b?.c is equivalent to (a?.b)?.c What is
your point here?
It is useful to establish rules under which a chain can be factored. Stephan

Hi Stephan You wrote
You missed a post I made, 17 minutes before yours. I then believed that PEP 505 specified. #1. (None ?. dne) is None #2. (None ?. dne ?. __str__) is None #3. (None.__str__) is not None #4. ((None ?. dne) ?. __str__) is (None.__str__) After my last post, you wrote
None.?__str__ produces None, even though None has a __str__ attribute.
This surprises me. But I think it follows from the rules in https://www.python.org/dev/peps/pep-0505/#the-maybe-dot-and-maybe-subscript-... My initial reading of
In other words, the same as
getattr(a, name, None)
But you and I agree, I think, that PEP 505 specifies (in the cited page fragment)
The abstract to PEP 505 writes === The "None-aware attribute access" operator ?. ("maybe dot") evaluates the complete expression if the left hand side evaluates to a value that is not None === This is, of course, informal prose. I'm now reading it carefully. Here's a specific example.
I don't see how to apply the prose in the abstract to this last example. The left hand side is not None, so we "evaluate the complete expression". On one reading, this is a recursion. I'd very much appreciate some help here. -- Jonathan

On Wed, Aug 01, 2018 at 11:03:05AM +0100, Jonathan Fine wrote: [...]
Correct. None?.__str__ is equivalent to: _v = None if _v is not None: _v = _v.__str__ return _v (except no actual _v variable is created, and it is not literally a return statement).
No. PEP 505 is for None-aware operators. It spends considerable time explaining that it treats None as special. It does not catch AttributeError from missing attributes. (45)?.upper() will fail with AttributeError, same as (45).upper() does.
The phrasing could be clearer. Since 42 is not None, it evaluates (42).str which of course will fail with AttributeError. To be specific, it is equivalent to something like this pseudo-code: _v = 42 if _v is not None: _v = _v.str return _v (The same disclaimer about _v and return apply as earlier.) -- Steve

Hi Steve Thank you for your reply. We're discussing the abstract to PEP 505, which writes === The "None-aware attribute access" operator ?. ("maybe dot") evaluates the complete expression if the left hand side evaluates to a value that is not None === I gave (42).str as an example. I wrote
You wrote
The phrasing could be clearer.
I think the phrasing could be considerably improved (see below).
Since 42 is not None, it evaluates (42).str [...]
Based on this hint, here's what I think is a better statement. === Let `lhs` be the value of the left hand side, and RHS the code fragment on the right hand side. If `lhs` is None, then the whole expression is `None`. Otherwise, `lhs . RHS` gives the value of the whole expression. === Please would the experts tell me: Is it true? And if true, is it better? And can it be improved? -- Jonathan

On Thu, Aug 2, 2018 at 12:04 AM, Jonathan Fine <jfine2358@gmail.com> wrote:
It may be true, but it isn't better IMO - especially not for the abstract. It's unnecessarily pedantic. The current wording isn't ambiguous, because infinite recursion makes no sense. MAYBE change it to "evaluate the rest of the expression", but I don't see a problem with the current wording. ChrisA

Chris Angelico wrote:
Thank you for this. Please take a look and compare https://www.python.org/dev/peps/pep-0505/#abstract https://www.python.org/dev/peps/pep-0572/#abstract I'd like to get an abstract for PEP 572 that is as concise and clear as that for PEP 505. My previous comment focused just on 'the sharp edge that cut me'. But the more I look at PEP 572, the more I see sharp edges (in the expression of the ideas in the PEP). Having said that, I hope now to return to lurking, until we have cover for the BDFL vacation. -- Jonathan

On Tue, Jul 31, 2018 at 02:14:19PM -0400, David Mertz wrote:
It is if *only* spam can be None, which is the scenario being discussed. Please don't ignore the context of statements. I didn't say that spam?.eggs.cheese.aardvark would guard against eggs or cheese being None. That interpretation of my comment is uncharitable and not supported by my re-write of the pseudo-code from the PEP. It also goes completely against my comment "Figuratively speaking..." that the ?. operator groups to the right: spam?(.eggs.cheese.aardvark) # grouping, not function call. Perhaps you didn't read my post all the way to the end before firing off a triumphant post accusing me of getting it wrong. In context, the question is whether: spam?.eggs.cheese.aardvark might attempt to evaluate None.cheese.aardvark. It won't. In context, the issue was not whether these two expressions are identical in every imaginable way: spam?.eggs.cheese.aardvark spam?.eggs?.cheese?.aardvark In pseudo-code, they would be something close to: if spam is not None: return spam.eggs.cheese.aardvark versus if spam is not None: if spam.eggs is not None: if spam.eggs.cheese is not None: return spam.eggs.cheese.aardvark except without needing to evaluate each subexpression multiple times. In context, we are discussing the behaviour when spam is None. When spam is None, there's no need for the additional maybe-dots, because the first short-circuits the rest. If additional attributes also could be None, then they too could be guarded with maybe-dot operators, but if they are never None, it is unnecessary and redundant to guard spam alone all the way through the expression with maybe-dots.
Putting aside your prejudicial language about guaranteeing errors, I think that the PEP could be improved. I managed to read it multiple times without taking in the fact that ?. short-circuits to the right. I didn't explicitly say so (perhaps I should have) but I too initially made the same error as Abe, assuming that spam?.eggs.cheese would fail if spam was None (evaluating None.cheese) but according to the PEP that's not the case. With a working implementation, it would be the work of moments for people to experiment in the REPL and clarify any lingering doubts about the operation. Without it, there will always be some misunderstandings from those who (like me) failed to read the documentation carefully enough and made upjustified assumptions. That's no different from any other non-trivial feature. -- Steve

On Sun, Jul 29, 2018 at 06:32:19AM -0400, David Mertz wrote:
The difficulty one or two people had in coming up with a correct equivalent to none-aware operators on the spur of the moment is simply not relevant. Aside from whichever developers implements the feature, the rest of us merely *use* it, just as we already use import, comprehensions, yield from, operators, class inheritence, and other features which are exceedingly difficult to emulate precisely in pure Python code. Even something as simple as the regular dot attribute lookup is difficult to emulate precisely. I doubt most people would be able to write a pure-Python version of getattr correctly the first time. Or even the fifth. I know I wouldn't. I'm sure that you are fully aware that if this proposal is accepted, people will not need to reinvent the wheel by emulating these none-aware operators in pure Python, so your repeated argument that (allegedly) even the supporters can't implement it correctly is pure FUD. They won't have to implement it, that's the point. -- Steve

Abe Dillon wrote:
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. -- Greg

On Sun, Jul 29, 2018 at 12:49:13PM +1200, Greg Ewing wrote:
You're talking like the syntax is used only by a handful of experimental languages with a total user-base measured in the dozens. ?. is used by some of the most commonly used languages in the world, such as C++, Objective C and PHP, as well as up-and-coming "cool" languages getting lots of industry buzz, like Swift and Dart. Its certainly more familiar now than Python's slicing syntax was when Python first started. -- Steve

Le 29/07/2018 à 08:02, Steven D'Aprano a écrit : the only point (alos, I'm convinced that if we were making a poll with average and advanced users of these languages about what these syntax were doing, a big proportion wouldn't know). I believe the evolution should be done if the benefice are big enough (which I doubt, but I don't use Python in all the ways possible), not because others do it (whatever their reasons were). Then, when we know there's definitely a need to solve a problem, the solution should be chosen (??, but anything else could enter the discussion). Here, we're mixing arguments about (and I confess I've done it too): - how usefull it would be to have a solution to this problem - if it should be solved by Python's syntax or by libraries (if it may be done, and if it may not, how spread are the use cases that can't be done this way) - if other languages support something like that, if some are, how well this was accepted and if it's getting used in new code - the proposed syntax itself

On 28/07/18 21:07, Abe Dillon wrote: [>> = Me]
But it does and did mean that we should consider it when coming up with Python's version of the ternary operator. *It isn't unintuitive* and neither is ?? None-aware operators may be bad for other reasons, but that argument doesn't wash.
(This was about ??=, for context.) Pardon? It composes exactly like any other in-place operator. [Abe previously wrote:]
Apology elsewhere accepted, but yes, you do need to explain how merely being high precedence helps here. Let's start small; please explain programmatically what name? does as an expression. Once we have that down, we can move on to person.(name?) (parentheses for emphasis.) Short of actual magic, I can't see how this is going to work. -- Rhodri James *-* Kynesim Ltd

On Mon, Jul 23, 2018 at 6:05 PM Giampaolo Rodola' <g.rodola@gmail.com> wrote:
I think both of those are equally explicit. It's just that one notation is more concise than the other. Explicitness and conciseness are related but different things. When something is "explicit", as I understand it, that means it does what it says on the cover. There is no unstated behavior. The plain meaning of `v = a?.b` is that it expands to the longer form (`v = a; if a.b ...`), and it is just as explicit. This reminds me of something I read about once called Stroustrup's Rule <https://thefeedbackloop.xyz/stroustrups-rule-and-layering-over-time/> [1]:
For new features, people insist on LOUD explicit syntax. For established features, people want terse notation.
I think the "explicit vs. implicit" part of this discussion is probably better expressed as a discussion about "loud vs. terse" syntax. None of the operators in PEP 505 have implicit behavior, to the best of my understanding. It's just that the operators are new and have terse spellings. As a point of comparison, I think a good example of implicit behavior is type coercion. When you ask Python to add an int to a float a = 3 + 4.5 all that you've explicitly asked for is the addition. However, Python implicitly converts the 3 from an int to a float as part of the operation. The type conversion isn't anywhere "on the cover" of the + operator. It's implicit behavior. [1] Bjarne Stroustrup makes the observation in this talk <https://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Keynote> at 23:00.

On Wed, Jul 25, 2018 at 12:12:40PM -0400, Nicholas Chammas wrote:
Right. We don't insist on writing mydict.look_up_key_in_self_and_raise_keyerror_if_the_key_is_not_found(key="spam") instead of mydict["spam"] out of a mistaken idea that explicitness requires verbosity and that punctuation is always implicit. Symbols (whether made of text or punctuation) have meaning, and that meaning has to be taken into account. You *can't* spell everything out in full. We always have to take the meaning of something as given, and that can be punctuation just as easily as words.
That is a great observation! Thanks. -- Steve

The problem here is not whether it's explicit. It's about Readability and conciseness. Using symbols in place of words almost always harms readability in favor of conciseness. value = person.name if person.name else person almost reads like english (aside from being a weird and totally uncommon use case) value = person?.name Is a huge step towards the concise illegible soup of symbols that Perl is famous for. It's a huge No from me. On Wed, Jul 25, 2018 at 11:12 AM, Nicholas Chammas < nicholas.chammas@gmail.com> wrote:

On Wed, Jul 25, 2018 at 3:11 PM Abe Dillon <abedillon@gmail.com> wrote:
Value is name of person almost reads like english. value = person.name Starts to look like pearl (but does not avoid repetition; only hurts english-like-readability) - or perhaps some other programming languages that use similar operators, such as C#, Swift, Dart, F#, Kotlin and others. As far as I know it is not generally considered a bad addition in any of these languages, all of which put emphasis on readability. Elazar

On Wed, Jul 25, 2018 at 6:11 PM Abe Dillon <abedillon@gmail.com> wrote:
The two statements you wrote are not the same. The first statement will error out if person is None. The proposed None-aware operators are specifically designed to handle variables that may be None. The first statement should instead read: value = person.name if person is not None else person That's what `value = person?.name` means. As others have pointed out, I suppose the fact that multiple people have messed up the meaning of the proposed operators is concerning. Perhaps the PEP could be improved by adding some dead simple examples of each operator and an equivalent statement that doesn't use the operator, to better illustrate their meaning. But I gather that will do little in the way of addressing some of the stronger objections raised here.

The two statements you wrote are not the same. The first statement will error out if person is None.
That's my bad. I was copying off of an erroneous example. Thanks for correcting me. The proposed None-aware operators are specifically designed to handle
variables that may be None.
Yes, I think the syntax that you've landed on is confusing enough that it opens the door to more errors than it closes. Just reading "(a?.b ?? c).d?.e" and "await a?.b(c).d?[e]" On Wed, Jul 25, 2018 at 6:06 PM, Nicholas Chammas < nicholas.chammas@gmail.com> wrote:

*sorry, cat hit "send"... Just reading those examples made me want to cry and go hide in a dark dark cave and never come out. I'm sure using actual variable names would help a bit, but not much. As for the non-english nature of: value = person.name I highly disagree with the argument that since that is a step removed from natural language readability that: value = person?.name Should be considered fair game. Nor do I buy the "other languages do it" argument. Some of the syntax of Python is based on familiar patterns in other languages (like '.' access) some of it is based on common math (e.g. "=", "+", etc.) which is also taught in grade school. Some of the patterns borrowed from other languages were a mistake and considered cruft. Some of that cruft was scraped off in the 2to3 migration. Maybe "." should have been apostrophe "s" all along. Maybe lambda should have been 'make_function'. That's not what we're here to discuss. A lot of languages have a ternary operator (x ? y : z). Python wisely used words instead of symbols and now many students don't even have to crack a book to decipher Python's ternary operator. Adding crap to a language is easy. Removing it is damn near impossible. You have to have extremely good reasons to add new syntax and I don't have to defend any of Python's warts to justify rejecting yours. On Wed, Jul 25, 2018 at 6:15 PM, Abe Dillon <abedillon@gmail.com> wrote:

On Wed, Jul 25, 2018 at 05:11:08PM -0500, Abe Dillon wrote:
And that is why we prefer COBOL over unreadable Perl-like languages that use unreadable symbols like: arithmetic operators + - * / // % ** bitwise operators & | ^ >> << ~ comparison operators == != < <= >= > function call symbols func(arg) argument packing and unpacking *args, **kwargs sequence slicing seq[::] assignment = comments # decorator syntax @ What a horrible, unreadable language that would be. REMARK this is much better than Perl-like x = func(a, b, c) PUT THE RESULT OF CALLING FUNCTION func WITH ARGUMENTS a AND b AND c INTO x \s Why do you claim that symbols and conciseness is "almost always" worse for readability? Your assertion doesn't survive even a cursory consideration. Readability doesn't occur in a vacuum. You have to understand the symbols, and it doesn't matter whether they are punctuation or words, if you don't understand them, they're "unreadable": result = page_count + 1 # readable jwrtsv = xpwvabs_ue + 1 # what? result obqxayrhs page_count wmsna 1 # what? The first time I encounted Python 1.5, I found it unreadable: it was full of mysterious keywords "class", "def", strange punctuation like [1:] and x.y, and idioms I couldn't guess like for i in range(len(seq)) With no context, it might as well have been Ancient Sumarian. Do you know what helps readability? *Learning to read*. Once you have learned to read ?. and friends, they will be as readable as . and slicing is now. -- Steve

On Thu, Jul 26, 2018 at 5:10 AM, Steven D'Aprano <steve@pearwood.info> wrote:
[...]
No it is not like that. E.g. slicing is intuitive and straightforward concept even for a non-programmer. And no, it is not (only) about learning. For example, no matter how experienced a programmer is, complex comprehensions will be less readable than the same construct written in loops and ifs. Or say this: user?.profile?.food will be less readable than say: user » profile » food No matter how long you learn it, the former remains ugly and obfuscating. With that said, I don't say this syntax deserves something better, I just say that I can't see how your "learn more" argument applies here. Mikhail

On 25/07/18 23:11, Abe Dillon wrote:
ITYM value = person.name if person.name is not None else None
almost reads like english (aside from being a weird and totally uncommon use case)
I think Nicholas (?) was right to observe that it's uncommon to not want to know where a deeply nested attribute access failed. Exactly what you want to do when an underlying library (presumably) has "helpfully" turned non-existent attributes into Nones is going to vary tremendously from application to application. On the other hand, value = parameter ?? default encapsulates a very common usage, and doesn't read badly compared with: value = parameter if parameter is not None else default which is a tad wordy. That example isn't too bad, but replace "parameter" with "spam.spam.spam.eggs" and DRY suddenly becomes a rather more important principle :-) -- Rhodri James *-* Kynesim Ltd

The fact that a while bunch have people have commented on this subthread while not recognizing that the semantics of the '?.' and the if blocks are entirely different suggests the operators are but magnets. On Wed, Jul 25, 2018, 5:17 PM Nicholas Chammas <nicholas.chammas@gmail.com> wrote:

Can you explain? What do you mean by "the operators are but magnets"? The "None coalescing" operator seems so similar to the short circuit behavior of "or" that it has pretty much no merit. It's compared to ternary statements in the last section of the PEP (which is suspiciously lacking the "or" pattern). I would agree that Python could use some more support for EAFP <https://en.wikipedia.org/wiki/EAFP> style coding. An express-ionized version of try/catch might help there, but I'm pretty sure the search for an elegant solution to that has been relatively fruitless. The attribute access and indexing are just unreadable in my view. Maybe if the question mark came at the end of the expression it would be more readable and just mean, "if the preceding expression raises an attribute exception on a none-type object, ignore it and evaluate to None otherwise return the result of the evaluation" Then just use parentheses to capture the scope: initial = (person.name[0])? # handles if person is None or person.name is None but that still seems like a good way to end up with very ugly code. Haskel's Maybe seems like a much better and more readable approach. On Wed, Jul 25, 2018 at 5:36 PM, David Mertz <mertz@gnosis.cx> wrote:

Sorry. From my tablet. "Bug magnets" (it really, really wants to autocorrect that) And yes, the problem is that the equivalent is actually: v = a if v is not None: v=a.b The semantics are simply not the ones that are intuitive to most people reading 'v = a?.b' On Wed, Jul 25, 2018, 7:01 PM Abe Dillon <abedillon@gmail.com> wrote:

On Wed, Jul 25, 2018 at 07:06:35PM -0400, David Mertz wrote:
Tell us more about these "intuitive to most people" semantics. What are they? Did you do a survey? How many people did you ask? Did you do a comparison to the "intuitive to most people" semantics of slicing syntax? Intuitive does not mean "familiar". If you're going to dismiss a proposal because it's not "intuitive"[1] then to avoid accusations of intellectual hypocracy you need to do one of two things: - demonstrate that the syntactic features we know and love and use frequently (e.g. slicing, decorators, comprehensions) are intuitively obvious and don't need to be learned; - or say that *those features were terrible errors* that Python still has not recovered from. You can't have it both ways: its okay that people have to learn slicing, decorators, comprehensions, never mind that they aren't intuitive, for *those* features "intuitiveness" isn't that important; but for this proposal, "intuitiveness" is all the matters: usefulness, conciseness and the increase in expressivity don't matter one whit. Its one thing to say that slicing, dot attribute access, decorators, augmented assignment, comprehensions etc are mistakes we should not repeat. But this double-standard of demanding higher standards for new features than we accept in existing, beloved features is one that we should reject with extreme prejudice. [1] To whom? Babies? First-time programmers? People with a Ph.D. in comp sci and thirty years experience in half a dozen languages? -- Steve

Just wanted to note another possibility (most likely to be added to the "rejected solutions" section). Add a prefix "!" operator, which will work similarly to the iterator-unpacking operator "*", but for None. The expression "[!x]" is equivalent to the expression "[] if x is None else [x]". This can be combined with the "or" operator, now working as expected since it's about lists, and a simple "first" or "single" library function. Example: optdict = dict(encoding=single([!encoding] or sys.getdefaultencoding()), css=options.css) It is more verbose than the proposed "??" operator (which I personally like), but maybe it's less line noise. One can use it with simple parens and get a tuple, "(!x) or y". note that "(!x or y)" is a syntax error in this case. Another, related idea: instead of "??" use a double-token operator "! or": value = ham! or spam! or eggs optdict = dict(encoding=encoding! or sys.getdefaultencoding(), css=options.css) Elazar

The curious thing about PEP 505 as far as I can see is that it introduces a new piece of syntax -- and for many people (to judge from the reactions here) a controversial piece of syntax -- to solve what seems to be a rather specific problem. The use-case that seems most discussed is unpacking information from nested JSON. One of the things that makes me really twitch about the examples like food = ham?.eggs?.spam?.chips is that I don't usually deal with deeply nested structures like that where it wouldn't matter to me which level of the nest returned a value. But let's leave that aside, because it seems to be an issue people face. For the specific case mentioned, what would be wrong with a utility function that looked something like: # Warning - typed on gmail before I've had my morning coffee and so not # at all tested, but I hope you can see what I mean. def nested_conditional_find(search_this, *args): this = search_this for term in args: new_this = getattr(search_this, term) if new_this is None: return this else: this = new_this then you could use this function as: food = nested_conditional_find(ham, 'eggs', 'spam', 'chips') One could even imagine adding extra tests along the way to a function like that, raising attribute errors if the wrong kind of data was encountered etc. What problem does PEP 505 add that can't be solved very nearly as elegantly by a utility function? PEP 505 doesn't seem to be about speed, only convenience. For that matter, for specific use cases, why couldn't objects be created where the attribute access functioned in just this kind of way? (not example code I'm willing to attempt before coffee ;-) ) Just a thought. I've carefully avoided words like 'pythonic', 'unpythonic', 'explicit', 'implicit', 'compact', 'bug-magnet' or 'perl'. Best wishes, N P.S. As an aside, in examples like food = spam?.eggs?.bacon -- if bacon is None then food is None, even if there were in fact eggs and spam. That thought tells me it must be breakfast time.

On 25/07/18 23:36, David Mertz wrote:
*Entirely* different? Rubbish. It's more like the difference between "+" and attempting to render "+" as a function using dunder methods. I'm not keen on "?." and "?[]", but they are nothing like as bad as you are implying, and certainly not as bad as introducing a whole new concept like boxed types. -- Rhodri James *-* Kynesim Ltd

On 2018-07-23 13:04, Giampaolo Rodola' wrote:
[snip] I think you're misunderstanding something: we're not talking about a special operator "?" that somehow combines with existing operators, we're talking about completely new and separate operators "?.", "?[", etc., which resemble the existing ".", "[", etc.

On Sun, Jul 22, 2018 at 05:09:39PM +0200, Giampaolo Rodola' wrote:
Does this mean that instead of writing: result = obj.attr + 1 you prefer to be "explicit" and split it over multiple lines? # named functions are always better than punctuation from operator import add # explicit is better than punctuation value = getattr(obj, "attr") result = add(value, 1)
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. Do you think that Python is a worse language than it should have been because we use . for attribute access?
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'): ... etc. There is a HUGE amount of implicit complexity and work done behind the scenes in every import, and we're happy to keep it that way. -- Steve

On 2018-07-22 08:10, Steven D'Aprano wrote:
I like the ?? operator too; I like the short circuiting behavior a lot, and the semantics are simple. I guess I'd use the other operators fairly often, too, mostly in quick-n-dirty scripts. The one place where I miss them is when browsing through dictionaries that I get by querying a remote server and deserializing the resulting JSON. I foten have situation where the value I'm interested in is e.g. either in response[0]["addresses"]["workplace"]["email"], or in response["records"][0]["contactInfo"]["emails"][0], and any of these subrecords may be missing. Rewriting these using the ?[…] and ?. operators, I guess I would write something like this: tmp = response?.get("records") try: tmp = tmp?[0] except IndexError: tmp = None tmp = tmp?.get("contactInfo")?.get("emails") try: tmp = tmp?[0] except IndexError: tmp = None Is there a shorter way to write these with the "?[…]" and "?." operators? I guess the difficulty is that I need to swallow index and key errors, not just the type errors that come from indexing into None. For cases like the one above, I usually use something like nget(response, ["records"], [0], ["contactInfo"], ["emails"], [0]), where nget is defined as shown below (in this use case, the lack of short-circuiting isn't an issue): def nget(obj, *fields, default=None): for field in fields: if obj is None: return default if isinstance(field, str): obj = getattr(obj, field, None) elif isinstance(field, list): try: obj = obj.__getitem__(field[0]) except (TypeError, KeyError, IndexError): obj = None return obj class Test(): def __init__(self): self.x = [{"y": 42, "z": ["aBc", "def"]}, [1]] a = Test() print(nget(a, "x", [0], ["z"], [0], [1])) # B print(nget(a, "x", [0], ["y"])) # 42 print(nget(a, "z", [0], ["y"], default="not found")) # not found print(nget(a, "z", [57], ["y"], default="not found")) # not found It would probably not be hard to wrap this into a special object, to be able to write something like wrap(response)["records"][0]["contactInfo"]["emails"][0].unwrap(). "wrap" would change its argument into a proxy returning a special indexable variant of None on key errors, and that dictionary would also call "wrap" on the results of __getitem__. Something like this: class wrap(): SENTINEL = object() def __init__(self, obj): self.obj = obj def unwrap(self): return self.obj def __getitem__(self, key): try: return wrap(self.obj.__getitem__(key)) except (TypeError, AttributeError, KeyError): return wrap(None) a = [{"y": 42, "z": ["aBc", "def"]}, [1]] print(wrap(a)[0]["z"][0][1].unwrap()) I think that's more or less what pymaybe does, in fact. Cheers, Clément.

Steven D'Aprano writes:
Sure, if "expression or default" won't do. But in my code, I can't recall encountering a case where it wouldn't. That is, in the vast majority of my code, the point of using None as the "oops, try again" sentinel is not that it's a different *value* from a falsie of the expected type, it's that it's a different *type*. It has very few attributes, and so will most likely eventually raise if I forget the "or default" clause and a falsie escapes from the enclosing code, rather than silently doing an erroneous computation. Of course "in my code" is very anecdotal, and we do have testimony from many people that these cases are important to them. I'd still like to know, not how much code looks better with "expr ?? default" vs. spelling it out as a conditional statement or expression, but rather how much code *must* be expressed as "expr ?? default" (or "expr ?. attr ?? default", as Steve Dower reminds us) because the falsie of expected type is a useful value of expr (or expr.attr).
No, it somewhat obfuscates the DRY violation, but you've still used "temp" twice. (Consider "var if var is None else default".) I also don't agree that it's more verbose than the first version if "expression" is more complex than a variable reference, not to mention the possibility of side effects in expression. Steve

On 2018-07-22 10:33:23 -0700, Michael Selik wrote:
They are also words if you are not an English speaker. I don't speak Chinese, but "Pǔtonghuà" is certainly a word for me (although I wouldn't recognize 普通话 as that word).
I know a few people who don't know enough English to read English documentation. But AFAIK they didn't have a problem memorizing a few dozen keywords. Learning the semantics of a programming language is a much larger task than learning a few words, and having familiar keywords probably doesn't really help much (they still don't mean what they mean in English). Of course we do use the Latin alphabet, I don't know how somebody who had to learn the Latin alphabet specifically for programming would cope. It's probably like programming in APL. I guess I could learn PerlYuYan[1] to find out ;-). hp [1] https://metacpan.org/pod/Lingua::Sinica::PerlYuYan -- _ | Peter J. Holzer | we build much bigger, better disasters now |_|_) | | because we have much more sophisticated | | | hjp@hjp.at | management tools. __/ | http://www.hjp.at/ | -- Ross Anderson <https://www.edge.org/>

[Steve Dower <steve.dower@python.org>]
...
* The "``None``-aware attribute access" operator ``?.`` evaluates the
complete expression if the left hand side evaluates to a value that is not ``None``
And if the LHS does evaluate to `None` ...? I'll assume the result is also `None` then.
I got lost on the `for` here. The part following `in`: for name in getattr(base, "__abstractmethods__", ()): looks in `base` (regardless of whether `base` is `None`) for an attribute named "_abstractmethods__".. If such an attribute exists, the value of the attribute is returned (`None` or not). Else an AttributeError is swallowed and `()` is returned. It's hard to see how for name in base?.__abstractmethods__ ?? (): does the same. If `base` itself is `None`, I guess it returns `()`, or if `base` has an "_abstractmethods__" attribute then the value of that attribute is returned - unless its value is None, in which case `()` is again returned. But if `base` is not `None` and the attribute does not exist, doesn't this raise AttributeError? The later "Exception-aware operators" section seemed to explicitly reject the idea that `?.` and `?[]` would suppress AttributeError and/or TypeError. In short, the original getattr() didn't care at all whether `base` was `None`, or whether the value of its "__abstractmethods__" attribute was `None`, but cared a whole lot about whether that attribute exists. I just can't see how the updated code matches that in any of those respects. Ignoring that and pressing on, I suffer the same kind of confusions on the `if` part. What am I missing? For example, do these operators swallow exceptions after all?

I am rather fond of the idea of null-coalescing, at the very least, for mutable default values: def foo(a=None): a ??= [] ... but I worry about the code messes we will run into with some of the other options. Woe be unto anyone forced to understand the behavior of: thing?.attr?[key]?.subattr ?? 127 What if we added the Elvis operator "?:" for null coalescing and left the rest for future consideration On Wed, Jul 18, 2018 at 10:49 PM Tim Peters <tim.peters@gmail.com> wrote:

Hi, I think this breaks one of the most important aspect which makes Python more and more popular every year: its readability. Until now, you didn't need to know the language very well to have some understanding about what a script did. Some parts of it could be a bit complicated (like comprehensions, which we only allow in unit tests and small utilities where I work, exactly for this reason) or non-obvious (like the new := operator, but even if you don't get exactly what it does, it's not really cryptic either. It's still some-kind-of-assignment). We've been teaching for years that there is virtually no cost in adding a few simple lines of code from times to times, and that it has to be done to improve readability, while this proposal's goal seems to be the opposite (make the code more compact at the cost of readability), which complicates the understanding of the whole language to save a few `if x is None:`. To me, it goes the opposite of most of the first lines of the zen of Python. Where I work, we update most of our projects to use new Python versions almost immediately when a new one is out and the libs we use are compatible. This would probably change that; we would likely wait for a few years, the time for the feature to be used widely in other projects, and for us to have some real-life feedback to know what to do about it, like: are there some cases it really is justified? Does it make it harder/simpler to read/write python code for experts and non-experts? Should we add a rule to enforce its use, or to prevent it? The biggest drawback of this, is that (if I understand it well), it may be done quite easily without any change to the language: def first_set(*elements): # please don't mind the name of the function, it's not the purpose here """ Will return the first element that is not None """ for element in elements: if element is not None: return element raise AllNoneException() first_set(3, 5) # -> 3 first_set(None, 5) # -> 5 first_set(None, None, 8, 10) # -> 8 first_set(None, Car(model="sport")).buy() # calling Car(model="sport").buy() first_set(None, ["a", "b", "c"])[1] # -> "b" first_set(None, None) # -> exception is raised (note that such function could even accept a "rejected_values" kwarg, like `rejected_values=(None, [], "")`, just by replacing the `if` clause by `if element not in rejected_values:`) I might have missed some implications, or even not understood the PEP at all, though! - Brice

On Thu, Jul 19, 2018 at 5:45 PM, Brice Parent <contact@brice.xyz> wrote:
No it can't, for the same reason that the 'and' and 'or' operators can't be implemented cleanly as functions: it short-circuits. The right-hand operator _will not_ be evaluated unless the left is None. ChrisA

Honestly speaking, I don't want make Python syntax more complex. But when comparing to accepted PEP 572, I think this PEP is useful often enough. And PEP 505 doesn't break border between expression and statement unlike PEP 572. Especially, ?? and ??= seems useful very often. And `x ?? default` seems much more readable than `x if x is not None else default` or `default if x is None else x`. On the other hand, `?.` and `?[]` seems useful less often and more confusing. When looking `spam?.egg`, people can expect `getattr(spam, 'egg', None)` rather than `spam.egg if spam is not None else None`. Since `?.` and `?[]` can't avoid AttributeError, IndexError and KeyError, I think it's confusing and not useful enough compared with it's ugliness. So my current position is +1 for `??` and `??=`, but -1 for others. Regards, -- INADA Naoki <songofacandy@gmail.com>

Chris Angelico writes later in thread:
On Thu, Jul 19, 2018 at 9:55 AM, Giampaolo Rodola' <g.rodola@gmail.com> wrote:
I am 100% in sync with the reasoning, but -0 on the PEP (and only that high because the advocates are so passionate). To be honest, code transformations like this
to this
make me cringe. Exactly one of two things is true: 1. mimetypes.guess_type guarantees that the only falsie it will ever return is None, or 2. it doesn't. In case 1, "ctype or 'application/octet-stream'" ALSO does the right thing. In case 2, ONLY "ctype or 'application/octet-stream'" does the right thing, as few callers of BaseUploadObject.find_content_type will be prepared for "", (), [], or any variety of 0 as the return value. This example is extreme in that by the nature of application/octet- stream, any caller that can handle it at all will do a very reasonable thing if find_content_type barfs up a falsie that is not None. In many cases, of course, it would be better to (eventually) raise an exception when a falsie escapes the expression. But in this particular example, it's hard to imagine that is true: if the user's expectation is violated, they'll complain, and then you can go debug. In the meantime, the application doesn't crash and work gets done. The prevalence of these cringe-worthy examples in advocates' posts are why I'm basically - on the idea. So I would like to see various examples of code where 1. in the original code, treating a falsie that is not None the same way that None is treated is a detectable bug; and 2a. letting such falsies escape to raise exceptions somewhere else is a good idea (and how you could know that); or 2b. catching spurious falsies and handling them here is a better idea; or 2c. some falsies that are not None are legitimate. That's three kinds of examples. I suspect the 2b examples are going to be pretty "meh", because you're saving little reader cognition or code. More important are the 2a examples, because I suspect that most examples will fall into the same category as find_content_type: "or" is as good as "??", and that "??" is sometimes an accident waiting to happen due to falsie escapes (eg, when receiving objects from somebody else's code who might not understand the contract that the only falsie they should ever provide is None). Steve

On 7/19/2018 4:32 AM, Stephen J. Turnbull wrote:
It seems to me that the problem is returning None. Guess_type should have default='application/octet-stream' (or whatever *is* the default) in its signature. The premise of returning None as default is that users can follow with conditional code. If we now think that this is too much of a burden, we should stop returning None, at least by default.
or 3. it never returns anything other than a non-blank string unless the user so requests. -- Terry Jan Reedy

On Thu, Jul 19, 2018 at 2:19 PM Terry Reedy <tjreedy@udel.edu> wrote:
It seems to me that the problem is returning None.
If functions raised errors instead of returning None, we wouldn't have so much trouble. Much of the benefit of PEP-572 was handling Nones and similar sentinel values. Instead of providing better tools for handling Nones, I'd rather encourage folks to raise exceptions with a nice tool like PEP 463, exception-catching expressions. Re-reading Guido's explanation for rejecting PEP 463, I now understand the acceptance of PEP 572 better, as he also disagrees with the preference for EAFP over LBYL. For what it's worth, I agree with the folks that think ?. and ?[ create too much visual clutter and are too easy to overlook or misunderstand. The ??= and ?? operators will have spaces around them, so they're easier to read. However, their benefit is so minor, if any, that it's better to stick with the status quo. I'd rather write one of these patterns: # when x was assigned elsewhere if x is None: x = value # when x is typically None value if x is None else x # when x is typically not None x if x is not None else value # when I'm playing code golf x or value In many of these cases I'd rather write 4 lines of code. It reads how I'd speak the logic to another person, though out-loud I'd probably add a "then" and say "otherwise" instead of "else": if x is None: y = a else: y = b

Terry Reedy writes:
On 7/19/2018 4:32 AM, Stephen J. Turnbull wrote:
make me cringe. Exactly one of two things is true:
It seems to me that the problem is returning None.
Exactly, but this seems to indicate that the problem I'm talking about is the code. It's not; my issue is with the use of THIS code as an example justifying "??".
Note that in the case of an MUA, code might look like something like this: class BaseSaveObject(object): # NOTE NAME CHANGE! def find_content_type(self, filename): ctype, encoding = mimetypes.guess_type(filename) while ctype is None: ctype = input('Couldn't recognize MIME type. Ideas?") ctype = self.validate_mimetype(ctype) # returns None on bad return ctype So returning "application/octet-stream" is probably inappropriate if parsing the message header for Content-Type fails. I'm not sure why you say "burdensome". Using "x() or y()" *because* it short-circuits is idiomatic (although some deprecate it). The main point of "??" is that "or" is not good enough because it does the same thing with other falsies that it does with None. There are other tweaks (such as very high precedence), but that's the main thing. What I'm looking for is a set of examples where "or" won't do, and explanations of *why* that is true and why programmers *won't* abuse "??" to create less resilient code, to balance against the avalanche of examples where "??" leads to less resilient code, such as this case. Note that if you want to use "??" rather than "or", there's at least one other falsie that you want to *avoid* a default for, and then in cases like this one, the question is "why aren't you checking for it to prevent exceptions in a module written long long ago in a galaxy far far away?"
That would be nice, but that's off-topic. The point here is that Steve ripped a page from Tim's book and transformed a pile of real code. (This deserves a standing ovation IMO!) So he has to take the API as it is, warts and all. Steve

On Thu, Jul 19, 2018 at 05:32:48PM +0900, Stephen J. Turnbull wrote:
I don't think that's a good use of ?? and that method should either remain in the original form, or at most, be re-written using return ctype if ctypes is not None else 'application/octet-stream' (although I prefer the original form). There doesn't seem to be any advantage to ?? in this example except to transform a four-line imperative-style return statement to a one-line return statement, trading off vertical space for horizontal. But there's no improvement in expressiveness that I can see.
It does the right thing, but it doesn't look like it does the right thing. The ?? operator promises to only transform None, and nothing but None. To the reader, the `or` operator promises to transform any falsey value. Is `or` too broad and do too much? There's no way of knowing. Instead of treating `guess_type` as a black-box, the reader now has to dig-deep into its documentation and/or implementation to find out which falsey values it might return and whether or not they ought to be treated the same as None.
I doubt that guess_type (and hence find_content_type) will return 0 or [] etc as part of their documented interface (as opposed to a bug), but if they did, surely we ought to assume that the caller is prepared to handle its output. More likely is that it might return the empty string "". At face value I would expect that a better interface for guess_type would be to return the empty string rather than None. But perhaps there is a legitimate reason to treat the empty string as distinct from None, it should *not* be transformed. On perhaps the function is avoiding the "failure looks like success" anti-pattern. Who hasn't been bitten by writing something like this? chunk = text[text.find('spam')+4:] It looks okay at a glance: return the chunk of text immediately following "spam". But if "spam" isn't present at all, it returns the entire text minus the first three characters, which is probably not what you wanted. So there is a good argument to be made that guess_type ought to promise: - return None if it cannot guess the type; - otherwise return a non-empty string. (or that the empty string is itself a legitimate output that ought to be considered distinct from None and should *not* be transformed). In that case, `or` reads wrongly, because it either wrongly transforms "" into the default when it should not, or it suggests to the reader that it might. Either way, an explicit test for None -- whether we use "if ... is None" or the proposed ?? operator -- is better than a test for falsey.
I don't see that this is relevant. We already had this discussion, when the ternary if was introduced, and a million times when discussing why it is better to write "if spam is None" rather than "if not spam". In other words, we ought to be comparing the expressiveness of process(spam ?? something) versus: process(something if spam is None else spam) (and similar variations), not against process(spam if spam else something) process(spam or something) both of which do something different from the ?? operator. -- Steve

Just for fun, I decided to go through some recently written code by some genuine Python experts (without their permission...) to see what changes would be worth taking. So I went to the sources of our github bots. Honestly, I only found three places that were worth changing (though I'm now kind of leaning towards ?[] eating LookupError, since that seems much more useful when traversing the result of json.loads()...). I'm also not holding up the third one as the strongest example :)
From https://github.com/python/miss-islington/blob/master/miss_islington/status_c...:
async def check_status(event, gh, *args, **kwargs): if ( event.data["commit"].get("committer") and event.data["commit"]["committer"]["login"] == "miss-islington" ): sha = event.data["sha"] await check_ci_status_and_approval(gh, sha, leave_comment=True) After: async def check_status(event, gh, *args, **kwargs): if event.data["commit"].get("committer")?["login"] == "miss-islington": sha = event.data["sha"] await check_ci_status_and_approval(gh, sha, leave_comment=True)
From https://github.com/python/bedevere/blob/master/bedevere/__main__.py:
try: print('GH requests remaining:', gh.rate_limit.remaining) except AttributeError: pass Assuming you want to continue hiding the message when no value is available: if (remaining := gh.rate_limit?.remaining) is not None: print('GH requests remaining:', remaining) Assuming you want the message printed anyway: print(f'GH requests remaining: {gh.rate_limit?.remaining ?? "N/A"}')
From https://github.com/python/bedevere/blob/master/bedevere/news.py (this is the one I'm including for completeness, not because it's the most compelling example I've ever seen):
async def check_news(gh, pull_request, filenames=None): if not filenames: filenames = await util.filenames_for_PR(gh, pull_request) After: async def check_news(gh, pull_request, filenames=None): filenames ??= await util.filenames_for_PR(gh, pull_request) On 19Jul2018 2222, Steven D'Aprano wrote:
Agreed, though to make it a more favourable comparison I'd replace "spam" with "spam()?.eggs" and put it in a class/module definition where you don't want temporary names leaking ;) Cheers, Steve

This is adding a whole range of new operators without enough of a use case. It is also making code harder to read, as evaluation can stop at any of the "?*" operators. And it looks like noise (or like Perl 6, which is the same). There is a use case I sympathize with: the argument-is-None case. For that I would suggest a simpler form: "A else B" which would evaluate to A if A is not None, otherwise to B (parentheses may be mandatory). So e.g. one of the examples would read: def insort_right(a, x, lo=0, hi=None): # ... hi = hi else len(a) # ... Regards Antoine. On Wed, 18 Jul 2018 10:43:36 -0700 Steve Dower <steve.dower@python.org> wrote:

On 19/07/18 09:33, Antoine Pitrou wrote:
Much as I would like a keyword, "else" is the wrong one. It implies we are dealing with truthiness, which we aren't, and lays a subtle semantic trap as a consequence. If anyone can think of a good word for "if it isn't None, otherwise", I'd be all for it :-) -- Rhodri James *-* Kynesim Ltd

On Thu, Jul 19, 2018 at 8:47 AM Rhodri James <rhodri@kynesim.co.uk> wrote:
I think that it may look better with the order switched and the word unless, as in def insort_right(a, x, lo=0 hi=None): # ... hi = len(a) unless hi # ... Unfortunately, this does maybe feel more like checking for truthiness than non-Null value

FWIW, I like ?? It is short and distinctive. There is prior art in this spelling in c#. It requires no new keyword, nor does it give new meaning to an existing one. I understand why ?[ needs to be spelled using only a single ?, but I am afraid it will be used infrequently, and people will accidentally write a??[x] which is legal but different. I found the example code in the PEP using ?. and ?[ hard to read. ?? and ??= are compelling, though. One more question: what does this do? del x x ??= 42 Stephan Op do 19 jul. 2018 15:00 schreef Judah Levy <judah.j.levy@gmail.com>:

On 7/19/2018 8:46 AM, Rhodri James wrote:
'A else B' is about the truthiness of 'A is not None', just as 'A or B' is about the truthiness of 'bool(a) is not False'. See my response to Antoine. A else B' would abbreviate 'tem = A; tem if tem is not None else B'
and lays a subtle semantic trap as a consequence.
I think most anyone should be able to get that 'A else B' is similar to 'A or B' but must be different.

On Thu, Jul 19, 2018 at 02:47:11PM -0400, Terry Reedy wrote:
I think most anyone should be able to get that 'A else B' is similar to 'A or B' but must be different.
Knowing that `else` is different to `or` is not the problem. As you say, that's obvious. The problem is that `A else B` looks like it ought to be the same as "else B" in if...else statements and the ternary if operator. That is, "if the condition is false", and in this case there is nothing that even hints that the condition is "A is None" rather than just A. A else B if condition else C else D Oh my aching head. Things which are different ought to look different, not look like what they aren't. A ?? B if condition ?? C else D The precedence is still unclear and will probably benefit from some parentheses, but at least here the None-aware operator and the ternary if operator look different and can't be confused. -- Steve

On 2018-07-20 10:52, Steven D'Aprano wrote:
Personally I consider this a problem with the whole null-coalescing idea. Although it's true that the proposed null-coalescing operators don't look like existing syntax, it's also true that there's nothing about them that suggests to the uninitiated that they have anything to do with comparing to None. In this situation I lean toward "explicit is better than implicit" --- if you want to compare against None, you should do so explicitly --- and "special cases aren't special enough to break the rules" --- that is, None is not special enough to warrant the creation of multiple new operators solely to compare things against this specific value. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

On 20Jul2018 1119, Brendan Barnwell wrote:
"The rules" declare that None is special - it's the one and only value that represents "no value". So is giving it special meaning here breaking the rules or following them? (See also the ~50% of the PEP dedicated to this subject, and also consider proposing a non-special result for "??? if has_no_value(value) else value" in the 'True' case.) Cheers, Steve

Rhodri James wrote:
If anyone can think of a good word for "if it isn't None, otherwise", I'd be all for it :-)
I don't think there's any single Engish word that captures all of that, so we'd have to invent one. Some suggestions: inno (If Not None, Otherwise) oft (Or, Failing That) -- Greg

Hi -- I'm a new voice here, though I've been lurking for a while now. How do people feel about removing "??=" and "foo?[bar]" from the PEP and sticking with just "foo?.bar" and "foo ?? bar"? Reasons for not implementing "??=": 1. I think the other operators are useful in that they can be chained together to shrink many different lines of code. In contrast, ??= can shrink at most one line of code: we get to remove a single 'if foo is None'. If the rest of the PEP is implemented, we can also just express this by doing "foo = foo ?? some_expr" which I think is still relatively concise. 2. None of the other short-circuiting operators have an augmented assignment form -- e.g. we can do "foo = foo or bar", but there's no such thing as "foo or= bar". I don't really see why ?? in particular deserves to have an augmented assignment form. 3. We already have two different kinds of assignment operators with the inclusion of PEP 572 -- in the interests of keeping Python as simple as possible, I think it'd be a good idea not to add a third one. Reasons for not implementing "foo?[bar]": 1. It seems like "foo?[bar]" could be easily confused with "foo??[bar]". I don't think it's immediately obvious to a newcomer which spelling corresponds to which usage, and getting the two mixed up seems like the sort of thing that could cause all sorts of subtle bugs. 2. We can already easily get the same functionality using standard Python. E.g., instead of doing foo?["bar"]?[0]?["baz"], we could do lookup(foo, "bar", 0, "baz") where lookup is a function that looks roughly like this: def lookup(item, *parts): for part in parts: if item is None: return None item = item[parts] return item Of course, we could probably create the same sort of function to replace foo?.bar (albeit less ergonomically), but unlike foo?[bar], there's no possibility for confusion: doing foo??.bar will never be a valid Python expression. 3. One counter-argument against removing foo?[bar] is that it would make expression that need both safe index and attribute lookups look weird -- you'd sometimes be using the "lookup" function described above (or something similar) and sometimes using ".?". However, I'd argue that these sorts of scenarios are relatively rare in practice, and that if you really need to deal with a bunch of code that requires you to use both forms of safe navigation, your code is likely already becoming pretty unreadable and should be rewritten -- maybe split up into multiple lines or something, or maybe just redesigned from scratch so you don't need to constantly manage a mess of Nones. More broadly, I think I agree with the sentiment some other people have that Python has acquired a lot of new features in a relatively short period of time, and that it would be nice to have some cooldown to let tooling and other implementations catch up. In that regard, I'd personally be happy if we didn't implement this PEP or just deferred it again. But if we *are* moving forward with it, I think it's worth trying to simplify it as much as possible/try and make it orthogonal with existing Python features. (But of course, I'm just some random person on the internet, so IDK if my opinion counts for much.) Regards, -- Michael On Thu, Jul 19, 2018 at 5:06 PM, Chris Angelico <rosuav@gmail.com> wrote:

On Thu, Jul 19, 2018 at 08:57:50PM -0400, Michael Selik wrote:
Consider the case that foo['bar'] is supposed to return either a collection (something that can be indexed) or None. But due to a bug, it returns a float 1.234. Now the try...except will *wrongly* catch the exception from 1.234[0] and return the default. The problem here is that the try...except block is NOT equivalent to the None-aware pattern: obj = foo['bar'] # avoid time-of-check-to-time-of-use bugs if obj is None: x = 'default' else: x = obj[0] except in the special case that we can guarantee that foo['bar'] can only ever return a collection or None and will never, ever be buggy. -- Steve

I think I am with Michael here. I like the parallel between `??` and `or`, we don't have `or=`, so `??=` is also not needed. Although I understand a parallel between `obj.attr` and `obj['attr']`, I think there is an additional point (in addition to two valid points by Michael) why I don't like `?[`: in many situations in my experience optional container attributes were results of suboptimal APIs, so I would not encourage this pattern. FWIW, I am rather -0 on adding all proposed operators, but I would be +1 on adding just the two essential ones: ?? and ?. -- Ivan On 20 July 2018 at 01:40, Michael Lee <michael.lee.0x2a@gmail.com> wrote:

On Fri, Jul 20, 2018 at 12:03:47PM +1200, Greg Ewing wrote:
How about pttpciseano? ("pity that the python community is so edgy about new operators") *wink* Remember that adding a new keyword risks breaking code that uses that keyword as a variable. Adding a new operator does not. -- Steve

On 7/19/2018 4:33 AM, Antoine Pitrou wrote:
I like this. (A or B) and (A and B) could now* be explained as an abbreviations of A if A else B A if not A else B but with A only evaluated once, as in tem = A; tem if tem else B tem = A; tem if not A else B (A if A else B) is equivalent to (A if bool(A) is not False else B) (A else B) is then easily explained as an abbreviation of A if A is not None else B that only evaluates A once. * In the future, tem if (tem := A) else B -- Terry Jan Reedy

On Thu, Jul 19, 2018 at 10:33:21AM +0200, Antoine Pitrou wrote:
I read that as "hi, if it is truthy, else len(a)". The advantage of ?? as None-aware operator is that it cannot possibly be misread as applying to arbitrary falsey objects: - for those familiar with the equivalent from other languages, the use of ?? to check for nil/null/None is familiar and obvious; - for those who aren't, the ?? syntax avoids leading them to guess the wrong behaviour, as "else" would do. P.S. I just had to delete 40+ screenfuls of irrelevant quoted text. Come on folks, please trim your posts! Don't blame your tools. -- Steve

Hi
Perhaps "argue" is not the right word here. It sounds too much for or against. And it has implications of being heated and angry. I think "discuss" might be a better word to use. This has associations of a disinterested pursuit of truth, and an openness to new ideas. The ability to see both sides of an issue is, I think, very important for reaching the compromises that have helped Python succeed, both technically and in creating a broad and harmonious community. That said, there is a passion in "argue" that seems not to be present in "discuss". -- Jonathan

Jonathan Fine wrote:
Perhaps "argue" is not the right word here. It sounds too much for or against. And it has implications of being heated and angry.
At least arguing about the nature of argument is a very Pythonic thing to do. "That's not arguing, it's just contradiction!" "No, it isn't!" -- Greg

On 18/07/18 18:43, Steve Dower wrote:
Many thanks for your work on this, Steve. Having just written a fair bit of code that would have benefited from None-coalescing, I'm +1 for "??" and "??=" and +0 for the rest.
I was a bit worried about the priority here, but your explanations elsewhere make sense. Perhaps the PEP would benefit from being a bit more explicit about this?
ITYM optdict = dict(encoding=options.encoding ?? sys.getdefaultencoding(), css=options.css)
Here's where I start to part company. To me, the updated version is markedly harder to read, even if it does (once deciphered) convey the intent of the function better than the original :-) The "?." and "?[]" operators just aren't obvious enough not to trip my internal WTF filter; either that or I'll overlook the "?" part entirely, which is probably worse.
While the four whole extra characters bothers me not one bit, the repetition is more of an issue. It's much worse if what you have is more like: item = spam.spam.spam.eggs if spam.spam.spam.eggs is not None else beans -- Rhodri James *-* Kynesim Ltd

On 19 July 2018 at 13:39, Rhodri James <rhodri@kynesim.co.uk> wrote:
I completely agree. The semantics of ?. and ?[] is non-intuitive at best - and particularly when chained. The Groovy language has the ?. operator (see http://groovy-lang.org/operators.html#_safe_navigation_operator), and I pretty much always read it as if it were the equivalent expression without the ? signs, with an added proviso "it'll probably do something useful if we hit None". But when it comes to what that "something useful" is, I'm left hoping the original writer knew what they were doing. So I'd never write code using ?. or ?[] myself, and I'd be unable to reasonably review or maintain code containing them. That's a pretty serious condemnation of the syntax in my view. Conversely, while I find ?= and ?? ugly, and would avoid them, the semantics are relatively easy to assess when reading code. Of the two, I dislike ?? more than ?=, but both are streets ahead of ?. and ?[]. Paul

On 18/07/18 19:43, Steve Dower wrote:
## NB I only skimmed most of this thread after reading the PEP, so I ## ## apologize if this has been discussed before and I missed it. ## I quite like the general idea, but I'm nervous about a rather fundamental aspect: This adds a special case in which you can't add parentheses to an expression involving a chain of operators to make precedence and evaluation order clear. To use your example, a ?? 2 ** b ?? 3 === (a ?? 2) ** (b ?? 3) # fine In the present day, a or 2 + 3 * c() === a or (2 + (3 * (c()))) a.b(c).d[e] === (((a.b)(c)).d)[e] # silly, but true. Short-circuiting doesn't break this. With and and or, the expression that's short-circuited away is a self-contained expression in imagined (or actual) parentheses. With this ?. operator, the chain a?.b(c).d?[e] can no longer be broken into sub-expressions, but becomes one single, long, atomic expression, just like a comparison chain. If I try: (a?.b)(c).d?[e] # TypeError: 'NoneType' object is not callable a?.(b(c).d?[e]) # SyntaxError, and illogical Also, where does this end? if a is None, is (a?.b,c()) equal to None or (None, c())? Presumably the latter because of operator precedence, but still. Is introducing the idea of an "attribute reference, call and subscription" chain worth it? This could be fixed by adding a maybe-call ?( operator, which would allow a?.b?(C())?.d?[E()] === ((((a?.b) ?( C() )) ?.d) ?[ E() ]) _v = a _v = _v.b if _v is not None else None _v = _v(C()) if _v is not None else None _v = _v.d if _v is not None else None _v = _v[E()] if _v is not None else None with None falling all the way through, and the calls to C() and E() being short-circuited out. Of course you need either attribute-call-subscription chains or ‘?()’ maybe-calls for ‘?.’ to be worthwhile at all, since otherwise you can't write None-aware method calls. Aside: other languages cited From a quick look at the C# docs linked in the PEP [1], I'm guessing that C# allows A?.B?.C?.Do(E), but does not allow A?.B?.C.Do(E). Does anybody know? Obviously method calls work differently in C# than in Python. Dart? I dunno. The docs aren't as thorough. Am I missing something? Cheers Thomas [1] https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/...

On 24 July 2018 at 08:38, Grégory Lielens <gregory.lielens@gmail.com> wrote:
Looks like there's a Compare node that takes a list of operators - and indeed the docs say (at https://docs.python.org/3.7/library/ast.html#abstract-grammar) Compare(expr left, cmpop* ops, expr* comparators) Paul

On Jul 18, 2018, at 10:43 AM, 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?).
It probably is the wrong time and probably can hurt (by introducing divisiveness when we most need for be focusing on coming together). This PEP also shares some traits with PEP 572 in that it solves a somewhat minor problem with new syntax and grammar changes that affect the look and feel of the language in a way that at least some of us (me for example) find to be repulsive. This PEP is one step further away from Python reading like executable pseudo-code. That trait is currently a major draw to the language and I don't think it should get tossed away just to mitigate a minor irritant. We should also consider a moratorium on language changes for while. There is more going on than just a transition to a post-bdfl world. The other implementations of Python are having a hard time keeping up with our recent, ferocious rate of change. Even among the core developers, most people are not fully up to date learning all the new features that have already been added (how many of you are competent with typing, data classes, generalized unpacking, concurrent futures, async, the scoping rules for exceptions and comprehensions, the hundreds of niggling changes in the past few releases, __init_subclass__, __set_name__, details of import logic, issues with SSL certificates, new collections ABCs, etc.?) We've been putting major changes in faster than anyone can keep up with them. We really need to take a breath. Raymond

On Thu, Jul 26, 2018 at 12:25 AM, Raymond Hettinger < raymond.hettinger@gmail.com> wrote:
+1. Also this whole none-aware problem is really really complicated, so I'd like to add a few thoughts: 1. I spent a few days on it last year, and came to the following conclusions: 2. It is a *really* useful feature -- that I want in quite a lot of code that I write. 3. The problem is way deeper than simply adding '?.' and other operators. For real use cases, you also need to say "how far" the an operator can "spread" -- and this is real hard to solve. 4. Coming up with a readable syntax that doesn't look like line noise is really hard; and very subjective. Based on all that, I have to agree -- now is not the time to try to resolve these issues, there are more important issues to resolve -- I'll write more on that tomorrow.

On Thu, Jul 26, 2018 at 01:02:47AM -0400, Amit Green wrote:
Why do you think that is the case? No other operators "spread". Why should these?
4. Coming up with a readable syntax that doesn't look like line noise is really hard; and very subjective.
Define "line noise". Is a.b.c syntax line noise? Is a**b**c syntax line noise? Is a == b == c line noise? -- Steve

On Thu, Jul 26, 2018 at 1:09 AM, Steven D'Aprano <steve@pearwood.info> wrote:
If you take 'a?.b.c' then if the first '?.' yields none, logically you don't want to evaluate the second '.'; otherwise you have to write: a?.b?.c -- And this starts to look like line noise. If you also implemented a concept such as use non-aware operators in function calls: such as: f(a, b?, c) -- which would mean collapse the whole thing out of non-existance if 'b' yields none (i.e.: don't call function f) -- how far does this spread? Outside of f, if you then write: f(a, b?, c).d.e Does this also mean don't evaluate the '.d' & '.e' ? And both of these examples are from *REAL* uses cases in my code I would want to use none-aware operators.
These examples, from python, all look great & are very readable. Which is why python is such a great language :) Simple non-aware operators are also very readable, such as: a?.b This starts to become unreadable & line noise: a?.b.c.d ?? "hi". Again, though it is subjective -- as is clearly evident by the discussions in this group. My simple point above is -- I really do want this feature -- but having tried for days, I can't come up with a really clean syntax to capture all of my *OWN* use cases ... let alone others... Anyway, as I said, I think we have more important issues to address right now, than this very difficult issue & I'll write up more tomorrow.

Hi All To start this thread, on 18 July, Steve Dower wrote:
On 26 July, Raymond Hettinger wrote:
It probably is the wrong time and probably can hurt (by introducing divisiveness when we most need for be focusing on coming together). [...]
+10 Here's some good news for Raymond. On 23 July, Steve Dower wrote: ---
and this is Steve's last post (so far) to the discussion. +10 If you've not seen this message from Steve before, you're forgiven. This topic has about 200 messages. Who's got time to read them all them all? If you want to have a go, they're listed by date at https://mail.python.org/pipermail/python-ideas/2018-July/thread.html#52036 Regarding discussion of PEP 505, I'll follow the advice and example of Raymond and Steve. Which is to pick it up again later, once we've got proper cover for the BDFL's vacation. Have a nice holiday, Guido. -- Jonathan

On 2018-07-18 18:43, Steve Dower wrote:
[snip]
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".
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:
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:
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.)
Thanks.
Correct, another typo. Cheers, Steve

On Wed, Jul 18, 2018 at 7:46 PM Steve Dower <steve.dower@python.org> wrote:
or these they the proposed that the preferred
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 -- Giampaolo - http://grodola.blogspot.com -- Giampaolo - http://grodola.blogspot.com

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 ]
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:
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. -- Giampaolo - http://grodola.blogspot.com

On Thu, Jul 19, 2018 at 4:06 PM, Greg Ewing <greg.ewing@canterbury.ac.nz> 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. ChrisA

On Thu, 19 Jul 2018 19:11:33 +1000 Chris Angelico <rosuav@gmail.com> wrote:
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:
^ 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. -- Giampaolo - http://grodola.blogspot.com

On Thu, Jul 19, 2018, 5:12 AM Chris Angelico <rosuav@gmail.com> wrote:
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:
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 |. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

On 2018-07-19 11:43, Elazar wrote:
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. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

Let me just address this point: 2018-07-19 20:36 GMT+02:00 Brendan Barnwell <brenbarn@brenbarn.net>:
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:
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:
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:
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. :-/
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:
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

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:
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 Thu, Jul 19, 2018 at 2:36 PM Brendan Barnwell <brenbarn@brenbarn.net> wrote:
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

Rhodri James wrote:
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. -- Greg

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 Fri, Jul 20, 2018 at 10:24 PM, Paul Moore <p.f.moore@gmail.com> wrote:
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:

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:

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:

On Wed, Jul 18, 2018 at 08:05:56PM -0400, David Mertz wrote:
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

"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 2018-07-19 06:38, Steven D'Aprano wrote:
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. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

On Thu, Jul 19, 2018 at 11:39:50AM -0700, Brendan Barnwell wrote:
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.) -- Steve

On Thu, Jul 19, 2018, 9:39 AM Steven D'Aprano <steve@pearwood.info> wrote:
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:
"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.
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:
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.
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: == != ~ - + * ** / // @ % ^ & | << >> < <= > >= [] () [...]
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.
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
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. -- Steve

On 22 July 2018 at 02:54, Steven D'Aprano <steve@pearwood.info> wrote:
Certainly *my* concerns about readability are around the other proposed operators (?[ and ?. in particular).
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:
[...] 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. -- Giampaolo - http://grodola.blogspot.com

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:
Yes, I think you're right. -- Giampaolo - http://grodola.blogspot.com

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:
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.
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. -- Steve

On Sun, Jul 22, 2018 at 10:10 PM, Steven D'Aprano <steve@pearwood.info> wrote:
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:
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:
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:
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... -- Steve

On 23Jul2018 0151, Steven D'Aprano wrote:
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:
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. -- Steve

On 2018-07-22 09:01:58 -0400, David Mertz wrote:
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 -- _ | Peter J. Holzer | we build much bigger, better disasters now |_|_) | | because we have much more sophisticated | | | hjp@hjp.at | management tools. __/ | http://www.hjp.at/ | -- Ross Anderson <https://www.edge.org/>

On Sun, Jul 22, 2018 at 2:10 PM Steven D'Aprano <steve@pearwood.info> wrote:
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. -- Giampaolo - http://grodola.blogspot.com

On Sun, Jul 22, 2018 at 11:35 PM, Giampaolo Rodola' <g.rodola@gmail.com> wrote:
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:
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. -- Giampaolo - http://grodola.blogspot.com

On Mon, Jul 23, 2018 at 1:09 AM, Giampaolo Rodola' <g.rodola@gmail.com> wrote:
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:
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. -- Giampaolo - http://grodola.blogspot.com

On Mon, Jul 23, 2018 at 6:43 AM, Giampaolo Rodola' <g.rodola@gmail.com> wrote:
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 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:
"Crazy" is just a personal judgement. I do think high (and even very high) demands are entirely justified by the language's maturity.
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:
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.
Ditto. -- Giampaolo - http://grodola.blogspot.com

On Sun, Jul 22, 2018 at 11:51 PM Giampaolo Rodola' <g.rodola@gmail.com> wrote:
s/I'm going/I'm not going -- Giampaolo - http://grodola.blogspot.com

On Mon, Jul 23, 2018 at 12:08 AM Chris Angelico <rosuav@gmail.com> 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. [1] https://mail.python.org/pipermail/python-ideas/2018-July/052068.html -- Giampaolo - http://grodola.blogspot.com

On Mon, Jul 23, 2018 at 12:59:20AM +0200, Giampaolo Rodola' wrote:
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. -- Steve

On Mon, Jul 23, 2018 at 2:38 AM Steven D'Aprano <steve@pearwood.info> wrote:
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
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:
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:
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:
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 :
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:
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:
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 23 July 2018 at 12:39, Grégory Lielens <gregory.lielens@gmail.com> 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. 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:
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: 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. -- C Anthony

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.

On Mon, Jul 23, 2018 at 12:09:00PM +0100, Steve Dower wrote:
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. -- Steve

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

On Mon, Jul 23, 2018 at 5:52 AM Steve Dower <steve.dower@python.org> wrote:
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
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. -- 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 23Jul2018 1530, David Mertz wrote:
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:
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 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.
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:
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.
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. -- Giampaolo - http://grodola.blogspot.com

On Mon, Jul 23, 2018 at 02:04:17PM +0200, Giampaolo Rodola' wrote: [I wrote this]
[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 isn't a first. Many existing operators use two adjacent symbols not interrupted by a space: e.g. == <= >= != ** // << >> += -= *= etc.
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.
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. -- Steve

On Mon, Jul 23, 2018 at 6:53 PM Steven D'Aprano <steve@pearwood.info> wrote:
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.
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))
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.
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'). -- Giampaolo - http://grodola.blogspot.com

On 2018-07-23 23:05, Giampaolo Rodola' wrote:
It's no more implicit than 'or' checking for falseness.
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:
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'. -- Giampaolo - http://grodola.blogspot.com

On Tue, Jul 24, 2018 at 12:05:14AM +0200, Giampaolo Rodola' wrote:
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.
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 [...]
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.
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.
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? -- Steve

On Tue, Jul 24, 2018 at 11:50 AM Steven D'Aprano <steve@pearwood.info> wrote:
You totally missed my point about explicitness. Nevermind.
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.
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.
Yes, I forgot 'a . b' was legal - my bad.
Again, you missed my point.
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:
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:
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

On Tuesday, July 24, 2018 at 1:38:42 PM UTC+2, Rhodri James wrote:
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.

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

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. -- Rhodri James *-* Kynesim Ltd

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?
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!!!"
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'.) -- Steve

On Tue, Jul 24, 2018, 7:38 AM Rhodri James <rhodri@kynesim.co.uk> wrote:
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:
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

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 Tue, Jul 24, 2018 at 11:02 PM, David Mertz <mertz@gnosis.cx> wrote:
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:
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:
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*
Why does Python have named variables if it is "abuse" to use them? -- Steve

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/).
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.
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. -- 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 11:02 AM, David Mertz <mertz@gnosis.cx> wrote:
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:
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:
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:
-- 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:14 PM, David Mertz <mertz@gnosis.cx> wrote:
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:
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
This is childishly simple:
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"
# The simple approximation: for iter in iters: for _ in iter: yield iter
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) -- 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:45 PM, David Mertz <mertz@gnosis.cx> wrote:
Except for the aforementioned "single lookup, single assignment" semantics. You can't achieve that with an if/else block.
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?
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:
-- 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:
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.
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.
That isn't 42. That's a thing that, forever afterwards, will be a proxy. And look at this:
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:
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.
Yeah. That's a thing it does. It's less of an issue than you think since, e.g.:
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.
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. -- 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 Wed, Jul 25, 2018 at 11:08 PM Chris Angelico <rosuav@gmail.com> wrote:
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. -- 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 1:19 PM, David Mertz <mertz@gnosis.cx> wrote:
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:
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:
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:
-- 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 2018-07-25 23:53, David Mertz wrote:
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 Wed, Jul 25, 2018 at 9:47 PM Nicholas Chammas <nicholas.chammas@gmail.com> wrote:
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?.eggs?.bacon # Should be: None
-- 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 Wed, Jul 25, 2018 at 10:12 PM David Mertz <mertz@gnosis.cx> 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? :) What misled me was this example from the PEP <https://www.python.org/dev/peps/pep-0505/#the-maybe-dot-and-maybe-subscript-...> 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:
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?!
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.

On Wed, Jul 25, 2018 at 11:09 PM David Mertz <mertz@gnosis.cx> wrote:
Forgive me for being slow. I'm missing what's different in semantics between the translation above and Chris's translation below: _tmp = spam
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' -- 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:17 AM David Mertz <mertz@gnosis.cx> wrote:
<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:
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. -- Steve

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 26/07/18 22:28, Abe Dillon wrote:
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.
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.
Really? Quick straw poll: how often do people care how in-place operators are implemented?
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.)
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 "?[]".
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.
NaN isn't used like None, though, and custom sentinels aren't that common.
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

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 Sun, Jul 29, 2018 at 6:07 AM, Abe Dillon <abedillon@gmail.com> wrote:
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.
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 1:56 PM, Abe Dillon <abedillon@gmail.com> wrote:
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]()
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. -- Steve

On Sun, Jul 29, 2018 at 3:54 PM, Steven D'Aprano <steve@pearwood.info> wrote:
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]
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]
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
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]
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.com> wrote:

On Sun, Jul 29, 2018 at 5:12 PM, Abe Dillon <abedillon@gmail.com> wrote:
The syntax of this proposal is almost objectively disgusting. It's truly horrid. I don't know how many ways to say it.
Almost. Except for the problem that "disgusting" is a subjective term. If you want to say that this is *objectively disgusting*, you're going to need some actual justification. If you just mean "I find this disgusting", then that's fine, but we already heard that argument. It's a statement of opinion, not of fact. ChrisA

Le 29/07/2018 à 09:12, Abe Dillon a écrit :
[...]
I don't believe we need spam?.eggs.cheese?.aardvark, because I don't think it is justified by the small benefits it gets us. For the same reason, I don't believe we need spam.eggs.cheese.aardvark? (there is exactly the same number of use cases). We win a bit in readability as it's closer to most spoken languages, but we lose in granularity as we're hiding any error that would raise if spam.eggs returned None, so it's not a full win on this side either...

On Sun, Jul 29, 2018 at 02:12:22AM -0500, Abe Dillon wrote: [I said this]
Am I? Your exact words were: My point is that '.' is unobtrusive, so it's not *that* different from `person name' which isn't *that* different from `person's name`. so you absolutely are claiming that dot access "isn't *that* different" from the possessive in English. The dot might be small, but the last thing we want if for the dot to be unobtrusive. We want it to stand out and be obvious, so we can never mistake 1.5 for 15 or (say) "appbuild" for "app.build". (The first is a variable called "appbuild", the second is a build method or attribute invoked on a variable called "app". Very different things indeed. We really don't want to mistake one for the other.) This is one important reason that most programmers use monospaced fonts for code, so that the dot takes up as much space and any other character and stands out more. The ?. syntax is obviously different from regular dot, but surely that is an *advantage* not a disadvantage? Things which do something different should look different. One of the problems with David Mertz's counter-proposal for a magic None-aware proxy is that we can't tell the difference between spam.eggs and spam.eggs where one of them is magic and the other is not. So we have a nice, clear, obvious difference in syntax so that at a glance the reader can instantly distinguish between dot and maybe-dot, and you are complaining that it is too clear, obvious and easily distinguishable compared to a regular dot.
That's exactly what you proposed, you just didn't realise it. See below. Your syntax is foo.spam? where the dot and the question mark together make a new binary operator. They aren't really delimiters like the parentheses in func( ... ) or the square brackets in seq[ ... ] but two halves of a single operator (or operator like construct) with an infix part (the dot) and a postfix part (the question mark). They certainly can't be two separate operators, a dot and a question mark. I don't know how the Python parser itself will deal with this, but consider the syntax from the perspective of a reader. You suggest:
I'm proposing:
spam.eggs.cheese.aardvark?
Given that, we read the code as: spam normal dot, so get the attribute eggs normal dot, so get the attribute cheese normal dot, so get the attribute aardvark question mark, rewind to the beginning and start again: spam maybe-dot, so maybe get eggs, or None maybe-dot, so maybe get cheese, or None maybe-dot, so maybe get aardvark I'm not saying that's what the parser will have to do (although I suspect it will need some sort of look-ahead, or backtracking, to parse this) but that's certainly how I'll read this.
A single POSTFIX operator that has a high priority in the order of operations.
I don't think that will do what you think it will do. If ? is a normal unary postfix operator then its operand here: spam.eggs.cheese.aardvark? will be (spam.eggs.cheese.aardvark), which defeats the purpose of having a maybe-dot operator. By the time the ? operator is called, the regular dot operators will have already been called. It may be possible to get the syntax you prefer, but readability-wise, I think it is magical and confusing to have a postfix symbol at the end of an expression *retroactively* change the meaning of symbols occuring before it in the expression. I expect it will also be a bug-magnet when we need to mix regular dot and maybe-dot in the same expression: spam.eggs?.cheese # eggs might be None, but spam won't be would need to be written as (spam.eggs).cheese? but who is going to remember those parentheses? Instead I expect people will consistently write: spam.eggs.cheese? thinking the ? applies only to *one* dot, not all of them, and wrongly suppress exceptions when spam is None (which ought to be treated as a bug). -- Steve

On Sun, Jul 29, 2018, 7:46 AM Steven D'Aprano <steve@pearwood.info> wrote:
That's exactly the strongest advantage in what I suggest! The special domain where you want attribute access (or dictionary lookup) to be None coalescing is something marked and different from most of what you want in general attribute access. But once you're in that special world of "semi-structured hierarchical data with leaves marked with None" you don't want to be reminded in every operator. It's just what you do with that kind of object. Naming variable might be a good way to remind readers of the domain. Or comments in the source code. Or putting the special behavior in an isolated function. But effectively, this is a domain you want to enter for a while, then return to the "real world" after some operations are done. For a pretty good analogy, you cannot tell the difference between 'a+b' and 'a+b' easily either, in isolation. But one of those is arithmetic over the domain of integers, while the other is arithmetic over the domain of a Z(65537) modular ring. Many operations are the same in both domains, but I'd really hate to need a special operator to perform modular arithmetic rather than integer arithmetic. E.g. 'a + b' vs 'a ⊕ b'. It is MUCH more elegant and clear in both examples to put the work on the *object* that has special behavior, not on the operator. I can see that if you did a very special kind of programming where you frequently mixed modular and integer arithmetic, you might want the reminder. But that's a rare need, and the language itself should not grow that syntax (perhaps something specialized like Sage or Octave might want a different balance, very reasonably). "Semi-structured hierarchical data with leafs marked with None" is just altogether too special to impose the burden all Python programmers. A None-aware proxy isn't the best possible thing for that special use by any means. But it's pretty good without imposing any burden on the large majority who will never need or use it. Another similar case is the '@' operator. This is exactly a special domain where an operator indicates special behavior. In principle, you could make objects that did anything whatsoever when they see that, but the name .__matmul__ is pretty suggestive of recommended use. However, nothing in the builtins or standard library define any behavior at all. You can happily program with Python your whole life and never think about this operator. The only folks who will see it are folks who use NumPy and *also* do linear algebra with it. By definition, the objects if this domain are already special, as it should be. Well, footnote. I think I've seen some other libraries that create a DSL around email that use '@' also. That also feels fine. It's a very different, but equally special, domain where the objects themselves define the special behavior. If PEP 505 were proposing a regular new protocol for .__none_attr__() or some better dunder name, I would oppose it far less. Specifically, if the behavior of this dunder operator were left to library authors who created such special objects, the danger and bug-magneticism would be much less. I'm well aware that dunders can't get you shortcutting, but modulo that, such a proposal would be relatively innocuous (I'd still be -0, but not really care).

[Steve D'Aprano]
so you absolutely are claiming that dot access "isn't *that* different" from the possessive in English.
Only in the context that I gave: `person.name`. In other contexts (e.g. thing.is_metal) the possessive analogy is obviously inaccurate, but I didn't make the broad claim that '.' means possessive. [Steve D'Aprano]
Unobtrusive doesn't mean invisible. *ob·tru·sive adjective: *noticeable or prominent *in an unwelcome or
intrusive way*.
I don't think '.' is invisible, nor that it should be. It's fairly elegant because it communicates information without breaking the flow of reading. It's as noticeable as it needs to be without getting in the way. [Steve D'Aprano]
They can be two separate operators. The '?' operator would say, "evaluate the preceding expression as though it was in a try-except block and if a NoneType error is thrown, ignore it and evaluate to None." I imagine it would work similarly to how ternary operators work: <expr_1> if <condition_expr> else <expr_2> where neither expr_1 nor expr_2 are evaluated before <condition_expr>. [Steve D'Aprano]
look-ahead and back-tracing is how people read punctuation like '?' and '!' in natural language, and there are plenty of examples of Python following this pattern in other expressionized syntax like in comprehensions and ternary expressions. I actually think this makes sense for expressionized syntax because it puts the "meat" before the "potatoes". When you're reading code like: initials = [person.name[0] ... You can usually guess what goes in the `...` from context so it's redundant, so it makes sense that it comes later. I made the argument a while back that this same pattern should have been followed for lambda expressions for the same reason: the argument list is almost always redundant: hand = sorted(cards, key=(<expression_based_on_individual_card> with <parameters>)) Similarly, If I want to guard against None errors, that's something that can come after the fact because obviously I'm not trying to access attributes or index into None objects: initial = person.name[0]? [Steve D'Aprano]
1) That's the way it works in natural language 2) That's the way it should work in the given context because it's largely a redundant check (from the reader's perspective) because you obviously don't want to access or index into a None object. That can be inferred from context by most humans, but needs to be explicit for the interpreter's sake. 3) there's plenty of precedent for behavior in which the order of execution isn't left to right. [Steve D'Aprano]
It may very well be a bug magnet. I'm not sure how often that usage will come up. I don't find anything all that objectionable about the second form. At least it looks like Python. As you've pointed out many times before: people would have to learn it to use it correctly. There's no way around that. I don't know why you'd all of a sudden abandon that line of reasoning. I believe the want for this functionality is symptomatic of a want for more general functionality like deferred expression evaluation or an expressionized try-except. I would rather try to crack those nuts than implement a overly specific solution that would become cruft later on when deffered evaluation or expressionized try-except are implemented.

On Sun, Jul 29, 2018 at 12:08:46PM -0500, Abe Dillon wrote:
look-ahead and back-tracing is how people read punctuation like '?' and '!' in natural language
I really wish you would stop talking about "natural language" as if there were only one (and it were English). Many natural languages don't even have punctuation like '?', or if they do, they are a very recent innovation. Spanish uses a bracketing pair of marks: ¿Qué Mr Fawlty? while many other European languages make do with just one at the end. And not all of them use the same symbol. (Greek uses the semi-colon, which must make reading C-like languages a remarkable experience.) But even in English, that isn't how people read questions in general, which is why we can still comprehend punctuation-less sentences what's for dinner where are you who was the German professor in Lord Palmerston's quote about the Schleswig-Holstein business as questions, despite the lack of a question mark. Questions really only need the question mark if they would otherwise be ambiguously either a statement or a question. You may be able to find examples of English questions which do require back-tracking, but that is generally considered to be a bad thing, increasing surprise (not a good thing unless you are attempting humour) and reducing the speed and ease of comprehension. See my earlier link about garden path sentences. In other words, reducing not increasing readability.
But not binary operators which take only two arguments, in this case a name and an attribute name.
Can you? In that example I can't even guess if that's the start of a list comprehension or a list display. If its a comprehension, I sure as hell can't guess the important question of what it being iterated over.
so it's redundant, so it makes sense that it comes later.
If it's redundant, why don't we just leave it out? -- Steve

[Steven D'Aprano]
really wish you would stop talking about "natural language" as if there were only one (and it were English).
I'm fine with that. I've just had someone jump down my throat before about being overly English-centric when talking about readability. [Steven D'Aprano]
even in English, that isn't how people read questions in general, which is why we can still comprehend punctuation-less sentences
Yes because you can make use of other context clues. So if I wrote: def f(person=None): initial = person.name[0]? ... You can anticipate the '?' at the end of the expression. Otherwise I would be clearly missing an important edge case. None checking is often done in very close proximity to some code that might yield a None, so this should rarely be a surprise unless the coder is writing the equivalent of a garden-path sentence. [Steven D'Aprano]
You may be able to find examples of English questions which do require back-tracking
It's easier, I'll admit, to find example's of exclamations that require back-tracking, but it's not unheard of! [Steven D'Aprano]
Again, my proposal was never for a binary operator. [Steven D'Aprano]
Maybe I should have fleshed out my example a little more. Usually there are plenty of context clues such that the latter part of a comprehension is more a formality for the computer's sake because computers, unlike humans, can't deal with ambiguity very well. So when you see: def func(people): initials = [person.name[0] ... You can usually guess that, since `person` isn't a local variable, we're probably dealing with a generator so there's probably a `for` clause down the line where `person` is declared and it probably iterates over, I don't know, how about `people` and maybe there's a filter clause too. The point is that the most important information, the "meat" of the expression is up front. In the same vein, In the spirit of EAFP, it's better to get the meat of your business logic up front and do all the tedious-yet-necessary edge-case checking later just like: def func(person=None): initial = person.name[0] ? ... The business logic comes first and the edge-case checking follows.
so it's redundant, so it makes sense that it comes later. If it's redundant, why don't we just leave it out?
Because redundancy from the perspective of the reader doesn't imply redundancy from the perspective of the computer. People can make sense of ambiguous language in a way that computers cant. The sentence "time flies like an arrow" has over 11 valid interpretations, yet few humans would think "oh, you're commanding me to use a stop watch to measure some behavior of common flying insects in the same way that I would use the same instrument to measure the behavior of an arrow!". If I type: def func(people): initials = [person.name[0] for person in people return Counter(initals).most_common(1)[0] You, as a human, can probably guess with extremely high certainty that I meant to put a ']' at the end of the second line. In that sense, it's redundant to you, but not to the computer which doesn't know how to handle that situation. Does that make sense? On Sun, Jul 29, 2018 at 7:48 PM, Steven D'Aprano <steve@pearwood.info> wrote:

Typo: *You can usually guess that, since `person` isn't a local variable, we're probably dealing with a generator *comprehension* On Sun, Jul 29, 2018 at 11:00 PM, Abe Dillon <abedillon@gmail.com> wrote:

On Sun, Jul 29, 2018, 2:00 AM Steven D'Aprano <steve@pearwood.info> wrote:
This is where being wrong matters. The experience in this thread of most supporters failing to get the semantics right shows that this isn't an extra day to learn. It's something that experienced programmers would continue to get won't in edge cases after years of use. The proposed operators don't match how most experienced developers think about code. Being glaringly ugly as written, and using an unintuitive or counterintuitive symbol alternates that, of course. I can hardly imagine a stronger bug magnet than PEP 505.

On Sun, Jul 29, 2018 at 8:32 PM, David Mertz <mertz@gnosis.cx> wrote:
I can hardly imagine a stronger bug magnet than PEP 505.
The hyperbole in this thread is impressive. Not just "I can hardly imagine anyone PROPOSING a stronger bug magnet". You cannot imagine one even existing. And this is after people have proposed that "if x = 1:" become the spelling for assignment expressions - something so well known as a bug magnet that C compilers specifically warn against it, and style guides recommend always writing "if (1 == x)" instead. Claiming that "?." is *the worst possible bug magnet* rather weakens your credibility here. ChrisA

[Chris Angelico]
Almost. Except for the problem that "disgusting" is a subjective term.
Yes. Thank you for explaining the joke. [Brice Parent]
First, I mostly proposed that alternative to serve my argument that there may be a better syntax that PEP 505 would make impossible down the road. It's a half-baked proposal at best and I agree with your over-all sentiment that it's probably not something we should consider either. That being said; in the normal usage of '?.' and '?[]' it would make little sense to only check the beginning of the expression: spam?.eggs.cheese.aardvark # why would you ever do this? It makes some sense to do this: spam.eggs.cheese?.aardvark But in my proposal, you could force any part of the expression to evaluate before the '?' with parentheses and expose whatever errors that might throw: (spam.eggs.cheese).aardvark? On Sun, Jul 29, 2018 at 7:15 AM, Chris Angelico <rosuav@gmail.com> wrote:

On 29/07/18 16:12, Abe Dillon wrote:
spam?.eggs.cheese.aardvark # why would you ever do this?
If you knew that if you really have something in "spam", your program guarantees it will have an "eggs" attribute with a "cheese" attribute, etc, you just don't know if "spam" is not None. It's the same insider knowledge you would use if you wrote it out as spam.eggs.cheese.aardvark if spam is not None else None The same sort of knowledge of your program structure could lead to you to use "?." instead of "." in any place or places in the chain. If your program gives you strong enough guarantees, it is the sort of thing you can use to reduce the code's workload. By way of example, I was working on some C code last night that was expanding an internal buffer, possibly from non-existence. It did lot of taking the difference between various pointers to determine how big the new buffer needed to be, how much to copy from the old buffer, etc. It looked rather hairy until you realised that the code gave a strong guarantee that either all the pointers were meaningful or they were all NULL, so the pointer differences always made sense. I rewrote that code so that it didn't take differences of NULL pointers since that was what the bug specified, but honestly it looks lumpier and less clear now. A big comment explaining what was going on would probably be better. -- Rhodri James *-* Kynesim Ltd

[Rhodri James]
No. The fact that other languages use it doesn't mean that "it isn't unintuitive". The fact that other languages use it doesn't mean that it doesn't harm readability. The fact that other languages use it has pretty much no bearing on any of that. What it actually means is: other languages are trying it out so we have the luxury of seeing if they regret it in the future or if it becomes a popular and well loved feature before integrating it into Python. I believe it's counter-intuitive because '?' always comes at the end of an english sentence, so it reads like other clause-ending operators like the ")" on a function call or the "]" at the end of a index operation, then the "." reads like a follow-on. And no; you can't say the same thing about "." always coming at the end of a sentence because: a) that's not true (e.g. i.e, e.g., etc.) b) "." attribute access has many decades of precedence c) a "." is less visually obtrusive than a "?". [Rhodri James]
Pardon? It composes exactly like any other in-place operator.
Yes. I'll concede. I was wrong about that. [Rhodri James]
yes, you do need to explain how merely being high precedence helps here. Let's start small; please explain programmatically what
name?
does as an expression. Once we have that down, we can move on to person.(name?) Sure. First, I would suggest we define a new class NoneException(BaseException), and derive new Exception multi-classes like: class NoneAttributeError(AttributeError, NoneException). Then I would make those the exceptions thrown by attribute errors on the None object. That way you can conceptualize the '?' operator is to the try-except statement as the ternary operator is to if-else statements EXCEPT that the "except" part of the try-except block is implicitly filled in with "except NoneException: return None" so in general: <expression>? Would parse to: try: result = eval(expression) except NoneException: result = None Similar to how: <expression_1> if <condition_expression> else <expression_2> Parses to: if eval(condition_expression): result = eval(expression_1) else: result = eval(expression_2) More specifically: name? Wouldn't do much: try: result = name except NoneException: result = None And: person.(name?) would throw a SyntaxError because person.( is not valid syntax regardless of my proposal. Perhaps you meant something like: (person.name).first? In that case; person.name would evaluate to some result or throw an un-guarded AttributeError, in the former case the <expression> to the left of the '?' is an attribute access on the result of (person.name). In the latter case; an AttributeError is thrown. That seems pretty straight forward. No? On 29/07/18 16:12, Abe Dillon wrote:
spam?.eggs.cheese.aardvark # why would you ever do this?
spam.eggs.cheese.aardvark if spam is not None else None
That's not the equivalent as described in PEP 505. If "spam" is None, "spam?.eggs" evaluates to None without throwing an AttributeError, but the rest of the expression will throw an AttributeError from trying to access "None.cheese". On Mon, Jul 30, 2018 at 9:10 AM, Rhodri James <rhodri@kynesim.co.uk> wrote:

On Mon, Jul 30, 2018 at 12:41:20PM -0500, Abe Dillon wrote: [Rhodri James]
Yes it is. Rhodri is correct, although I admit that I hadn't realised this point myself until he pointed it out. (That is why until now I've been writing examples like "spam?.eggs?.cheese?.aardvark" which is redundant.) The abstract says: The "None-aware attribute access" operator ?. ("maybe dot") evaluates the complete expression if the left hand side evaluates to a value that is not None and later on the PEP says: 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 I admit the example isn't the clearest. The inclusion of "await" doesn't seem helpful to me (if anything, the opposite, since "await None" will fail) and perhaps it would be clearer to write it as: _v = a if _v is not None: _v = _v.b(c).d if _v is not None: _v = _v[e] The PEP also says: Parenthesised expressions are handled by the atom rule (not shown above), which will implicitly terminate the short- circuiting behaviour of the above transformation. which again isn't too clear, and the example given seems complicated enough to obfuscate the point rather than illustrate it, but if I'm reading it correctly, the behaviour you expected would have to be written as (spam?.eggs).cheese.aardvark which would likely fail since None has no cheese attribute. The bottom line is that in any unparenthesized chain of maybe-dot and maybe-subscript operations, only the first needs to be "maybe" as that will short-circuit the rest of the expression. Figuratively speaking, it is as if we had: spam?(.eggs.cheese.aardvark) where the () are used for grouping, not function call. -- Steve

On Tue, Jul 31, 2018, 1:47 PM Steven D'Aprano <steve@pearwood.info> wrote:
Again, one of the most vocal advocates of the PEP gets the semantics wrong! `spam?.eggs?.cheese?.aardvark` is NOT redundant for ` spam?.eggs.cheese.aardvark`. The two expressions simply do different things, but in a way guaranteed to assure that also everyone gets the actual behaviors wrong. Hint, run this before each expression: spam.eggs.cheese = None

David Mertz wrote:
I agree, assuming ?. is a binary operator. Given this, in Python (+ PEP 505) one can write tmp = spam ?. eggs val1 = tmp ?. cheese ?. aardvark # For spam?.eggs?.cheese?.aardvark val2 = tmp . cheese . aardvark # For spam?.eggs.cheese.aardvark No special knowledge of PEP 505 is needed. If val1 is always equal to val2, then the dot and None-dot operators must be the same. From the assumptions, this is something that can be mathematically proved. By the way, there's a widely used programming language in which val = a.method() and tmp = a.method val = tmp() are not always equivalent. Can you guess which language it is? The answer is in: https://www.slideshare.net/jonathanfine/javascript-the-easiest-quiz-in-the-w... (question 6: Dot binds). I'll now go back to following the example of Steve Bower and Raymond Hettinger, which in my words is to wait until we have proper cover for the BDFL's vacation. -- Jonathan

Op di 31 jul. 2018 20:49 schreef Jonathan Fine <jfine2358@gmail.com>:
It isn't. Given this, in Python (+
Nope, the introduction of the tmp variable changed the semantics. It isn't a "chain" anymore so it breaks shortcutting. To be honest I didn't get this either until it was pointed out to me
And false.
Javascript. I suppose in the same way as x+2 and x*2 are " not always" equivalent. Stephan

Stephan Houben wrote:
Nope, the introduction of the tmp variable changed the semantics. It isn't a "chain" anymore so it breaks shortcutting.
I'm confused. Assume 'a' is not defined. With Python's dot (attribute access) we have
I'd expect, after PEP 505 is added to Python that we'd get
If this is not correct, please some do tell me what we would get. Now assume 'a' is defined. I'd also expect, for any value for 'a', that
If this is not so, then can the second val be computed from tmp? And if so, how? -- Jonathan

On 2018-07-31 20:53, Jonathan Fine wrote:
Correct.
Also correct. On the first point, Steven said that _figuratively speaking_ (his words) the series of attribute accesses would be _as though_ it was grouped a?.(b.c), so if a is None, then remainder of the attributes accessed would be short-circuited. He _wasn't_ suggesting that that was _actual_ syntax. On the second point, you'd said: tmp = spam ?. eggs val2 = tmp . cheese . aardvark # For spam?.eggs.cheese.aardvark i.e. that: spam?.eggs.cheese.aardvark could be rewritten as: tmp = spam ?. eggs val2 = tmp . cheese . aardvark Steven pointed out that that was wrong. In the first, if spam is None, then the remainder is short-circuited, and so the result of spam?.eggs.cheese.aardvark is None. In the second, if spam is None, then tmp would be None, and tmp.cheese would fail.

Hi All I have two further questions. I'm keen to clarify what is the behaviour specified by PEP 505. I'm not, at this time, interested in why and how PEP 505 specifies behaviour. I just wish, through explicit examples, to clarify the behaviour that is specified. Here 'a' is an identifier. Consider the following 12 expressions (I use the word loosely). 1) a . b . c 2) (a . b) . c 3) a . (b . c) 4) a ?. b . c 5) (a ?. b) . c 6) a ?. (b . c) 7) a . b ?. c 8) (a . b) ?. c 9) a . (b ?. c) 10) a ?. b ?. c 11) (a .? b) ?. c 12) a ?. (b ?. c) Question A: Which of the expressions are NOT valid (Python + PEP 505) syntax? Question B: Which of the valid (Python + PEP 505) expressions are equivalent, for all possible values of 'a'. The answer depends, of course, on the exact text of PEP 505. I've not read PEP 505 that closely. My expectations, based on my Python experience, are that PEP 505 would be written so that: Answer A: Expressions 3, 6, 9 and 12 are invalid. The others are valid. Answer B: 1 and 2 are equivalent. Similarly 4 and 5. Similarly 7 and 8. Similarly 10 and 11. There are no other equivalences (for all values of 'a'). I would like to know if my answers to my questions are correct, and if not please may I be given correct answers. Thank you in advance. -- Jonathan

On Wed, Aug 1, 2018 at 5:29 PM, Jonathan Fine <jfine2358@gmail.com> wrote:
Correct. After a dot (whether ?. or plain . ), you need a NAME, not any form of expression (test, expr, etc).
Incorrect. The short-circuiting behaviour ends at any sort of grouping. It's like how "a < b < c" is not equivalent to "(a < b) < c", nor to "a < (b < c)". ChrisA

Hi Chris You wrote:
Oh. The equivalent ones are #1 and #2, and #7 and #8, where this proposal doesn't change anything. Otherwise, they're not equivalent.
Are you sure. I'd also expect #10 and #11 to be equivalent. By the way, there's a typo in my examples: 11) (a .? b) ?. c -- Jonathan

On Wed, Aug 1, 2018 at 6:00 PM, Jonathan Fine <jfine2358@gmail.com> wrote:
Hmm.
10) a ?. b ?. c 11) (a ?. b) ?. c
I would parse those differently, but you may be right that they'll always have the same final result. Technically they should result in different code, though. ChrisA

Hi Chris Thank you for your reply. I think we're making good progress. You wrote
I'd like to get some certainty on this. I'm not aware of any value of 'a' for which #10 and #11 give different values. Can you (or anyone else) think of any such value?
Technically they should result in different code, though.
Maybe. We need to think. Should can be a difficult word. Elsewhere you have, as I recall, pointed out that if None: do_something() generates no code. Perhaps the compiler should collapse #11 to #10, if they are equivalent. But this is a side issue. So, are there any values of 'a' for which #10 and #11 don't give the same result? -- Jonathan

Hi Chris We're discussing.
10) a ?. b ?. c 11) (a ?. b) ?. c
I asked
So, are there any values of 'a' for which #10 and #11 don't give the same result?
You replied
I am willing to put my neck out and say a.b.c and (a.b).c are equivalent. And my understanding for PEP 505 is that #10 and #11 is that they are equivalent. You're right to be cautious. My understanding of PEP 505 is that #13. a ?. b ?. __str__ #14. (a ?. b) ?. __str__ are not equivalent. The first is None, and the other is None.__str__. That looks like a gotcha. (By the way, it was not my intention to catch you out. I'm simply looking for clarity. I wasn't aware of the gotcha until I started answering myself the question I had asked you.) However, the None object is somewhat special, in that all it's methods and double-under (dunder) methods. And one of them is __bool__. And we can't add or change the attributes of None. Chris, you don't have to reply to this. But I would be pleased if an expert could either tell me that my neck is safe, or produce a value of 'a' that cuts it off (so to speak). -- Jonathan

Op wo 1 aug. 2018 10:50 schreef Chris Angelico <rosuav@gmail.com>:
Let me stand up and claim that if a chain consists *only* of None-coalescing operations, then breaking up the chain by adding parentheses does not matter, ever. So a?.b?.c is equivalent to (a?.b)?.c What is
your point here?
It is useful to establish rules under which a chain can be factored. Stephan

Hi Stephan You wrote
You missed a post I made, 17 minutes before yours. I then believed that PEP 505 specified. #1. (None ?. dne) is None #2. (None ?. dne ?. __str__) is None #3. (None.__str__) is not None #4. ((None ?. dne) ?. __str__) is (None.__str__) After my last post, you wrote
None.?__str__ produces None, even though None has a __str__ attribute.
This surprises me. But I think it follows from the rules in https://www.python.org/dev/peps/pep-0505/#the-maybe-dot-and-maybe-subscript-... My initial reading of
In other words, the same as
getattr(a, name, None)
But you and I agree, I think, that PEP 505 specifies (in the cited page fragment)
The abstract to PEP 505 writes === The "None-aware attribute access" operator ?. ("maybe dot") evaluates the complete expression if the left hand side evaluates to a value that is not None === This is, of course, informal prose. I'm now reading it carefully. Here's a specific example.
I don't see how to apply the prose in the abstract to this last example. The left hand side is not None, so we "evaluate the complete expression". On one reading, this is a recursion. I'd very much appreciate some help here. -- Jonathan

On Wed, Aug 01, 2018 at 11:03:05AM +0100, Jonathan Fine wrote: [...]
Correct. None?.__str__ is equivalent to: _v = None if _v is not None: _v = _v.__str__ return _v (except no actual _v variable is created, and it is not literally a return statement).
No. PEP 505 is for None-aware operators. It spends considerable time explaining that it treats None as special. It does not catch AttributeError from missing attributes. (45)?.upper() will fail with AttributeError, same as (45).upper() does.
The phrasing could be clearer. Since 42 is not None, it evaluates (42).str which of course will fail with AttributeError. To be specific, it is equivalent to something like this pseudo-code: _v = 42 if _v is not None: _v = _v.str return _v (The same disclaimer about _v and return apply as earlier.) -- Steve

Hi Steve Thank you for your reply. We're discussing the abstract to PEP 505, which writes === The "None-aware attribute access" operator ?. ("maybe dot") evaluates the complete expression if the left hand side evaluates to a value that is not None === I gave (42).str as an example. I wrote
You wrote
The phrasing could be clearer.
I think the phrasing could be considerably improved (see below).
Since 42 is not None, it evaluates (42).str [...]
Based on this hint, here's what I think is a better statement. === Let `lhs` be the value of the left hand side, and RHS the code fragment on the right hand side. If `lhs` is None, then the whole expression is `None`. Otherwise, `lhs . RHS` gives the value of the whole expression. === Please would the experts tell me: Is it true? And if true, is it better? And can it be improved? -- Jonathan

On Thu, Aug 2, 2018 at 12:04 AM, Jonathan Fine <jfine2358@gmail.com> wrote:
It may be true, but it isn't better IMO - especially not for the abstract. It's unnecessarily pedantic. The current wording isn't ambiguous, because infinite recursion makes no sense. MAYBE change it to "evaluate the rest of the expression", but I don't see a problem with the current wording. ChrisA

Chris Angelico wrote:
Thank you for this. Please take a look and compare https://www.python.org/dev/peps/pep-0505/#abstract https://www.python.org/dev/peps/pep-0572/#abstract I'd like to get an abstract for PEP 572 that is as concise and clear as that for PEP 505. My previous comment focused just on 'the sharp edge that cut me'. But the more I look at PEP 572, the more I see sharp edges (in the expression of the ideas in the PEP). Having said that, I hope now to return to lurking, until we have cover for the BDFL vacation. -- Jonathan

On Tue, Jul 31, 2018 at 02:14:19PM -0400, David Mertz wrote:
It is if *only* spam can be None, which is the scenario being discussed. Please don't ignore the context of statements. I didn't say that spam?.eggs.cheese.aardvark would guard against eggs or cheese being None. That interpretation of my comment is uncharitable and not supported by my re-write of the pseudo-code from the PEP. It also goes completely against my comment "Figuratively speaking..." that the ?. operator groups to the right: spam?(.eggs.cheese.aardvark) # grouping, not function call. Perhaps you didn't read my post all the way to the end before firing off a triumphant post accusing me of getting it wrong. In context, the question is whether: spam?.eggs.cheese.aardvark might attempt to evaluate None.cheese.aardvark. It won't. In context, the issue was not whether these two expressions are identical in every imaginable way: spam?.eggs.cheese.aardvark spam?.eggs?.cheese?.aardvark In pseudo-code, they would be something close to: if spam is not None: return spam.eggs.cheese.aardvark versus if spam is not None: if spam.eggs is not None: if spam.eggs.cheese is not None: return spam.eggs.cheese.aardvark except without needing to evaluate each subexpression multiple times. In context, we are discussing the behaviour when spam is None. When spam is None, there's no need for the additional maybe-dots, because the first short-circuits the rest. If additional attributes also could be None, then they too could be guarded with maybe-dot operators, but if they are never None, it is unnecessary and redundant to guard spam alone all the way through the expression with maybe-dots.
Putting aside your prejudicial language about guaranteeing errors, I think that the PEP could be improved. I managed to read it multiple times without taking in the fact that ?. short-circuits to the right. I didn't explicitly say so (perhaps I should have) but I too initially made the same error as Abe, assuming that spam?.eggs.cheese would fail if spam was None (evaluating None.cheese) but according to the PEP that's not the case. With a working implementation, it would be the work of moments for people to experiment in the REPL and clarify any lingering doubts about the operation. Without it, there will always be some misunderstandings from those who (like me) failed to read the documentation carefully enough and made upjustified assumptions. That's no different from any other non-trivial feature. -- Steve

On Sun, Jul 29, 2018 at 06:32:19AM -0400, David Mertz wrote:
The difficulty one or two people had in coming up with a correct equivalent to none-aware operators on the spur of the moment is simply not relevant. Aside from whichever developers implements the feature, the rest of us merely *use* it, just as we already use import, comprehensions, yield from, operators, class inheritence, and other features which are exceedingly difficult to emulate precisely in pure Python code. Even something as simple as the regular dot attribute lookup is difficult to emulate precisely. I doubt most people would be able to write a pure-Python version of getattr correctly the first time. Or even the fifth. I know I wouldn't. I'm sure that you are fully aware that if this proposal is accepted, people will not need to reinvent the wheel by emulating these none-aware operators in pure Python, so your repeated argument that (allegedly) even the supporters can't implement it correctly is pure FUD. They won't have to implement it, that's the point. -- Steve

Abe Dillon wrote:
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. -- Greg

On Sun, Jul 29, 2018 at 12:49:13PM +1200, Greg Ewing wrote:
You're talking like the syntax is used only by a handful of experimental languages with a total user-base measured in the dozens. ?. is used by some of the most commonly used languages in the world, such as C++, Objective C and PHP, as well as up-and-coming "cool" languages getting lots of industry buzz, like Swift and Dart. Its certainly more familiar now than Python's slicing syntax was when Python first started. -- Steve

Le 29/07/2018 à 08:02, Steven D'Aprano a écrit : the only point (alos, I'm convinced that if we were making a poll with average and advanced users of these languages about what these syntax were doing, a big proportion wouldn't know). I believe the evolution should be done if the benefice are big enough (which I doubt, but I don't use Python in all the ways possible), not because others do it (whatever their reasons were). Then, when we know there's definitely a need to solve a problem, the solution should be chosen (??, but anything else could enter the discussion). Here, we're mixing arguments about (and I confess I've done it too): - how usefull it would be to have a solution to this problem - if it should be solved by Python's syntax or by libraries (if it may be done, and if it may not, how spread are the use cases that can't be done this way) - if other languages support something like that, if some are, how well this was accepted and if it's getting used in new code - the proposed syntax itself

On 28/07/18 21:07, Abe Dillon wrote: [>> = Me]
But it does and did mean that we should consider it when coming up with Python's version of the ternary operator. *It isn't unintuitive* and neither is ?? None-aware operators may be bad for other reasons, but that argument doesn't wash.
(This was about ??=, for context.) Pardon? It composes exactly like any other in-place operator. [Abe previously wrote:]
Apology elsewhere accepted, but yes, you do need to explain how merely being high precedence helps here. Let's start small; please explain programmatically what name? does as an expression. Once we have that down, we can move on to person.(name?) (parentheses for emphasis.) Short of actual magic, I can't see how this is going to work. -- Rhodri James *-* Kynesim Ltd

On Mon, Jul 23, 2018 at 6:05 PM Giampaolo Rodola' <g.rodola@gmail.com> wrote:
I think both of those are equally explicit. It's just that one notation is more concise than the other. Explicitness and conciseness are related but different things. When something is "explicit", as I understand it, that means it does what it says on the cover. There is no unstated behavior. The plain meaning of `v = a?.b` is that it expands to the longer form (`v = a; if a.b ...`), and it is just as explicit. This reminds me of something I read about once called Stroustrup's Rule <https://thefeedbackloop.xyz/stroustrups-rule-and-layering-over-time/> [1]:
For new features, people insist on LOUD explicit syntax. For established features, people want terse notation.
I think the "explicit vs. implicit" part of this discussion is probably better expressed as a discussion about "loud vs. terse" syntax. None of the operators in PEP 505 have implicit behavior, to the best of my understanding. It's just that the operators are new and have terse spellings. As a point of comparison, I think a good example of implicit behavior is type coercion. When you ask Python to add an int to a float a = 3 + 4.5 all that you've explicitly asked for is the addition. However, Python implicitly converts the 3 from an int to a float as part of the operation. The type conversion isn't anywhere "on the cover" of the + operator. It's implicit behavior. [1] Bjarne Stroustrup makes the observation in this talk <https://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Keynote> at 23:00.

On Wed, Jul 25, 2018 at 12:12:40PM -0400, Nicholas Chammas wrote:
Right. We don't insist on writing mydict.look_up_key_in_self_and_raise_keyerror_if_the_key_is_not_found(key="spam") instead of mydict["spam"] out of a mistaken idea that explicitness requires verbosity and that punctuation is always implicit. Symbols (whether made of text or punctuation) have meaning, and that meaning has to be taken into account. You *can't* spell everything out in full. We always have to take the meaning of something as given, and that can be punctuation just as easily as words.
That is a great observation! Thanks. -- Steve

The problem here is not whether it's explicit. It's about Readability and conciseness. Using symbols in place of words almost always harms readability in favor of conciseness. value = person.name if person.name else person almost reads like english (aside from being a weird and totally uncommon use case) value = person?.name Is a huge step towards the concise illegible soup of symbols that Perl is famous for. It's a huge No from me. On Wed, Jul 25, 2018 at 11:12 AM, Nicholas Chammas < nicholas.chammas@gmail.com> wrote:

On Wed, Jul 25, 2018 at 3:11 PM Abe Dillon <abedillon@gmail.com> wrote:
Value is name of person almost reads like english. value = person.name Starts to look like pearl (but does not avoid repetition; only hurts english-like-readability) - or perhaps some other programming languages that use similar operators, such as C#, Swift, Dart, F#, Kotlin and others. As far as I know it is not generally considered a bad addition in any of these languages, all of which put emphasis on readability. Elazar

On Wed, Jul 25, 2018 at 6:11 PM Abe Dillon <abedillon@gmail.com> wrote:
The two statements you wrote are not the same. The first statement will error out if person is None. The proposed None-aware operators are specifically designed to handle variables that may be None. The first statement should instead read: value = person.name if person is not None else person That's what `value = person?.name` means. As others have pointed out, I suppose the fact that multiple people have messed up the meaning of the proposed operators is concerning. Perhaps the PEP could be improved by adding some dead simple examples of each operator and an equivalent statement that doesn't use the operator, to better illustrate their meaning. But I gather that will do little in the way of addressing some of the stronger objections raised here.

The two statements you wrote are not the same. The first statement will error out if person is None.
That's my bad. I was copying off of an erroneous example. Thanks for correcting me. The proposed None-aware operators are specifically designed to handle
variables that may be None.
Yes, I think the syntax that you've landed on is confusing enough that it opens the door to more errors than it closes. Just reading "(a?.b ?? c).d?.e" and "await a?.b(c).d?[e]" On Wed, Jul 25, 2018 at 6:06 PM, Nicholas Chammas < nicholas.chammas@gmail.com> wrote:

*sorry, cat hit "send"... Just reading those examples made me want to cry and go hide in a dark dark cave and never come out. I'm sure using actual variable names would help a bit, but not much. As for the non-english nature of: value = person.name I highly disagree with the argument that since that is a step removed from natural language readability that: value = person?.name Should be considered fair game. Nor do I buy the "other languages do it" argument. Some of the syntax of Python is based on familiar patterns in other languages (like '.' access) some of it is based on common math (e.g. "=", "+", etc.) which is also taught in grade school. Some of the patterns borrowed from other languages were a mistake and considered cruft. Some of that cruft was scraped off in the 2to3 migration. Maybe "." should have been apostrophe "s" all along. Maybe lambda should have been 'make_function'. That's not what we're here to discuss. A lot of languages have a ternary operator (x ? y : z). Python wisely used words instead of symbols and now many students don't even have to crack a book to decipher Python's ternary operator. Adding crap to a language is easy. Removing it is damn near impossible. You have to have extremely good reasons to add new syntax and I don't have to defend any of Python's warts to justify rejecting yours. On Wed, Jul 25, 2018 at 6:15 PM, Abe Dillon <abedillon@gmail.com> wrote:

On Wed, Jul 25, 2018 at 05:11:08PM -0500, Abe Dillon wrote:
And that is why we prefer COBOL over unreadable Perl-like languages that use unreadable symbols like: arithmetic operators + - * / // % ** bitwise operators & | ^ >> << ~ comparison operators == != < <= >= > function call symbols func(arg) argument packing and unpacking *args, **kwargs sequence slicing seq[::] assignment = comments # decorator syntax @ What a horrible, unreadable language that would be. REMARK this is much better than Perl-like x = func(a, b, c) PUT THE RESULT OF CALLING FUNCTION func WITH ARGUMENTS a AND b AND c INTO x \s Why do you claim that symbols and conciseness is "almost always" worse for readability? Your assertion doesn't survive even a cursory consideration. Readability doesn't occur in a vacuum. You have to understand the symbols, and it doesn't matter whether they are punctuation or words, if you don't understand them, they're "unreadable": result = page_count + 1 # readable jwrtsv = xpwvabs_ue + 1 # what? result obqxayrhs page_count wmsna 1 # what? The first time I encounted Python 1.5, I found it unreadable: it was full of mysterious keywords "class", "def", strange punctuation like [1:] and x.y, and idioms I couldn't guess like for i in range(len(seq)) With no context, it might as well have been Ancient Sumarian. Do you know what helps readability? *Learning to read*. Once you have learned to read ?. and friends, they will be as readable as . and slicing is now. -- Steve

On Thu, Jul 26, 2018 at 5:10 AM, Steven D'Aprano <steve@pearwood.info> wrote:
[...]
No it is not like that. E.g. slicing is intuitive and straightforward concept even for a non-programmer. And no, it is not (only) about learning. For example, no matter how experienced a programmer is, complex comprehensions will be less readable than the same construct written in loops and ifs. Or say this: user?.profile?.food will be less readable than say: user » profile » food No matter how long you learn it, the former remains ugly and obfuscating. With that said, I don't say this syntax deserves something better, I just say that I can't see how your "learn more" argument applies here. Mikhail

On 25/07/18 23:11, Abe Dillon wrote:
ITYM value = person.name if person.name is not None else None
almost reads like english (aside from being a weird and totally uncommon use case)
I think Nicholas (?) was right to observe that it's uncommon to not want to know where a deeply nested attribute access failed. Exactly what you want to do when an underlying library (presumably) has "helpfully" turned non-existent attributes into Nones is going to vary tremendously from application to application. On the other hand, value = parameter ?? default encapsulates a very common usage, and doesn't read badly compared with: value = parameter if parameter is not None else default which is a tad wordy. That example isn't too bad, but replace "parameter" with "spam.spam.spam.eggs" and DRY suddenly becomes a rather more important principle :-) -- Rhodri James *-* Kynesim Ltd

The fact that a while bunch have people have commented on this subthread while not recognizing that the semantics of the '?.' and the if blocks are entirely different suggests the operators are but magnets. On Wed, Jul 25, 2018, 5:17 PM Nicholas Chammas <nicholas.chammas@gmail.com> wrote:

Can you explain? What do you mean by "the operators are but magnets"? The "None coalescing" operator seems so similar to the short circuit behavior of "or" that it has pretty much no merit. It's compared to ternary statements in the last section of the PEP (which is suspiciously lacking the "or" pattern). I would agree that Python could use some more support for EAFP <https://en.wikipedia.org/wiki/EAFP> style coding. An express-ionized version of try/catch might help there, but I'm pretty sure the search for an elegant solution to that has been relatively fruitless. The attribute access and indexing are just unreadable in my view. Maybe if the question mark came at the end of the expression it would be more readable and just mean, "if the preceding expression raises an attribute exception on a none-type object, ignore it and evaluate to None otherwise return the result of the evaluation" Then just use parentheses to capture the scope: initial = (person.name[0])? # handles if person is None or person.name is None but that still seems like a good way to end up with very ugly code. Haskel's Maybe seems like a much better and more readable approach. On Wed, Jul 25, 2018 at 5:36 PM, David Mertz <mertz@gnosis.cx> wrote:

Sorry. From my tablet. "Bug magnets" (it really, really wants to autocorrect that) And yes, the problem is that the equivalent is actually: v = a if v is not None: v=a.b The semantics are simply not the ones that are intuitive to most people reading 'v = a?.b' On Wed, Jul 25, 2018, 7:01 PM Abe Dillon <abedillon@gmail.com> wrote:

On Wed, Jul 25, 2018 at 07:06:35PM -0400, David Mertz wrote:
Tell us more about these "intuitive to most people" semantics. What are they? Did you do a survey? How many people did you ask? Did you do a comparison to the "intuitive to most people" semantics of slicing syntax? Intuitive does not mean "familiar". If you're going to dismiss a proposal because it's not "intuitive"[1] then to avoid accusations of intellectual hypocracy you need to do one of two things: - demonstrate that the syntactic features we know and love and use frequently (e.g. slicing, decorators, comprehensions) are intuitively obvious and don't need to be learned; - or say that *those features were terrible errors* that Python still has not recovered from. You can't have it both ways: its okay that people have to learn slicing, decorators, comprehensions, never mind that they aren't intuitive, for *those* features "intuitiveness" isn't that important; but for this proposal, "intuitiveness" is all the matters: usefulness, conciseness and the increase in expressivity don't matter one whit. Its one thing to say that slicing, dot attribute access, decorators, augmented assignment, comprehensions etc are mistakes we should not repeat. But this double-standard of demanding higher standards for new features than we accept in existing, beloved features is one that we should reject with extreme prejudice. [1] To whom? Babies? First-time programmers? People with a Ph.D. in comp sci and thirty years experience in half a dozen languages? -- Steve

Just wanted to note another possibility (most likely to be added to the "rejected solutions" section). Add a prefix "!" operator, which will work similarly to the iterator-unpacking operator "*", but for None. The expression "[!x]" is equivalent to the expression "[] if x is None else [x]". This can be combined with the "or" operator, now working as expected since it's about lists, and a simple "first" or "single" library function. Example: optdict = dict(encoding=single([!encoding] or sys.getdefaultencoding()), css=options.css) It is more verbose than the proposed "??" operator (which I personally like), but maybe it's less line noise. One can use it with simple parens and get a tuple, "(!x) or y". note that "(!x or y)" is a syntax error in this case. Another, related idea: instead of "??" use a double-token operator "! or": value = ham! or spam! or eggs optdict = dict(encoding=encoding! or sys.getdefaultencoding(), css=options.css) Elazar

The curious thing about PEP 505 as far as I can see is that it introduces a new piece of syntax -- and for many people (to judge from the reactions here) a controversial piece of syntax -- to solve what seems to be a rather specific problem. The use-case that seems most discussed is unpacking information from nested JSON. One of the things that makes me really twitch about the examples like food = ham?.eggs?.spam?.chips is that I don't usually deal with deeply nested structures like that where it wouldn't matter to me which level of the nest returned a value. But let's leave that aside, because it seems to be an issue people face. For the specific case mentioned, what would be wrong with a utility function that looked something like: # Warning - typed on gmail before I've had my morning coffee and so not # at all tested, but I hope you can see what I mean. def nested_conditional_find(search_this, *args): this = search_this for term in args: new_this = getattr(search_this, term) if new_this is None: return this else: this = new_this then you could use this function as: food = nested_conditional_find(ham, 'eggs', 'spam', 'chips') One could even imagine adding extra tests along the way to a function like that, raising attribute errors if the wrong kind of data was encountered etc. What problem does PEP 505 add that can't be solved very nearly as elegantly by a utility function? PEP 505 doesn't seem to be about speed, only convenience. For that matter, for specific use cases, why couldn't objects be created where the attribute access functioned in just this kind of way? (not example code I'm willing to attempt before coffee ;-) ) Just a thought. I've carefully avoided words like 'pythonic', 'unpythonic', 'explicit', 'implicit', 'compact', 'bug-magnet' or 'perl'. Best wishes, N P.S. As an aside, in examples like food = spam?.eggs?.bacon -- if bacon is None then food is None, even if there were in fact eggs and spam. That thought tells me it must be breakfast time.

On 25/07/18 23:36, David Mertz wrote:
*Entirely* different? Rubbish. It's more like the difference between "+" and attempting to render "+" as a function using dunder methods. I'm not keen on "?." and "?[]", but they are nothing like as bad as you are implying, and certainly not as bad as introducing a whole new concept like boxed types. -- Rhodri James *-* Kynesim Ltd

On 2018-07-23 13:04, Giampaolo Rodola' wrote:
[snip] I think you're misunderstanding something: we're not talking about a special operator "?" that somehow combines with existing operators, we're talking about completely new and separate operators "?.", "?[", etc., which resemble the existing ".", "[", etc.

On Sun, Jul 22, 2018 at 05:09:39PM +0200, Giampaolo Rodola' wrote:
Does this mean that instead of writing: result = obj.attr + 1 you prefer to be "explicit" and split it over multiple lines? # named functions are always better than punctuation from operator import add # explicit is better than punctuation value = getattr(obj, "attr") result = add(value, 1)
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. Do you think that Python is a worse language than it should have been because we use . for attribute access?
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'): ... etc. There is a HUGE amount of implicit complexity and work done behind the scenes in every import, and we're happy to keep it that way. -- Steve

On 2018-07-22 08:10, Steven D'Aprano wrote:
I like the ?? operator too; I like the short circuiting behavior a lot, and the semantics are simple. I guess I'd use the other operators fairly often, too, mostly in quick-n-dirty scripts. The one place where I miss them is when browsing through dictionaries that I get by querying a remote server and deserializing the resulting JSON. I foten have situation where the value I'm interested in is e.g. either in response[0]["addresses"]["workplace"]["email"], or in response["records"][0]["contactInfo"]["emails"][0], and any of these subrecords may be missing. Rewriting these using the ?[…] and ?. operators, I guess I would write something like this: tmp = response?.get("records") try: tmp = tmp?[0] except IndexError: tmp = None tmp = tmp?.get("contactInfo")?.get("emails") try: tmp = tmp?[0] except IndexError: tmp = None Is there a shorter way to write these with the "?[…]" and "?." operators? I guess the difficulty is that I need to swallow index and key errors, not just the type errors that come from indexing into None. For cases like the one above, I usually use something like nget(response, ["records"], [0], ["contactInfo"], ["emails"], [0]), where nget is defined as shown below (in this use case, the lack of short-circuiting isn't an issue): def nget(obj, *fields, default=None): for field in fields: if obj is None: return default if isinstance(field, str): obj = getattr(obj, field, None) elif isinstance(field, list): try: obj = obj.__getitem__(field[0]) except (TypeError, KeyError, IndexError): obj = None return obj class Test(): def __init__(self): self.x = [{"y": 42, "z": ["aBc", "def"]}, [1]] a = Test() print(nget(a, "x", [0], ["z"], [0], [1])) # B print(nget(a, "x", [0], ["y"])) # 42 print(nget(a, "z", [0], ["y"], default="not found")) # not found print(nget(a, "z", [57], ["y"], default="not found")) # not found It would probably not be hard to wrap this into a special object, to be able to write something like wrap(response)["records"][0]["contactInfo"]["emails"][0].unwrap(). "wrap" would change its argument into a proxy returning a special indexable variant of None on key errors, and that dictionary would also call "wrap" on the results of __getitem__. Something like this: class wrap(): SENTINEL = object() def __init__(self, obj): self.obj = obj def unwrap(self): return self.obj def __getitem__(self, key): try: return wrap(self.obj.__getitem__(key)) except (TypeError, AttributeError, KeyError): return wrap(None) a = [{"y": 42, "z": ["aBc", "def"]}, [1]] print(wrap(a)[0]["z"][0][1].unwrap()) I think that's more or less what pymaybe does, in fact. Cheers, Clément.

Steven D'Aprano writes:
Sure, if "expression or default" won't do. But in my code, I can't recall encountering a case where it wouldn't. That is, in the vast majority of my code, the point of using None as the "oops, try again" sentinel is not that it's a different *value* from a falsie of the expected type, it's that it's a different *type*. It has very few attributes, and so will most likely eventually raise if I forget the "or default" clause and a falsie escapes from the enclosing code, rather than silently doing an erroneous computation. Of course "in my code" is very anecdotal, and we do have testimony from many people that these cases are important to them. I'd still like to know, not how much code looks better with "expr ?? default" vs. spelling it out as a conditional statement or expression, but rather how much code *must* be expressed as "expr ?? default" (or "expr ?. attr ?? default", as Steve Dower reminds us) because the falsie of expected type is a useful value of expr (or expr.attr).
No, it somewhat obfuscates the DRY violation, but you've still used "temp" twice. (Consider "var if var is None else default".) I also don't agree that it's more verbose than the first version if "expression" is more complex than a variable reference, not to mention the possibility of side effects in expression. Steve

On 2018-07-22 10:33:23 -0700, Michael Selik wrote:
They are also words if you are not an English speaker. I don't speak Chinese, but "Pǔtonghuà" is certainly a word for me (although I wouldn't recognize 普通话 as that word).
I know a few people who don't know enough English to read English documentation. But AFAIK they didn't have a problem memorizing a few dozen keywords. Learning the semantics of a programming language is a much larger task than learning a few words, and having familiar keywords probably doesn't really help much (they still don't mean what they mean in English). Of course we do use the Latin alphabet, I don't know how somebody who had to learn the Latin alphabet specifically for programming would cope. It's probably like programming in APL. I guess I could learn PerlYuYan[1] to find out ;-). hp [1] https://metacpan.org/pod/Lingua::Sinica::PerlYuYan -- _ | Peter J. Holzer | we build much bigger, better disasters now |_|_) | | because we have much more sophisticated | | | hjp@hjp.at | management tools. __/ | http://www.hjp.at/ | -- Ross Anderson <https://www.edge.org/>

[Steve Dower <steve.dower@python.org>]
...
* The "``None``-aware attribute access" operator ``?.`` evaluates the
complete expression if the left hand side evaluates to a value that is not ``None``
And if the LHS does evaluate to `None` ...? I'll assume the result is also `None` then.
I got lost on the `for` here. The part following `in`: for name in getattr(base, "__abstractmethods__", ()): looks in `base` (regardless of whether `base` is `None`) for an attribute named "_abstractmethods__".. If such an attribute exists, the value of the attribute is returned (`None` or not). Else an AttributeError is swallowed and `()` is returned. It's hard to see how for name in base?.__abstractmethods__ ?? (): does the same. If `base` itself is `None`, I guess it returns `()`, or if `base` has an "_abstractmethods__" attribute then the value of that attribute is returned - unless its value is None, in which case `()` is again returned. But if `base` is not `None` and the attribute does not exist, doesn't this raise AttributeError? The later "Exception-aware operators" section seemed to explicitly reject the idea that `?.` and `?[]` would suppress AttributeError and/or TypeError. In short, the original getattr() didn't care at all whether `base` was `None`, or whether the value of its "__abstractmethods__" attribute was `None`, but cared a whole lot about whether that attribute exists. I just can't see how the updated code matches that in any of those respects. Ignoring that and pressing on, I suffer the same kind of confusions on the `if` part. What am I missing? For example, do these operators swallow exceptions after all?

I am rather fond of the idea of null-coalescing, at the very least, for mutable default values: def foo(a=None): a ??= [] ... but I worry about the code messes we will run into with some of the other options. Woe be unto anyone forced to understand the behavior of: thing?.attr?[key]?.subattr ?? 127 What if we added the Elvis operator "?:" for null coalescing and left the rest for future consideration On Wed, Jul 18, 2018 at 10:49 PM Tim Peters <tim.peters@gmail.com> wrote:

Hi, I think this breaks one of the most important aspect which makes Python more and more popular every year: its readability. Until now, you didn't need to know the language very well to have some understanding about what a script did. Some parts of it could be a bit complicated (like comprehensions, which we only allow in unit tests and small utilities where I work, exactly for this reason) or non-obvious (like the new := operator, but even if you don't get exactly what it does, it's not really cryptic either. It's still some-kind-of-assignment). We've been teaching for years that there is virtually no cost in adding a few simple lines of code from times to times, and that it has to be done to improve readability, while this proposal's goal seems to be the opposite (make the code more compact at the cost of readability), which complicates the understanding of the whole language to save a few `if x is None:`. To me, it goes the opposite of most of the first lines of the zen of Python. Where I work, we update most of our projects to use new Python versions almost immediately when a new one is out and the libs we use are compatible. This would probably change that; we would likely wait for a few years, the time for the feature to be used widely in other projects, and for us to have some real-life feedback to know what to do about it, like: are there some cases it really is justified? Does it make it harder/simpler to read/write python code for experts and non-experts? Should we add a rule to enforce its use, or to prevent it? The biggest drawback of this, is that (if I understand it well), it may be done quite easily without any change to the language: def first_set(*elements): # please don't mind the name of the function, it's not the purpose here """ Will return the first element that is not None """ for element in elements: if element is not None: return element raise AllNoneException() first_set(3, 5) # -> 3 first_set(None, 5) # -> 5 first_set(None, None, 8, 10) # -> 8 first_set(None, Car(model="sport")).buy() # calling Car(model="sport").buy() first_set(None, ["a", "b", "c"])[1] # -> "b" first_set(None, None) # -> exception is raised (note that such function could even accept a "rejected_values" kwarg, like `rejected_values=(None, [], "")`, just by replacing the `if` clause by `if element not in rejected_values:`) I might have missed some implications, or even not understood the PEP at all, though! - Brice

On Thu, Jul 19, 2018 at 5:45 PM, Brice Parent <contact@brice.xyz> wrote:
No it can't, for the same reason that the 'and' and 'or' operators can't be implemented cleanly as functions: it short-circuits. The right-hand operator _will not_ be evaluated unless the left is None. ChrisA

Honestly speaking, I don't want make Python syntax more complex. But when comparing to accepted PEP 572, I think this PEP is useful often enough. And PEP 505 doesn't break border between expression and statement unlike PEP 572. Especially, ?? and ??= seems useful very often. And `x ?? default` seems much more readable than `x if x is not None else default` or `default if x is None else x`. On the other hand, `?.` and `?[]` seems useful less often and more confusing. When looking `spam?.egg`, people can expect `getattr(spam, 'egg', None)` rather than `spam.egg if spam is not None else None`. Since `?.` and `?[]` can't avoid AttributeError, IndexError and KeyError, I think it's confusing and not useful enough compared with it's ugliness. So my current position is +1 for `??` and `??=`, but -1 for others. Regards, -- INADA Naoki <songofacandy@gmail.com>

Chris Angelico writes later in thread:
On Thu, Jul 19, 2018 at 9:55 AM, Giampaolo Rodola' <g.rodola@gmail.com> wrote:
I am 100% in sync with the reasoning, but -0 on the PEP (and only that high because the advocates are so passionate). To be honest, code transformations like this
to this
make me cringe. Exactly one of two things is true: 1. mimetypes.guess_type guarantees that the only falsie it will ever return is None, or 2. it doesn't. In case 1, "ctype or 'application/octet-stream'" ALSO does the right thing. In case 2, ONLY "ctype or 'application/octet-stream'" does the right thing, as few callers of BaseUploadObject.find_content_type will be prepared for "", (), [], or any variety of 0 as the return value. This example is extreme in that by the nature of application/octet- stream, any caller that can handle it at all will do a very reasonable thing if find_content_type barfs up a falsie that is not None. In many cases, of course, it would be better to (eventually) raise an exception when a falsie escapes the expression. But in this particular example, it's hard to imagine that is true: if the user's expectation is violated, they'll complain, and then you can go debug. In the meantime, the application doesn't crash and work gets done. The prevalence of these cringe-worthy examples in advocates' posts are why I'm basically - on the idea. So I would like to see various examples of code where 1. in the original code, treating a falsie that is not None the same way that None is treated is a detectable bug; and 2a. letting such falsies escape to raise exceptions somewhere else is a good idea (and how you could know that); or 2b. catching spurious falsies and handling them here is a better idea; or 2c. some falsies that are not None are legitimate. That's three kinds of examples. I suspect the 2b examples are going to be pretty "meh", because you're saving little reader cognition or code. More important are the 2a examples, because I suspect that most examples will fall into the same category as find_content_type: "or" is as good as "??", and that "??" is sometimes an accident waiting to happen due to falsie escapes (eg, when receiving objects from somebody else's code who might not understand the contract that the only falsie they should ever provide is None). Steve

On 7/19/2018 4:32 AM, Stephen J. Turnbull wrote:
It seems to me that the problem is returning None. Guess_type should have default='application/octet-stream' (or whatever *is* the default) in its signature. The premise of returning None as default is that users can follow with conditional code. If we now think that this is too much of a burden, we should stop returning None, at least by default.
or 3. it never returns anything other than a non-blank string unless the user so requests. -- Terry Jan Reedy

On Thu, Jul 19, 2018 at 2:19 PM Terry Reedy <tjreedy@udel.edu> wrote:
It seems to me that the problem is returning None.
If functions raised errors instead of returning None, we wouldn't have so much trouble. Much of the benefit of PEP-572 was handling Nones and similar sentinel values. Instead of providing better tools for handling Nones, I'd rather encourage folks to raise exceptions with a nice tool like PEP 463, exception-catching expressions. Re-reading Guido's explanation for rejecting PEP 463, I now understand the acceptance of PEP 572 better, as he also disagrees with the preference for EAFP over LBYL. For what it's worth, I agree with the folks that think ?. and ?[ create too much visual clutter and are too easy to overlook or misunderstand. The ??= and ?? operators will have spaces around them, so they're easier to read. However, their benefit is so minor, if any, that it's better to stick with the status quo. I'd rather write one of these patterns: # when x was assigned elsewhere if x is None: x = value # when x is typically None value if x is None else x # when x is typically not None x if x is not None else value # when I'm playing code golf x or value In many of these cases I'd rather write 4 lines of code. It reads how I'd speak the logic to another person, though out-loud I'd probably add a "then" and say "otherwise" instead of "else": if x is None: y = a else: y = b

Terry Reedy writes:
On 7/19/2018 4:32 AM, Stephen J. Turnbull wrote:
make me cringe. Exactly one of two things is true:
It seems to me that the problem is returning None.
Exactly, but this seems to indicate that the problem I'm talking about is the code. It's not; my issue is with the use of THIS code as an example justifying "??".
Note that in the case of an MUA, code might look like something like this: class BaseSaveObject(object): # NOTE NAME CHANGE! def find_content_type(self, filename): ctype, encoding = mimetypes.guess_type(filename) while ctype is None: ctype = input('Couldn't recognize MIME type. Ideas?") ctype = self.validate_mimetype(ctype) # returns None on bad return ctype So returning "application/octet-stream" is probably inappropriate if parsing the message header for Content-Type fails. I'm not sure why you say "burdensome". Using "x() or y()" *because* it short-circuits is idiomatic (although some deprecate it). The main point of "??" is that "or" is not good enough because it does the same thing with other falsies that it does with None. There are other tweaks (such as very high precedence), but that's the main thing. What I'm looking for is a set of examples where "or" won't do, and explanations of *why* that is true and why programmers *won't* abuse "??" to create less resilient code, to balance against the avalanche of examples where "??" leads to less resilient code, such as this case. Note that if you want to use "??" rather than "or", there's at least one other falsie that you want to *avoid* a default for, and then in cases like this one, the question is "why aren't you checking for it to prevent exceptions in a module written long long ago in a galaxy far far away?"
That would be nice, but that's off-topic. The point here is that Steve ripped a page from Tim's book and transformed a pile of real code. (This deserves a standing ovation IMO!) So he has to take the API as it is, warts and all. Steve

On Thu, Jul 19, 2018 at 05:32:48PM +0900, Stephen J. Turnbull wrote:
I don't think that's a good use of ?? and that method should either remain in the original form, or at most, be re-written using return ctype if ctypes is not None else 'application/octet-stream' (although I prefer the original form). There doesn't seem to be any advantage to ?? in this example except to transform a four-line imperative-style return statement to a one-line return statement, trading off vertical space for horizontal. But there's no improvement in expressiveness that I can see.
It does the right thing, but it doesn't look like it does the right thing. The ?? operator promises to only transform None, and nothing but None. To the reader, the `or` operator promises to transform any falsey value. Is `or` too broad and do too much? There's no way of knowing. Instead of treating `guess_type` as a black-box, the reader now has to dig-deep into its documentation and/or implementation to find out which falsey values it might return and whether or not they ought to be treated the same as None.
I doubt that guess_type (and hence find_content_type) will return 0 or [] etc as part of their documented interface (as opposed to a bug), but if they did, surely we ought to assume that the caller is prepared to handle its output. More likely is that it might return the empty string "". At face value I would expect that a better interface for guess_type would be to return the empty string rather than None. But perhaps there is a legitimate reason to treat the empty string as distinct from None, it should *not* be transformed. On perhaps the function is avoiding the "failure looks like success" anti-pattern. Who hasn't been bitten by writing something like this? chunk = text[text.find('spam')+4:] It looks okay at a glance: return the chunk of text immediately following "spam". But if "spam" isn't present at all, it returns the entire text minus the first three characters, which is probably not what you wanted. So there is a good argument to be made that guess_type ought to promise: - return None if it cannot guess the type; - otherwise return a non-empty string. (or that the empty string is itself a legitimate output that ought to be considered distinct from None and should *not* be transformed). In that case, `or` reads wrongly, because it either wrongly transforms "" into the default when it should not, or it suggests to the reader that it might. Either way, an explicit test for None -- whether we use "if ... is None" or the proposed ?? operator -- is better than a test for falsey.
I don't see that this is relevant. We already had this discussion, when the ternary if was introduced, and a million times when discussing why it is better to write "if spam is None" rather than "if not spam". In other words, we ought to be comparing the expressiveness of process(spam ?? something) versus: process(something if spam is None else spam) (and similar variations), not against process(spam if spam else something) process(spam or something) both of which do something different from the ?? operator. -- Steve

Just for fun, I decided to go through some recently written code by some genuine Python experts (without their permission...) to see what changes would be worth taking. So I went to the sources of our github bots. Honestly, I only found three places that were worth changing (though I'm now kind of leaning towards ?[] eating LookupError, since that seems much more useful when traversing the result of json.loads()...). I'm also not holding up the third one as the strongest example :)
From https://github.com/python/miss-islington/blob/master/miss_islington/status_c...:
async def check_status(event, gh, *args, **kwargs): if ( event.data["commit"].get("committer") and event.data["commit"]["committer"]["login"] == "miss-islington" ): sha = event.data["sha"] await check_ci_status_and_approval(gh, sha, leave_comment=True) After: async def check_status(event, gh, *args, **kwargs): if event.data["commit"].get("committer")?["login"] == "miss-islington": sha = event.data["sha"] await check_ci_status_and_approval(gh, sha, leave_comment=True)
From https://github.com/python/bedevere/blob/master/bedevere/__main__.py:
try: print('GH requests remaining:', gh.rate_limit.remaining) except AttributeError: pass Assuming you want to continue hiding the message when no value is available: if (remaining := gh.rate_limit?.remaining) is not None: print('GH requests remaining:', remaining) Assuming you want the message printed anyway: print(f'GH requests remaining: {gh.rate_limit?.remaining ?? "N/A"}')
From https://github.com/python/bedevere/blob/master/bedevere/news.py (this is the one I'm including for completeness, not because it's the most compelling example I've ever seen):
async def check_news(gh, pull_request, filenames=None): if not filenames: filenames = await util.filenames_for_PR(gh, pull_request) After: async def check_news(gh, pull_request, filenames=None): filenames ??= await util.filenames_for_PR(gh, pull_request) On 19Jul2018 2222, Steven D'Aprano wrote:
Agreed, though to make it a more favourable comparison I'd replace "spam" with "spam()?.eggs" and put it in a class/module definition where you don't want temporary names leaking ;) Cheers, Steve

This is adding a whole range of new operators without enough of a use case. It is also making code harder to read, as evaluation can stop at any of the "?*" operators. And it looks like noise (or like Perl 6, which is the same). There is a use case I sympathize with: the argument-is-None case. For that I would suggest a simpler form: "A else B" which would evaluate to A if A is not None, otherwise to B (parentheses may be mandatory). So e.g. one of the examples would read: def insort_right(a, x, lo=0, hi=None): # ... hi = hi else len(a) # ... Regards Antoine. On Wed, 18 Jul 2018 10:43:36 -0700 Steve Dower <steve.dower@python.org> wrote:

On 19/07/18 09:33, Antoine Pitrou wrote:
Much as I would like a keyword, "else" is the wrong one. It implies we are dealing with truthiness, which we aren't, and lays a subtle semantic trap as a consequence. If anyone can think of a good word for "if it isn't None, otherwise", I'd be all for it :-) -- Rhodri James *-* Kynesim Ltd

On Thu, Jul 19, 2018 at 8:47 AM Rhodri James <rhodri@kynesim.co.uk> wrote:
I think that it may look better with the order switched and the word unless, as in def insort_right(a, x, lo=0 hi=None): # ... hi = len(a) unless hi # ... Unfortunately, this does maybe feel more like checking for truthiness than non-Null value

FWIW, I like ?? It is short and distinctive. There is prior art in this spelling in c#. It requires no new keyword, nor does it give new meaning to an existing one. I understand why ?[ needs to be spelled using only a single ?, but I am afraid it will be used infrequently, and people will accidentally write a??[x] which is legal but different. I found the example code in the PEP using ?. and ?[ hard to read. ?? and ??= are compelling, though. One more question: what does this do? del x x ??= 42 Stephan Op do 19 jul. 2018 15:00 schreef Judah Levy <judah.j.levy@gmail.com>:

On 7/19/2018 8:46 AM, Rhodri James wrote:
'A else B' is about the truthiness of 'A is not None', just as 'A or B' is about the truthiness of 'bool(a) is not False'. See my response to Antoine. A else B' would abbreviate 'tem = A; tem if tem is not None else B'
and lays a subtle semantic trap as a consequence.
I think most anyone should be able to get that 'A else B' is similar to 'A or B' but must be different.

On Thu, Jul 19, 2018 at 02:47:11PM -0400, Terry Reedy wrote:
I think most anyone should be able to get that 'A else B' is similar to 'A or B' but must be different.
Knowing that `else` is different to `or` is not the problem. As you say, that's obvious. The problem is that `A else B` looks like it ought to be the same as "else B" in if...else statements and the ternary if operator. That is, "if the condition is false", and in this case there is nothing that even hints that the condition is "A is None" rather than just A. A else B if condition else C else D Oh my aching head. Things which are different ought to look different, not look like what they aren't. A ?? B if condition ?? C else D The precedence is still unclear and will probably benefit from some parentheses, but at least here the None-aware operator and the ternary if operator look different and can't be confused. -- Steve

On 2018-07-20 10:52, Steven D'Aprano wrote:
Personally I consider this a problem with the whole null-coalescing idea. Although it's true that the proposed null-coalescing operators don't look like existing syntax, it's also true that there's nothing about them that suggests to the uninitiated that they have anything to do with comparing to None. In this situation I lean toward "explicit is better than implicit" --- if you want to compare against None, you should do so explicitly --- and "special cases aren't special enough to break the rules" --- that is, None is not special enough to warrant the creation of multiple new operators solely to compare things against this specific value. -- Brendan Barnwell "Do not follow where the path may lead. Go, instead, where there is no path, and leave a trail." --author unknown

On 20Jul2018 1119, Brendan Barnwell wrote:
"The rules" declare that None is special - it's the one and only value that represents "no value". So is giving it special meaning here breaking the rules or following them? (See also the ~50% of the PEP dedicated to this subject, and also consider proposing a non-special result for "??? if has_no_value(value) else value" in the 'True' case.) Cheers, Steve

Rhodri James wrote:
If anyone can think of a good word for "if it isn't None, otherwise", I'd be all for it :-)
I don't think there's any single Engish word that captures all of that, so we'd have to invent one. Some suggestions: inno (If Not None, Otherwise) oft (Or, Failing That) -- Greg

Hi -- I'm a new voice here, though I've been lurking for a while now. How do people feel about removing "??=" and "foo?[bar]" from the PEP and sticking with just "foo?.bar" and "foo ?? bar"? Reasons for not implementing "??=": 1. I think the other operators are useful in that they can be chained together to shrink many different lines of code. In contrast, ??= can shrink at most one line of code: we get to remove a single 'if foo is None'. If the rest of the PEP is implemented, we can also just express this by doing "foo = foo ?? some_expr" which I think is still relatively concise. 2. None of the other short-circuiting operators have an augmented assignment form -- e.g. we can do "foo = foo or bar", but there's no such thing as "foo or= bar". I don't really see why ?? in particular deserves to have an augmented assignment form. 3. We already have two different kinds of assignment operators with the inclusion of PEP 572 -- in the interests of keeping Python as simple as possible, I think it'd be a good idea not to add a third one. Reasons for not implementing "foo?[bar]": 1. It seems like "foo?[bar]" could be easily confused with "foo??[bar]". I don't think it's immediately obvious to a newcomer which spelling corresponds to which usage, and getting the two mixed up seems like the sort of thing that could cause all sorts of subtle bugs. 2. We can already easily get the same functionality using standard Python. E.g., instead of doing foo?["bar"]?[0]?["baz"], we could do lookup(foo, "bar", 0, "baz") where lookup is a function that looks roughly like this: def lookup(item, *parts): for part in parts: if item is None: return None item = item[parts] return item Of course, we could probably create the same sort of function to replace foo?.bar (albeit less ergonomically), but unlike foo?[bar], there's no possibility for confusion: doing foo??.bar will never be a valid Python expression. 3. One counter-argument against removing foo?[bar] is that it would make expression that need both safe index and attribute lookups look weird -- you'd sometimes be using the "lookup" function described above (or something similar) and sometimes using ".?". However, I'd argue that these sorts of scenarios are relatively rare in practice, and that if you really need to deal with a bunch of code that requires you to use both forms of safe navigation, your code is likely already becoming pretty unreadable and should be rewritten -- maybe split up into multiple lines or something, or maybe just redesigned from scratch so you don't need to constantly manage a mess of Nones. More broadly, I think I agree with the sentiment some other people have that Python has acquired a lot of new features in a relatively short period of time, and that it would be nice to have some cooldown to let tooling and other implementations catch up. In that regard, I'd personally be happy if we didn't implement this PEP or just deferred it again. But if we *are* moving forward with it, I think it's worth trying to simplify it as much as possible/try and make it orthogonal with existing Python features. (But of course, I'm just some random person on the internet, so IDK if my opinion counts for much.) Regards, -- Michael On Thu, Jul 19, 2018 at 5:06 PM, Chris Angelico <rosuav@gmail.com> wrote:

On Thu, Jul 19, 2018 at 08:57:50PM -0400, Michael Selik wrote:
Consider the case that foo['bar'] is supposed to return either a collection (something that can be indexed) or None. But due to a bug, it returns a float 1.234. Now the try...except will *wrongly* catch the exception from 1.234[0] and return the default. The problem here is that the try...except block is NOT equivalent to the None-aware pattern: obj = foo['bar'] # avoid time-of-check-to-time-of-use bugs if obj is None: x = 'default' else: x = obj[0] except in the special case that we can guarantee that foo['bar'] can only ever return a collection or None and will never, ever be buggy. -- Steve

I think I am with Michael here. I like the parallel between `??` and `or`, we don't have `or=`, so `??=` is also not needed. Although I understand a parallel between `obj.attr` and `obj['attr']`, I think there is an additional point (in addition to two valid points by Michael) why I don't like `?[`: in many situations in my experience optional container attributes were results of suboptimal APIs, so I would not encourage this pattern. FWIW, I am rather -0 on adding all proposed operators, but I would be +1 on adding just the two essential ones: ?? and ?. -- Ivan On 20 July 2018 at 01:40, Michael Lee <michael.lee.0x2a@gmail.com> wrote:

On Fri, Jul 20, 2018 at 12:03:47PM +1200, Greg Ewing wrote:
How about pttpciseano? ("pity that the python community is so edgy about new operators") *wink* Remember that adding a new keyword risks breaking code that uses that keyword as a variable. Adding a new operator does not. -- Steve

On 7/19/2018 4:33 AM, Antoine Pitrou wrote:
I like this. (A or B) and (A and B) could now* be explained as an abbreviations of A if A else B A if not A else B but with A only evaluated once, as in tem = A; tem if tem else B tem = A; tem if not A else B (A if A else B) is equivalent to (A if bool(A) is not False else B) (A else B) is then easily explained as an abbreviation of A if A is not None else B that only evaluates A once. * In the future, tem if (tem := A) else B -- Terry Jan Reedy

On Thu, Jul 19, 2018 at 10:33:21AM +0200, Antoine Pitrou wrote:
I read that as "hi, if it is truthy, else len(a)". The advantage of ?? as None-aware operator is that it cannot possibly be misread as applying to arbitrary falsey objects: - for those familiar with the equivalent from other languages, the use of ?? to check for nil/null/None is familiar and obvious; - for those who aren't, the ?? syntax avoids leading them to guess the wrong behaviour, as "else" would do. P.S. I just had to delete 40+ screenfuls of irrelevant quoted text. Come on folks, please trim your posts! Don't blame your tools. -- Steve

Hi
Perhaps "argue" is not the right word here. It sounds too much for or against. And it has implications of being heated and angry. I think "discuss" might be a better word to use. This has associations of a disinterested pursuit of truth, and an openness to new ideas. The ability to see both sides of an issue is, I think, very important for reaching the compromises that have helped Python succeed, both technically and in creating a broad and harmonious community. That said, there is a passion in "argue" that seems not to be present in "discuss". -- Jonathan

Jonathan Fine wrote:
Perhaps "argue" is not the right word here. It sounds too much for or against. And it has implications of being heated and angry.
At least arguing about the nature of argument is a very Pythonic thing to do. "That's not arguing, it's just contradiction!" "No, it isn't!" -- Greg

On 18/07/18 18:43, Steve Dower wrote:
Many thanks for your work on this, Steve. Having just written a fair bit of code that would have benefited from None-coalescing, I'm +1 for "??" and "??=" and +0 for the rest.
I was a bit worried about the priority here, but your explanations elsewhere make sense. Perhaps the PEP would benefit from being a bit more explicit about this?
ITYM optdict = dict(encoding=options.encoding ?? sys.getdefaultencoding(), css=options.css)
Here's where I start to part company. To me, the updated version is markedly harder to read, even if it does (once deciphered) convey the intent of the function better than the original :-) The "?." and "?[]" operators just aren't obvious enough not to trip my internal WTF filter; either that or I'll overlook the "?" part entirely, which is probably worse.
While the four whole extra characters bothers me not one bit, the repetition is more of an issue. It's much worse if what you have is more like: item = spam.spam.spam.eggs if spam.spam.spam.eggs is not None else beans -- Rhodri James *-* Kynesim Ltd

On 19 July 2018 at 13:39, Rhodri James <rhodri@kynesim.co.uk> wrote:
I completely agree. The semantics of ?. and ?[] is non-intuitive at best - and particularly when chained. The Groovy language has the ?. operator (see http://groovy-lang.org/operators.html#_safe_navigation_operator), and I pretty much always read it as if it were the equivalent expression without the ? signs, with an added proviso "it'll probably do something useful if we hit None". But when it comes to what that "something useful" is, I'm left hoping the original writer knew what they were doing. So I'd never write code using ?. or ?[] myself, and I'd be unable to reasonably review or maintain code containing them. That's a pretty serious condemnation of the syntax in my view. Conversely, while I find ?= and ?? ugly, and would avoid them, the semantics are relatively easy to assess when reading code. Of the two, I dislike ?? more than ?=, but both are streets ahead of ?. and ?[]. Paul

On 18/07/18 19:43, Steve Dower wrote:
## NB I only skimmed most of this thread after reading the PEP, so I ## ## apologize if this has been discussed before and I missed it. ## I quite like the general idea, but I'm nervous about a rather fundamental aspect: This adds a special case in which you can't add parentheses to an expression involving a chain of operators to make precedence and evaluation order clear. To use your example, a ?? 2 ** b ?? 3 === (a ?? 2) ** (b ?? 3) # fine In the present day, a or 2 + 3 * c() === a or (2 + (3 * (c()))) a.b(c).d[e] === (((a.b)(c)).d)[e] # silly, but true. Short-circuiting doesn't break this. With and and or, the expression that's short-circuited away is a self-contained expression in imagined (or actual) parentheses. With this ?. operator, the chain a?.b(c).d?[e] can no longer be broken into sub-expressions, but becomes one single, long, atomic expression, just like a comparison chain. If I try: (a?.b)(c).d?[e] # TypeError: 'NoneType' object is not callable a?.(b(c).d?[e]) # SyntaxError, and illogical Also, where does this end? if a is None, is (a?.b,c()) equal to None or (None, c())? Presumably the latter because of operator precedence, but still. Is introducing the idea of an "attribute reference, call and subscription" chain worth it? This could be fixed by adding a maybe-call ?( operator, which would allow a?.b?(C())?.d?[E()] === ((((a?.b) ?( C() )) ?.d) ?[ E() ]) _v = a _v = _v.b if _v is not None else None _v = _v(C()) if _v is not None else None _v = _v.d if _v is not None else None _v = _v[E()] if _v is not None else None with None falling all the way through, and the calls to C() and E() being short-circuited out. Of course you need either attribute-call-subscription chains or ‘?()’ maybe-calls for ‘?.’ to be worthwhile at all, since otherwise you can't write None-aware method calls. Aside: other languages cited From a quick look at the C# docs linked in the PEP [1], I'm guessing that C# allows A?.B?.C?.Do(E), but does not allow A?.B?.C.Do(E). Does anybody know? Obviously method calls work differently in C# than in Python. Dart? I dunno. The docs aren't as thorough. Am I missing something? Cheers Thomas [1] https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/...

On 24 July 2018 at 08:38, Grégory Lielens <gregory.lielens@gmail.com> wrote:
Looks like there's a Compare node that takes a list of operators - and indeed the docs say (at https://docs.python.org/3.7/library/ast.html#abstract-grammar) Compare(expr left, cmpop* ops, expr* comparators) Paul

On Jul 18, 2018, at 10:43 AM, 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?).
It probably is the wrong time and probably can hurt (by introducing divisiveness when we most need for be focusing on coming together). This PEP also shares some traits with PEP 572 in that it solves a somewhat minor problem with new syntax and grammar changes that affect the look and feel of the language in a way that at least some of us (me for example) find to be repulsive. This PEP is one step further away from Python reading like executable pseudo-code. That trait is currently a major draw to the language and I don't think it should get tossed away just to mitigate a minor irritant. We should also consider a moratorium on language changes for while. There is more going on than just a transition to a post-bdfl world. The other implementations of Python are having a hard time keeping up with our recent, ferocious rate of change. Even among the core developers, most people are not fully up to date learning all the new features that have already been added (how many of you are competent with typing, data classes, generalized unpacking, concurrent futures, async, the scoping rules for exceptions and comprehensions, the hundreds of niggling changes in the past few releases, __init_subclass__, __set_name__, details of import logic, issues with SSL certificates, new collections ABCs, etc.?) We've been putting major changes in faster than anyone can keep up with them. We really need to take a breath. Raymond

On Thu, Jul 26, 2018 at 12:25 AM, Raymond Hettinger < raymond.hettinger@gmail.com> wrote:
+1. Also this whole none-aware problem is really really complicated, so I'd like to add a few thoughts: 1. I spent a few days on it last year, and came to the following conclusions: 2. It is a *really* useful feature -- that I want in quite a lot of code that I write. 3. The problem is way deeper than simply adding '?.' and other operators. For real use cases, you also need to say "how far" the an operator can "spread" -- and this is real hard to solve. 4. Coming up with a readable syntax that doesn't look like line noise is really hard; and very subjective. Based on all that, I have to agree -- now is not the time to try to resolve these issues, there are more important issues to resolve -- I'll write more on that tomorrow.

On Thu, Jul 26, 2018 at 01:02:47AM -0400, Amit Green wrote:
Why do you think that is the case? No other operators "spread". Why should these?
4. Coming up with a readable syntax that doesn't look like line noise is really hard; and very subjective.
Define "line noise". Is a.b.c syntax line noise? Is a**b**c syntax line noise? Is a == b == c line noise? -- Steve

On Thu, Jul 26, 2018 at 1:09 AM, Steven D'Aprano <steve@pearwood.info> wrote:
If you take 'a?.b.c' then if the first '?.' yields none, logically you don't want to evaluate the second '.'; otherwise you have to write: a?.b?.c -- And this starts to look like line noise. If you also implemented a concept such as use non-aware operators in function calls: such as: f(a, b?, c) -- which would mean collapse the whole thing out of non-existance if 'b' yields none (i.e.: don't call function f) -- how far does this spread? Outside of f, if you then write: f(a, b?, c).d.e Does this also mean don't evaluate the '.d' & '.e' ? And both of these examples are from *REAL* uses cases in my code I would want to use none-aware operators.
These examples, from python, all look great & are very readable. Which is why python is such a great language :) Simple non-aware operators are also very readable, such as: a?.b This starts to become unreadable & line noise: a?.b.c.d ?? "hi". Again, though it is subjective -- as is clearly evident by the discussions in this group. My simple point above is -- I really do want this feature -- but having tried for days, I can't come up with a really clean syntax to capture all of my *OWN* use cases ... let alone others... Anyway, as I said, I think we have more important issues to address right now, than this very difficult issue & I'll write up more tomorrow.

Hi All To start this thread, on 18 July, Steve Dower wrote:
On 26 July, Raymond Hettinger wrote:
It probably is the wrong time and probably can hurt (by introducing divisiveness when we most need for be focusing on coming together). [...]
+10 Here's some good news for Raymond. On 23 July, Steve Dower wrote: ---
and this is Steve's last post (so far) to the discussion. +10 If you've not seen this message from Steve before, you're forgiven. This topic has about 200 messages. Who's got time to read them all them all? If you want to have a go, they're listed by date at https://mail.python.org/pipermail/python-ideas/2018-July/thread.html#52036 Regarding discussion of PEP 505, I'll follow the advice and example of Raymond and Steve. Which is to pick it up again later, once we've got proper cover for the BDFL's vacation. Have a nice holiday, Guido. -- Jonathan
participants (44)
-
Abe Dillon
-
Amit Green
-
Andre Roberge
-
Antoine Pitrou
-
Antoine Pitrou
-
Brendan Barnwell
-
Brett Cannon
-
Brice Parent
-
C Anthony Risinger
-
Chris Angelico
-
Chris Barker - NOAA Federal
-
Clément Pit-Claudel
-
David Mertz
-
David Mertz
-
Elazar
-
George Leslie-Waksman
-
Giampaolo Rodola'
-
Greg Ewing
-
Grégory Lielens
-
INADA Naoki
-
Ivan Levkivskyi
-
Jonathan Fine
-
Judah Levy
-
Kyle Lahnakoski
-
Mark E. Haase
-
Michael Lee
-
Michael Selik
-
Mikhail V
-
MRAB
-
Nicholas Chammas
-
Nicholas Cole
-
Paul Moore
-
Peter J. Holzer
-
Raymond Hettinger
-
Rhodri James
-
Richard Damon
-
Stephan Houben
-
Stephen J. Turnbull
-
Steve Dower
-
Steven D'Aprano
-
Terry Reedy
-
Thomas Jollans
-
Tim Peters
-
Todd