170

I'm trying to call a function inside another function in python, but can't find the right syntax. What I want to do is something like this:

def wrapper(func, args):
    func(args)

def func1(x):
    print(x)

def func2(x, y, z):
    return x+y+z

wrapper(func1, [x])
wrapper(func2, [x, y, z])

In this case first call will work, and second won't. What I want to modify is the wrapper function and not the called functions.

SilentGhost
  • 307,395
  • 66
  • 306
  • 293
SurDin
  • 3,281
  • 4
  • 26
  • 28

6 Answers6

292

To expand a little on the other answers:

In the line:

def wrapper(func, *args):

The * next to args means "take the rest of the parameters given and put them in a list called args".

In the line:

    func(*args)

The * next to args here means "take this list called args and 'unwrap' it into the rest of the parameters.

So you can do the following:

def wrapper1(func, *args): # with star
    func(*args)

def wrapper2(func, args): # without star
    func(*args)

def func2(x, y, z):
    print x+y+z

wrapper1(func2, 1, 2, 3)
wrapper2(func2, [1, 2, 3])

In wrapper2, the list is passed explicitly, but in both wrappers args contains the list [1,2,3].

itsadok
  • 28,822
  • 30
  • 126
  • 171
  • 26
    One thing that I didn't find mentioned very often is how to call a function with *args, if you have a list or tuple that you want to pass. For that you need to call it like this: wrapper1(func2, *mylist) – Ali Jul 09 '10 at 05:46
  • *args in `def wrapper(func, *args)` is what `method(params object[] args)`is in C#. – Jim Aho Mar 04 '16 at 19:20
  • 1
    Note that `*args` must be the last argument in the function definition. – Jim Aho Mar 04 '16 at 19:23
  • 3
    `The * next to args means "take the rest of the parameters given and put them in a list called args".` It puts them in a tuple, not a list. – Tomasz Nocoń Nov 04 '16 at 13:42
27

The simpliest way to wrap a function

    func(*args, **kwargs)

... is to manually write a wrapper that would call func() inside itself:

    def wrapper(*args, **kwargs):
        # do something before
        try:
            return func(*a, **kwargs)
        finally:
            # do something after

In Python function is an object, so you can pass it's name as an argument of another function and return it. You can also write a wrapper generator for any function anyFunc():

    def wrapperGenerator(anyFunc, *args, **kwargs):
        def wrapper(*args, **kwargs):
            try:
                # do something before
                return anyFunc(*args, **kwargs)
            finally:
                #do something after
        return wrapper

Please also note that in Python when you don't know or don't want to name all the arguments of a function, you can refer to a tuple of arguments, which is denoted by its name, preceded by an asterisk in the parentheses after the function name:

    *args

For example you can define a function that would take any number of arguments:

    def testFunc(*args):
        print args    # prints the tuple of arguments

Python provides for even further manipulation on function arguments. You can allow a function to take keyword arguments. Within the function body the keyword arguments are held in a dictionary. In the parentheses after the function name this dictionary is denoted by two asterisks followed by the name of the dictionary:

    **kwargs

A similar example that prints the keyword arguments dictionary:

    def testFunc(**kwargs):
        print kwargs    # prints the dictionary of keyword arguments
Sean Breckenridge
  • 1,932
  • 16
  • 26
Alex
  • 43,191
  • 44
  • 96
  • 127
15

The literal answer to your question (to do exactly what you asked, changing only the wrapper, not the functions or the function calls) is simply to alter the line

func(args)

to read

func(*args)

This tells Python to take the list given (in this case, args) and pass its contents to the function as positional arguments.

This trick works on both "sides" of the function call, so a function defined like this:

def func2(*args):
    return sum(args)

would be able to accept as many positional arguments as you throw at it, and place them all into a list called args.

I hope this helps to clarify things a little. Note that this is all possible with dicts/keyword arguments as well, using ** instead of *.

Alan Rowarth
  • 2,250
  • 2
  • 14
  • 10
11

You can use *args and **kwargs syntax for variable length arguments.

What do *args and **kwargs mean?

And from the official python tutorial

http://docs.python.org/dev/tutorial/controlflow.html#more-on-defining-functions

Community
  • 1
  • 1
JimB
  • 104,193
  • 13
  • 262
  • 255
  • So I need it to get both *args and **kwargs and call it with them? – SurDin May 03 '09 at 13:52
  • 1
    No, you can use either/or, but they are often paired together. In your case, you only need *args. – JimB May 03 '09 at 13:56
  • Ok, it works, but it still doesn't let me pass a list of argument, and i have to pass them seperately. It doesn't bother me much, in my current situation, but it still would be good to know how to do this. (I need to do wrapper(func2, x, y, z) and not wrapper(func2, [x,y,z]) ) – SurDin May 03 '09 at 13:58
  • If the latter is what you want, use the *args form when wrapper calls func2, but not in 'def wrapper'. – Alex Martelli May 03 '09 at 19:21
8

You need to use arguments unpacking..

def wrapper(func, *args):
    func(*args)

def func1(x):
    print(x)

def func2(x, y, z):
    print x+y+z

wrapper(func1, 1)
wrapper(func2, 1, 2, 3)
Joril
  • 19,961
  • 13
  • 71
  • 88
0

A small addition to previous answers, since I couldn't find a solution for a problem, which is not worth opening a new question, but led me here.

Here is a small code snippet, which combines lists, zip() and *args, to provide a wrapper that can deal with an unknown amount of functions with an unknown amount of arguments.

def f1(var1, var2, var3):
    print(var1+var2+var3)

def f2(var1, var2):
    print(var1*var2)

def f3():
    print('f3, empty')

def wrapper(a,b, func_list, arg_list):
    print(a)
    for f,var in zip(func_list,arg_list):
        f(*var)
    print(b)

f_list = [f1, f2, f3]
a_list = [[1,2,3], [4,5], []]

wrapper('begin', 'end', f_list, a_list)

Keep in mind, that zip() does not provide a safety check for lists of unequal length, see zip iterators asserting for equal length in python.

Community
  • 1
  • 1
P. Siehr
  • 525
  • 1
  • 6
  • 22