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