[Python-ideas] Proposal for an inplace else (?=) operator

Lee Braiden leebraid at gmail.com
Sat Sep 22 07:53:09 EDT 2018


Could I get some feedback on this?  I'd like to know if anyone thinks it
might make it through the pep process before spending too much (more) time
on it.  That said, it seems valuable to me, and I'm willing to put in the
time, of course, IF it has a chance.

---------------------------

Problem:

    Due to (biggest python WTF) (Problem 1), which prevents actual default
argument values being set in a function signature, many
    functions look like:

        > def teleport(from, to, passenger, hitchhiker=None,
food_accessory=None, comfort_accessory=None):
        >     if hitchhiker is None:
        >         hitchhiker = Fly()
        >
        >     if food_accessory is None:
        >         food_accessory = Cheeseburger()
        >
        >     if comfort_accessory is None:
        >         comfort_accessory = Towel()
        >
        >     ...

    This None checking and setting is unwieldy (Problem 2) boilerplate,
which is responsible for many extra lines of code in python
    (Problem 3), and tends to distract from the real code (Problem 4) in a
function.

    To reduce boilerplate, a ternary or binary expression can be used:

        > def teleport(from, to, passenger, hitchhiker=None,
accessories=[]):
        >     hitchhiker = hitchhiker if hitchhiker or Fly()
    # Existing Solution A

        > def teleport(from, to, passenger, hitchhiker=None,
accessories=[]):
        >    hitchhiker = hitchhiker or Fly()
  # Existing Solution B

    These help, but are still quite repetitive:

    * Existing Solution A is often avoided simply because many Pythonistas
dislike tenery expressions (perhaps due to
      readability or hidden code branch concerns), and can quickly become
unwieldy when the new value (Fly()) is a more
      complex expression, such as a list comprehension:

        > def teleport(from, to, passenger, hitchhiker=None,
accessories=[]):
        >    hitchhiker = hitchhiker if hitchhiker or filter(lambda h: not
h.already_hitching(), available_hitchhikers)[0]

    * Existing Solution B is less unwieldy (solving Problem 2), yet still
suffers from repetition (Problems 2, 3, and 4).

    In a similar scenario, when we want to populate an empty list (say,
accesories), we could write:

        > accessories |= [Cheeseburger(), Towel()]
     # Almost-Solution C

    However, this is not actually a solution, because:

        * The inplace-or (|=) operator is not provided for None in python:

          > food_accessor = None
          > food_accessory |= Cheeseburger()

          Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
          TypeError: unsupported operand type(s) for |=: 'NoneType' and
'Cheeseburger'

          This could be added, but would not solve the issue, because:

        * If an non-default (non-None) argument value WERE provided, we be
would modifying
          the argument unintentionally:

            > class Larry:
            >     def __ior__(self, other):
            >         print("{} modified".format(self))
            >
            > teleport(..., hitchhiker=Larry(), ...)
            <__main__.Larry object at 0x7f1ad9828be0> modified

    And so Problems 1,2,3, and 4 are compounded.

Proposal:

    The addition of a ?= operator could provide an elegant solution:

        > def teleport(from, to, hitchiker=None, food_accessory=None,
comfort_accessory=None):
        >     hitchhiker ?= Fly()
        >     food_accessory ?= Cheeseburger()
        >     comfort_accessory ?= Towel()

    In these examples,

        > a = None
        > a ?= b

        > c = [1, 2]
        > c ?= d

    Would be equivalent to (assuming ?= was called __ielse__):

        > class ExtendedNone(NoneType)
        >     def __ielse__(self, other):
        >         return other
        >
        > class ielse_list(list):
        >     def __ielse__(self, other):
        >         return self
        >
        > None = ExtendedNone()
        >
        > a = None
        > a = a.__ielse__(b)
        >
        > c = iff_list([1, 2])
        > c = a.__ielse__(d)

    Although explicitly provided for list above, this ielse operator could
be defined
    (again, as `return other` for NoneType only, but defaulted to `return
self` for
    all other values of `a`, requiring very little implementation effort or
intrusion
    into other types.

Possible interaction with typing

    It may be also possible to define a ? suffix on function arguments, so
that:

        > def func(a?):
        >     a ?= [1,2]

     greatly shorting the current verbose form:

         > from typing import Optional
         >
         > def func(a = None : Optional[Any])):
         >     a = [1,2] if a is None else a

    and equivalent to:

        > from typing import Optional
        >
        > def func(a: Optional[Any]):
        >     a = a.__ielse__([1,2])

Possible alternatives:

    * The __ielse__ name is highly debatable.  Alternatives such as __iif__
(inplace if), __imaybe__, __isomeor__ (borrowing from Rusts's Some/None
terminology, and reflecting the intended use as in relation to None types,
DB nulls, and similar void-like values).

    * The ? function argument suffix is not necessary to implement the core
?= proposal.

    * If the ? function argument suffix  is implemented, implementation via
typing.Optional is not necessary; it could also be simply implemented so
that:

        > def f(a?):
        >     ...

    is equivalent to:

        > def f(a=None):
        >     ...


---------------------------

Feedback would be much appreciated.

-- 
Lee
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180922/5db824cc/attachment.html>


More information about the Python-ideas mailing list