2

Hope the title is conveying the correct information.

My problem is that I don't understand why call kwarg_function(some_func, a=1, b=2, c=3) fails. I would have thought that as 'c' isn't referenced with some_func() it would simply be ignored. Can anyone explain why 'c' isn't simply ignored.

def kwarg_function(function, **kwargs):
    print(kwargs)
    function(**kwargs)

def some_func(a, b):
    print(f"type: {type(a)} values: {a}")
    print(f"type: {type(b)} values: {b}")


kwarg_function(some_func, a=1, b=2)         # called successfully

kwarg_function(some_func, a=1, b=2, c=3)    # fails with unexpected keyword arg 'c'   
eklektek
  • 1,083
  • 1
  • 16
  • 31
  • 4
    Because `kwarg_function` is in essence calling `function(..., c=3)`… – deceze Jan 03 '23 at 14:11
  • 2
    I think your confusion might come from `C++`ish background (which is nothing wrong, but the `*`, `**` have nothing in common with "referencing" per se, they are for "unpacking" iterables in this case) – Gameplay Jan 03 '23 at 14:18
  • @deceze thanks. I obviously hadn't thought deeply enough. I was thinking that kwargs was essentially a dictionary! However the ** means it is being unpacked, so if kwargs contains 'c' then 'c' is expected in function to which **kwargs is being passed. – eklektek Jan 03 '23 at 14:38
  • `kwargs` *is* a `dict` inside `kwarg_function`; whatever mapping is used as the argument when `kwarg_function` is called is used to define a `dict`. The contents of that `dict`, though, define keyword arguments not expected by the other function you passed to `kwarg_function`. – chepner Jan 03 '23 at 15:20

2 Answers2

2

Think of the ** as "unpack what's on my right side as keyword arguments" in this case.

def foo(a,b,**kwargs):
    # This will take any number of arguments provided that starting from the 3rd one they are keyword args


# Those are equivalent (and working) calls
foo(1,2, x = 7)
foo(1,2, **{"x":7})
# Those will fail
foo(1,2,7)
foo(1,2, {"x":7})

The function you declared expects 2 arguments

def some_func(a, b):

And you are calling it with three under the hood, because this:

kwarg_function(some_func, a=1, b=2, c=3)    # fails with unexpected keyword arg 'c'   

Does this (inside kwarg_function body):

funtion(a=1,b=2,c=3)
Gameplay
  • 1,142
  • 1
  • 4
  • 16
1

In python, * and ** are for unpacking iterables. They don't consider what's are in them, and just unpack whatever you pass in them.

You can find more info about it in this link.

So, when you pass a=1, b=2, c=3, ... as kwargs to your kwargs_function, you will get them as kwargs param, regardless of what you have passed.

And then, when you pass **kwargs to another function, all of your data would be passed to your another function, regardless of what's in that.

If you want your some_func be more flexible with your data and accept whatever you pass to it, you can add **kwargs param to it too:

def some_func(a, b, **kwargs):
    print(f"type: {type(a)} values: {a}")
    print(f"type: {type(b)} values: {b}")
Amin
  • 2,605
  • 2
  • 7
  • 15