[Python-ideas] Fwd: Keyword only argument on function call

Anders Hovmöller boxed at killingar.net
Wed Sep 26 03:18:54 EDT 2018


David,

I saw now that I missed the biggest problem with your proposal: yet again you deliberately throw away errors. I'm talking about making Python code _less_ error prone, while you seem to want to make it _more_. Anyway, I'll modify your reach() to not have the if in it that has this error hiding property, it also simplifies it a lot. It should look  like this:

def reach(name):
    return inspect.stack()[-2][0].f_locals[name]


> 1. Huge performance penalty
> 
> Huh? Have you actually benchmarked this is some way?!  A couple lookups into the namespace are really not pricey operations.  The cost is definitely more than zero, but for any function that does anything even slightly costly, the lookups would be barely in the noise.

I'm talking about using this for all or most function calls that aren't positional only. So no, you can absolutely not assume I only use it to call expensive functions. And yea, I did benchmark it, and since you didn't define what you would think is acceptable for a benchmark you've left the door open for me to define it. This is the result of a benchmark for 10k calls (full source at the very end of this email):

CPython 3.6

time with use: 0:00:02.587355
time with standard kwargs: 0:00:00.003079
time with positional args: 0:00:00.003023

pypy 6.0

time with use: 0:00:01.177555
time with standard kwargs: 0:00:00.002565
time with positional args: 0:00:00.001953

So for CPython 3.6 it's 2.587355/0.003079 = 840x times slower
and pypy: 1.177555/0.002565 =  460x slower

I'm quite frankly a bit amazed pypy is so good. I was under the impression it would be much worse there. They've clearly improved the speed of the stack inspection since I last checked.

>  
> 2. Rather verbose, so somewhat fails on the stated goal of improving readability
> 
> The "verbose" idea I propose is 3-4 characters more, per function call, than your `fun(a, b, *, this, that)` proposal.  It will actually be shorter than your newer `fun(a, b, =this, =that)` proposal once you use 4 or more keyword arguments.

True enough. 

> 3. Tooling* falls down very hard on this
> 
> It's true that tooling doesn't currently support my hypothetical function.  It also does not support your hypothetical syntax. 

If it was included in Python it would of course be added super fast, while the use() function would not. This argument is just bogus.

> It would be *somewhat easier* to add special support for a function with a special name like `use()` than for new syntax.  But obviously that varies by which tool and what purpose it is accomplishing.

Easier how? Technically? Maybe. Politically? Absolutely not. If it's in Python then all tools _must_ follow. This solved the political problem of getting tool support and that is the only hard one. The technical problem is a rounding error in this situation.

> Of course, PyCharm and MyPy and PyLint aren't going to bother special casing a `use()` function unless or until it is widely used and/or part of the builtins or standard library.  I don't actually advocate for such inclusion, but I wouldn't be stridently against that since it's just another function name, nothing really special.


Ah, yea, I see here you're granting my point above. Good to see we can agree on this at least.

/ Anders


Benchmark code:
-----------------------

import inspect
from datetime import datetime


def reach(name):
    return inspect.stack()[-2][0].f_locals[name]

def use(names):
    kws = {}
    for name in names.split():
        kws[name] = reach(name)
    return kws

def function(a=11, b=22, c=33, d=44):
    pass

def foo():
    a, b, c = 1, 2, 3
    function(a=77, **use('b'))

c = 10000

start = datetime.now()
for _ in range(c):
    foo()
print('time with use: %s' % (datetime.now() - start))


def bar():
    a, b, c = 1, 2, 3
    function(a=77, b=b)


start = datetime.now()
for _ in range(c):
    bar()
print('time with standard kwargs: %s' % (datetime.now() - start))


def baz():
    a, b, c = 1, 2, 3
    function(77, b)


start = datetime.now()
for _ in range(c):
    baz()
print('time with positional args: %s' % (datetime.now() - start))

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180926/c8dd5ba4/attachment.html>


More information about the Python-ideas mailing list