0

In the documentation, the following is what's described as the rough equivalent of partial's functionality. I have trouble understanding this code. From the / in the parameters to the attribute assignments - these are attributes of the partial object - how can they make their way into its definition? Any help pointing me to some resource where this is explained would be appreciated.

   def partial(func, /, *args, **keywords):
        def newfunc(*fargs, **fkeywords):
            newkeywords = {**keywords, **fkeywords}
            return func(*args, *fargs, **newkeywords)
        newfunc.func = func
        newfunc.args = args
        newfunc.keywords = keywords
        return newfunc

1 Answer 1

6

First, the / is new syntax added in Python 3.8.0. It indicates that the parameters that precede it are positional-only, and may not be specified as keyword arguments. This allows the implementation to change the parameter name at a later time without risking breaking any calls that refer to it by name (since they're prevented from doing so).

The function partial returns a closure which references the arguments that were passed to partial. When invoked, the returned function first constructs the arguments by combining the newly-passed arguments with the ones that were specified earlier, when partial was called. It then passes those constructed arguments to func, returning the result of the call.

For example, if you do:

f = partial(my_func, 1, 2, x=3, y=4)

then f is newfunc, but taken in the context of the call to partial, so when it's called, it will have access to the arguments that you passed.

If you then do:

r = f(5, 6, z=7, w=8)

Then the following happens: First, newfunc is called as:

newfunc(5, 6, z=7, w=8)

Within newfunc, fargs is (5, 6) and fkeywords is {'z' : 7, 'w' : 8}. It then constructs the combined arguments by combining these with the values of args and keywords from the call to partial, so args is (1, 2) and keywords is {'x' : 3, 'y' : 4}.

It then combines these. It first sets newkeywords to {'x' : 3, 'y' : 4, 'z' : 7, 'w' : 8}. It then calls func, which is my_func, combining the positional arguments in the call itself, so they end up as (1, 2, 5, 6).

The result is equivalent to:

r = my_func(1, 2, 5, 6, x=3, y=4, z=7, w=8)

So f is essentially a wrapper for my_func which supplies some of the arguments.

The rest of partial is just setting some attributes of newfunc. Those attributes (func, args, and keywords) are available to the caller from the returned function (i.e. f in this example). So you could access them as f.func, f.args, and f.keywords.

Sign up to request clarification or add additional context in comments.

4 Comments

You can also see the saved contents in the closure. Given f = lambda x,y,z: x + y + z and fp = partial(f, 3, y=9), look at [x.cell_contents for x in fp.__closure__]. (I don't have a good explanation for the actual order used by __closure__.)
@chepner That's interesting. It looks like it doesn't store the variable name in the cell, but only its value, unless it's stored someplace else that I couldn't find.
@TomKarzes Thank you for the detailed answer. I didn't know you could set arbitrary attributes to functions.
@TomKarzes I think it's probably just an implementation detail of CPython. If you look at the generated byte code for fp, you can see the integer indices used with the LOAD_DEREF instruction. The information stored is sufficient for use by the compiled code, but not designed for introspection.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.