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
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.