Что такое модель исполнения для * args в вызове функции?

Мне нужно передать огромную list/tuple функцию через *args.

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

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

И мне интересно, что происходит при вызове функции.

Поддерживает ли он arguments аналогичную любую позиционную переменную: сохранить ее и получить доступ по требованию во время выполнения тела? Или он выполняет итерацию через arguments еще до выполнения тела, расширяя позиционные аргументы? Или это что-то еще?

Ответ 1

Да, синтаксис вызова *arguments должен повторять итерацию arguments по двум причинам:

  • Вы передаете список, но аргумент *args переменной размера в функции является кортежем. Таким образом, элементы должны быть скопированы здесь.

  • Синтаксис вызова должен использоваться для любой функции, где у вас могут быть фактические позиционные аргументы вместо или в дополнение к переменной *varargs.

    Например, если сигнатура функции def f(foo, *args):, то первый элемент должен быть передан отдельно.

    В принципе, CPython может оптимизировать для случая, когда все значения кортежа, используемые в вызове с function(*tupleargs), заканчиваются аргументом *varargs и повторно используют этот кортеж. Однако на самом деле это не все, что распространено, и никто не сделал этого.

Обратите внимание, что для синтаксиса вызова **kwargs добавленная проблема изменчивости делает обмен используемым объектом действительно плохую идею; вам нужно создать копию используемого dict, потому что иначе функция или вызывающий может изменить этот словарь с изменениями, отраженными в другой ссылке.

Ответ 2

Простой тест с использованием генератора:

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

Как вы можете видеть из вывода, передача *arguments фактически распакует всю итерабельность, так как технически вы сообщаете Python передавать итерабельность как отдельные аргументы с использованием синтаксиса *arguments. Не имеет значения, что в определении функции также используется *args, что заставляет Python снова добавлять аргументы в кортеж.

Итак, вы распаковываете список, чтобы снова упаковать его здесь. Вы можете избежать этого, просто передав список напрямую.