Proposal for an inplace else (?=) operator

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

On Saturday, September 22, 2018, Lee Braiden <leebraid@gmail.com> wrote:
This is attacking the workaround not the actual problem. The real problem is that default parameters are evaluated at function definition time not call time. Setting the default to None and then replacing it is a workaround to that problem (and one that doesn't work if None is an allowable value). If Python were going to address this problem, I think it better to do something like: def teleport(from, to, hitchiker => Fly(), accessory => Towel(hitchhiker)): etc. Where => specifies an expression thst is evaluated inside the function and Is approximately equivalent to your code except it works even if the caller passes None. (I don't know what the right syntax for this would be. I just picked => as something that is suggestive, not legal today and isn't ?= to avoid confusion with your proposal.) Note that I shortened your example and modified it slightly to show that I would have the order of the parameters be significant. The call to Towel can use the previous hitchiker parameter and it will do what you expect. Clearly that would work with your code; you just weren't esplicit about it. My alternative doesn't allow arbitrary None replacement, but I'm not sure that's a prevalent pattern other than in this case notwithstanding your examples. --- Bruce -- --- Bruce

On Sat, 22 Sep 2018 at 12:54, Lee Braiden <leebraid@gmail.com> wrote:
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.
There have been other proposals along these lines already. For a start, you should take a look at the "None-aware operators" PEP, and the various discussions in the list archives around that. If nothing else, your proposal would been to include a review of that one and an explanation of why yours is better. Paul

On Sat, Sep 22, 2018 at 4:53 AM Lee Braiden <leebraid@gmail.com> wrote:
Problem: [Python] prevents actual default argument values being set in a function signature Feedback would be much appreciated.
You'd be more convincing if you stated the problem more precisely. Python supports default values for function arguments. Regardless, I'll echo the discussion of the None-aware operators PEP: it's quite simple to write ``if arg is None: arg = ...`` in the function body. I don't find the use case compelling.

On Saturday, September 22, 2018, Lee Braiden <leebraid@gmail.com> wrote:
This is attacking the workaround not the actual problem. The real problem is that default parameters are evaluated at function definition time not call time. Setting the default to None and then replacing it is a workaround to that problem (and one that doesn't work if None is an allowable value). If Python were going to address this problem, I think it better to do something like: def teleport(from, to, hitchiker => Fly(), accessory => Towel(hitchhiker)): etc. Where => specifies an expression thst is evaluated inside the function and Is approximately equivalent to your code except it works even if the caller passes None. (I don't know what the right syntax for this would be. I just picked => as something that is suggestive, not legal today and isn't ?= to avoid confusion with your proposal.) Note that I shortened your example and modified it slightly to show that I would have the order of the parameters be significant. The call to Towel can use the previous hitchiker parameter and it will do what you expect. Clearly that would work with your code; you just weren't esplicit about it. My alternative doesn't allow arbitrary None replacement, but I'm not sure that's a prevalent pattern other than in this case notwithstanding your examples. --- Bruce -- --- Bruce

On Sat, 22 Sep 2018 at 12:54, Lee Braiden <leebraid@gmail.com> wrote:
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.
There have been other proposals along these lines already. For a start, you should take a look at the "None-aware operators" PEP, and the various discussions in the list archives around that. If nothing else, your proposal would been to include a review of that one and an explanation of why yours is better. Paul

On Sat, Sep 22, 2018 at 4:53 AM Lee Braiden <leebraid@gmail.com> wrote:
Problem: [Python] prevents actual default argument values being set in a function signature Feedback would be much appreciated.
You'd be more convincing if you stated the problem more precisely. Python supports default values for function arguments. Regardless, I'll echo the discussion of the None-aware operators PEP: it's quite simple to write ``if arg is None: arg = ...`` in the function body. I don't find the use case compelling.
participants (6)
-
Anders Hovmöller
-
Bruce Leban
-
Lee Braiden
-
Michael Selik
-
Paul Moore
-
Robert Vanden Eynde