docs.python.org says that functools.partial
is roughly equivalent to:
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
(Note: /
is used to denote func
as a positional-only argument of partial
. See [1].)
If I understand correctly, when a variable is referenced within a nested function, such as newfunc
, Python first looks for the variable definition within the nested function. If the definition is not found there, Python will next look for the definition in the enclosing scope (i.e. the outer function; partial
in this case). So, are the explicit .func
, .args
, and .keywords
attribute bindings to newfunc
above really necessary? I tried an example without said bindings and it partial
worked just fine? Is there a case where they might be necessary?
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 # p0, p1, p2 are positional arguments # kw0, kw1, kw2 are keyword-only arguments def foo3(p0, p1, p2, *, kw0, kw1, kw2): return 100*p2 + 10*p1 + 1*p0, kw0 + kw1 + kw3 foo2 = partial(foo3, 1, kw0=1+1j) print(foo2(2,3,kw1=2+2j, kw2=3+3j)) # (321, (6+6j))
Are the .
bindings necessary if the keywords
or fkeywords
dictionaries includes an item with func
, args
, or keywords
as the keyword? What would be an example where these are necessary? As far as I can tell, that’s not a reason because the following works:
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 # p0, p1, p2 are positional arguments # kw0, kw1, kw2 are keyword-only arguments def foo3(p0, p1, p2, kw0, kw1, kw2, **kwargs): return 100*p2 + 10*p1 + 1*p0, kw0 + kw1 + kw2 + sum(kwargs.values()) foo2 = partial(foo3, 1, kw0=1+1J, func=10, args=10j, keywords=100+100j) print(foo2(2,3,kw1=2+2J, kw2=3+3J, func=20, args=20j, keywords=200+200j)) # (321, (226+6j))
[1] https://docs.python.org/3/whatsnew/3.8.html#positional-only-parameters
Advertisement
Answer
I think you can look at the partial
class implementation to help you understand better.
The following (Python 3.9.5
)
class partial: """New function with partial application of the given arguments and keywords. """ __slots__ = "func", "args", "keywords", "__dict__", "__weakref__" def __new__(cls, func, /, *args, **keywords): if not callable(func): raise TypeError("the first argument must be callable") if hasattr(func, "func"): args = func.args + args keywords = {**func.keywords, **keywords} func = func.func self = super(partial, cls).__new__(cls) self.func = func self.args = args self.keywords = keywords return self def __call__(self, /, *args, **keywords): keywords = {**self.keywords, **keywords} return self.func(*self.args, *args, **keywords) ...
When you replace self
with newfunc
, they’re pretty much the same.