17

Given a higher order function that takes multiple functions as arguments, how could that function pass key word arguments to the function arguments?

example

def eat(food='eggs', how_much=1):
    print(food * how_much)


def parrot_is(state='dead'):
    print("This parrot is %s." % state)


def skit(*lines, **kwargs):
    for line in lines:
        line(**kwargs)

skit(eat, parrot_is)  # eggs \n This parrot is dead.
skit(eat, parrot_is, food='spam', how_much=50, state='an ex-parrot') # error

state is not a keyword arg of eat so how can skit only pass keyword args relevant the function that it is calling?

alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
cheezsteak
  • 2,731
  • 4
  • 26
  • 41
  • 4
    No doubt there are ways to accomplish this, but I think you're approaching this from the wrong angle. What would you do if two of your functions take a keyword arg with the same name, and you want to pass different values to the two functions? Best to just pass in dicts of keyword args explicitly. Potentially each argument would be (say) a two-element tuple consisting of a function and its dict of keyword args. – Hammerite May 02 '14 at 14:21
  • That's probably the best solution. – cheezsteak May 02 '14 at 14:32

3 Answers3

16

You can filter the kwargs dictionary based on func_code.co_varnames (in python 2) of a function:

def skit(*lines, **kwargs):
    for line in lines:
        line(**{key: value for key, value in kwargs.iteritems() 
                if key in line.func_code.co_varnames})

In python 3, __code__ should be used instead of func_code. So the function will be:

def skit(*lines, **kwargs):
    for line in lines:
        line(**{key: value for key, value in kwargs.iteritems() 
                if key in line.__code__.co_varnames})

Also see: Can you list the keyword arguments a function receives?

Suraj Donthi
  • 329
  • 2
  • 11
alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • @Aशwiniचhaudhary thanks, good point, as always helpful. I've also added a link to the related thread. – alecxe May 02 '14 at 14:21
  • 1
    What is the benefit of inspect.getargspec(func).args over line.func_code.co_varnames? Does then inspect module just provide more options that we don't need in this case? – cheezsteak May 02 '14 at 16:16
  • 2
    @ptwales `inspect` really [uses](https://github.com/python/cpython/blob/2.7/Lib/inspect.py#L804) the same `func_code` under the hood. `inspect` is generally speaking the tool for the job that provides an interface to the "magic" involved in object inspection. – alecxe May 02 '14 at 16:20
8

If you add **kwargs to all of the definitions, you can pass the whole lot:

def eat(food='eggs', how_much=1, **kwargs):
    print(food * how_much)


def parrot_is(state='dead', **kwargs):
    print("This parrot is %s." % state)


def skit(*lines, **kwargs):
    for line in lines:
        line(**kwargs)

Anything in **kwargs that isn't also an explicit keyword argument will just get left in kwargs and ignored by e.g. eat.

Example:

>>> skit(eat, parrot_is, food='spam', how_much=50, state='an ex-parrot')
spamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspamspam
This parrot is an ex-parrot.
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
-1

def sample(a,b,*,c=0): print(a,b,c)

* -> is before argument is args and after argument is kwargs(keyword arguments)

sample(1,2,c=10) sample(a=1,b=2,c=1) # this also work sample(1,2,c) # TypeError: sample() takes 2 positional arguments but 3 were given

vraj
  • 31
  • 1
  • 10