partial: disallow overriding given keyword arguments

984 views Asked by At

Is there a way to disallow overriding given keyword arguments in a partial? Say I want to create function bar which always has a set to 1. In the following code:

from functools import partial

def foo(a, b):
  print(a)
  print(b)

bar = partial(foo, a=1)
bar(b=3) # This is fine and prints 1, 3
bar(a=3, b=3) # This prints 3, 3

You can happily call bar and set a to 3. Is it possible to create bar out of foo and make sure that calling bar(a=3, b=3) either raises an error or silently ignores a=3 and keeps using a=1 as in the partial?

3

There are 3 answers

3
Serge Ballesta On BEST ANSWER

This is by design. The documentation for partial says (emphasize mine):

functools.partial(func, /, *args, **keywords)

Return a new partial object which when called will behave like func called with the positional arguments args and keyword arguments keywords. If more arguments are supplied to the call, they are appended to args. If additional keyword arguments are supplied, they extend and override keywords.

If you do not want that, you can manually reject frozen keyword arguments:

def freeze(f, **kwargs):
    frozen = kwargs
    def wrapper(*args, **kwargs):
        kwargs.update(frozen)
        return f(*args, **kwargs)
    return wrapper

You can now do:

>>> baz = freeze(foo, a=1)
>>> baz(b=3, a=2)
1
3
0
Ajax1234 On

If you want to raise an error when overriding occurs, you can create a custom implementation of partial:

class _partial:
   def __init__(self, f, **kwargs):
      self.f, self.kwargs = f, kwargs
   def __call__(self, **kwargs):
      if (p:=next((i for i in kwargs if i in self.kwargs), None)) is not None:
         raise ValueError(f"parameter '{p}' already specified")
      return self.f(**self.kwargs, **kwargs)

def foo(a, b):
   print(a)
   print(b)

>>> bar = _partial(foo, a=1)
>>> bar(b=3)
1
3
>>> bar(a=3, b=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __call__
ValueError: parameter 'a' already specified
0
Atreyagaurav On

Do you really need to use partial? You can just define a new function bar normally with fewer arguments.

Like:

def bar(a):
  b = 1
  foo(a, b)

This will give error.

Or like:

def bar(a, b=1):
  b = 1 #ignored provided b
  foo(a, b)

This will ignore b.

EDIT: use lambda if you want to oneline these: like: bar = lambda a:foo(a,b=1) or bar = lambda a,b=1:foo(a,b=1)