On the Python function call interface

I hope that in this thread we will share and develop our understanding of how Python handles the interface between defining a function and calling a function. In this message, I describe a tension between the caller and implementer of a function. I intend in further messages to cover Elias Tarhini's post on __iter__(), keys(), and the mapping protocol [This affects the behaviour of fn(**kwargs), perhaps to our disadvantage.] https://mail.python.org/pipermail/python-ideas/2018-September/053320.html Marko Ristin-Kaufmann post on Pre-conditions and post-conditions [This is, in part, about wrapping a function, so it can be monitored.] https://mail.python.org/pipermail/python-ideas/2018-August/052781.html and probably some other topics. My main concern is that we know what choices are already available, and the human forces that good design decisions will balance. The signature of a function has at least three purposes. First, to provide the CALLER of the function with guidance as to how the function should be used. Second, to provide the IMPLEMENTER of the function with already initialised variables, local to the function body. Third, to provide both caller and implementer with visible default values. When there are many arguments, these two purposes can be opposed. Here's an example. The IMPLEMENTER might want to write (not tested) def method(self, **kwargs): # Do something with kwargs. # Possibly mutate kwargs. # Change values, remove items, add items. # And now pass the method up. super().method(**kwargs) In some case, the implementer might prefer def method(self, aaa, bbb, **kwargs): # Do something with aaa and bbb. super().method(**kwargs) However, the CALLER might wish that the implementer has a signature def method(self, aaa, bbb, ccc, ddd, eee, fff, ggg, hhh): and this encourages the implementer to write super().method(aaa=aaa, ...). However, there is an alternative: def method(self, aaa, bbb, ccc, ddd, eee, fff, ggg, hhh): lcls = dict(locals()) lcls.pop('aaa') lcls.pop('bbb') # Do something with aaa and bbb. super().method(**lcls) This implementation bring benefits to both the user and the implementer. But it's decidedly odd that the local variables ccc through to hhh are initialised, but not explicitly used. I think it would help to dig deeper into this, via some well-chosen examples, taken for real code. By the way, Steve Barnes has suggested Python be extended to provide "access to a method, or dictionary, called __params__ which would give access, as a dictionary, to the parameters as supplied in the call, (or filled in by the defaults)." [See: https://mail.python.org/pipermail/python-ideas/2018-September/053322.html] Such a thing already exists, and I've just showed how it might be used. https://docs.python.org/3/library/functions.html#locals >>> def fn(a, b, c, d=4, e=5): return locals() >>> fn(1, 2, 3, e=6) {'e': 6, 'd': 4, 'c': 3, 'a': 1, 'b': 2} I think, once we understand Python function and code objects better, we can make progress without extending Python, and know better what extensions are needed. Much of what we need to know is contained in the inspect module: https://docs.python.org/3/library/inspect.html -- Jonathan

On Tue, Sep 11, 2018 at 8:03 PM, Jonathan Fine <jfine2358@gmail.com> wrote:
In some case, the implementer might prefer
def method(self, aaa, bbb, **kwargs):
# Do something with aaa and bbb. super().method(**kwargs)
However, the CALLER might wish that the implementer has a signature
def method(self, aaa, bbb, ccc, ddd, eee, fff, ggg, hhh):
and this encourages the implementer to write super().method(aaa=aaa, ...).
However, there is an alternative:
def method(self, aaa, bbb, ccc, ddd, eee, fff, ggg, hhh):
lcls = dict(locals()) lcls.pop('aaa') lcls.pop('bbb')
# Do something with aaa and bbb. super().method(**lcls)
What about this alternative: @override def method(self, aaa, bbb, **kwargs): # ... stuff with aaa and bbb super().method(**kwargs) The decorator would do something akin to functools.wraps(), but assuming that it's the class's immediate parent (not sure how you'd determine that at function definition time, but let's assume that can be done), and intelligently handling the added args. Basically, it would replace kwargs with every legal keyword argument of the corresponding method on the class's immediate parent. Yes, I know that super() can pass a function call sideways, but if that happens, the only flaw is in the signature, not the implementation (ie tab completion might fail but the code still runs). ChrisA
participants (2)
-
Chris Angelico
-
Jonathan Fine