[Python-ideas] PEP 505 (None coalescing operators) thoughts

Ron Adam ron3200 at gmail.com
Fri Oct 2 12:01:12 CEST 2015


On 10/01/2015 04:12 PM, Random832 wrote:
> On Thu, Oct 1, 2015, at 16:57, Ron Adam wrote:
>> >And to go out on a limb...;-)
>> >
>> >Another possibility is to have a*special magic callable*  that when
>> >called skips the argument evaluation and returns None.

> That's dangerous talk indeed. Special magic callables are Lisp
> territory.;)

It's also lambda calculus territory.

> And I don't even know how you'd implement it efficiently without them
> being known at compile-time.

> I guess at*every*  callsite you could test if the callable is magic, and
> if it is evaluate the arguments, and if it's not just pass in a lambda
> that will return the arguments. But you've got to generate those lambdas
> for*all*  callsites, even the vast majority that will never be a magic
> callable.

I realized this evening the parser doesn't need to know at parse time, 
and the object doesn't need to be special.  It's the syntax that gives 
it the specialness, not the object.  So what I was thinking is still 
possible, but it would work more like the other suggestions.

If you look at byte code generated for a function call...

 >>> def bar(x, y):
...     return x + y
...
 >>> def foo(x, y):
...     return bar(x+1, y+1)
...
 >>> dis(foo)
   2           0 LOAD_GLOBAL              0 (bar)
               3 LOAD_FAST                0 (x)
               6 LOAD_CONST               1 (1)
               9 BINARY_ADD
              10 LOAD_FAST                1 (y)
              13 LOAD_CONST               1 (1)
              16 BINARY_ADD
              17 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
              20 RETURN_VALUE

You will notice the function is loaded on the stack *before* the 
argument expressions are evaluated.

It won't require generating lamba expressions, just a conditional jump 
where '?(' is used.  So no, it won't be required at every call site.

Something roughly like ...  (presume the index's are adjusted correctly)

2  0  LOAD_GLOBAL 0 (bar)

    #-- inserted only if '?(' is used --------------
       POP_JUMP_IF_NOT_NOCALL 3
       LOAD_CONST 0 (None)
       JUMP_FORWARD  8 (to 20)
    #-----------------------------------------------

    3  LOAD_FAST 0 (x)
    6  LOAD_CONST 1 (1)
    9  BINARY_ADD
    10 LOAD_FAST 1 (y)
    13 LOAD_CONST 1 (1)
    16 BINARY_ADD
    17 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
    20 RETURN_VALUE

So the parser doesn't need to know if it's the NoneCall object at parse 
time, it just needs to know to check for it, and the ?( syntax can tell 
it to do that only where it's needed.

There really isn't anything special about the NoneCall object, it's just 
a reserved builtin object.  And adding the check for it isn't all the 
special either.  It's the behavior that is special because of the ?( sytnax.

I was thinking it could call a __cond_call__ method on the object.  That 
extra indirect call could allow objects to specify how they respond to 
?( conditional calls.  It's just requires an extra LOAD_ATTR and call it 
with no arguments in the bytecode.

The case of unpacking the args if the object is None would require an 
additional check for None and to load an identity function in it's place 
or could be done by adding a __cond_call__ method (if that is how ?( 
will work) to None that is an identity function.

     value = obj?(default)  #None?(default) --> identity(default)

Think of these as moving conditions that would otherwise be elsewhere to 
a more convenient location.  So I don't expect them to be much slower 
than the equivalent code that does the same thing.

A __cond_call__ method works because it can't conflict with __call__. 
So it avoids any conflicts that might occur by calling __call__ by mistake.


The point is that a conditional call syntax could also fill the need of 
the or None operator.  The specific details may be a bit different than 
what I describe here, but I think it's doable.



> What if the callable only wants*some*  of the arguments? Hey, if this
> had existed back then the ternary operator could have been a normal
> function - instead of (b() if a else c()) just do iif(a, b(), c()).

Only wanting some arguments would be much harder.  It might be done by 
splitting the arguments into nested ?( calls similar to how we apply 
multiple decorators.  I don't expect it to be pretty or practical, so 
lets just not go there for now.

> And is this going to be fully general? I.e. should this be supported for
> regular operators? If __add__ is magic does + do this, for example?

No, it would only work with ?( syntax.


Cheers,
    Ron




More information about the Python-ideas mailing list