Mikhail M.
Mikhail M.

Reputation: 5988

What is execution model for *args in function call?

I need to pass huge list/tuple to function through *args.

def f(*args):  # defined in foreign module
    pass

arguments = tuple(range(10000))
f(*arguments)

And I wonder what happens at function call.

Does it handle arguments similar to any positional variable: save it and access on-demand during body execution? Or does it iterate through arguments even before body execution, extending positional arguments? Or is it something else?

Upvotes: 9

Views: 954

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1124238

Yes, the *arguments call syntax has to iterate over the arguments iterable, for two reasons:

  • You are passing in a list, but the *args variable-size argument in the function is a tuple. So elements will have to be copied here.

  • The call syntax has to be usable for any function, where you may have actual positional arguments instead of, or in addition to, a *varargs variable.

    For example, if the function signature was def f(foo, *args):, then the first element would have to be passed in separately.

    In principle, CPython could optimise for the case where all values of a tuple used in a call with function(*tupleargs) end up in a *varargs argument, and re-use that tuple. However, this is actually not all that common and no-one has done this.

Note that for the **kwargs call syntax, the added challenge of mutability makes sharing the object used a really bad idea; you have to create a copy of the dict used because otherwise the function or the caller could mutate that dictionary with the changes reflected in the other reference.

Upvotes: 7

poke
poke

Reputation: 388223

A simple test using a generator:

def gen():
    print('Yielding 1')
    yield 1
    print('Yielding 2')
    yield 2
    print('Yielding 3')
    yield 3


arguments = gen()
def f(*args):
    pass

f(*arguments)
# Yielding 1
# Yielding 2
# Yielding 3

As you can see from the output, passing *arguments will actually unpack the whole iterable, since technically, you are telling Python to pass the iterable as individual arguments using the *arguments syntax. It does not matter that the function definition also uses *args which makes Python pack the arguments back into a tuple again.

So yeah, you are unpacking the list only to pack it again here. You can avoid this by just passing the list directly.

Upvotes: 12

Related Questions