Получение соответствующих *args, **kwargs из набора функций, затем заполнение нерегулярной 2d-структуры предоставленными 1d-аргументами

У меня есть такие данные:

args, kwargs = (('foo', 'bar', 'baz'), {'goo': 1})

И у меня есть функции внутри объекта, которым нужны эти данные в качестве аргументов. Они должны быть предоставлены через метод, который имеет такую ​​подпись (представленную как *args):

callFunctions((*args, **kwargs), ...)

Или, более точно, в его структуре:

callFunctions(((args ...), {kwargs ...}), ...)

(Надеюсь, достаточно ясно, что я ожидаю от этого метода.)

Скажем, для примера, что мои две функции следующие:

def func1(foo, bar):
    print foo, bar

def func2(baz, goo=0):
    print baz, goo

funcs = func1, func2    # for iteration

На этом фоне у меня есть две проблемы.

Получение формата *args, **kwargs для каждой функции

Я пытался использовать модуль inspect, чтобы получить спецификацию аргумента для каждой функции, чтобы я мог «заполнить» структуру данными 1d в args и kwargs. Я пробовал следующее:

format = [(spec.args, spec.keywords) for spec in (inspect.getargspec(func) for func in funcs)]

Но я не понимаю, почему spec.keywords всегда дает мне None (казалось бы идиотским). Значение по умолчанию любого аргумента ключевого слова будет отображаться в spec.defaults, но я не знаю, как связать его с правильным аргументом ключевого слова. (Также расстраивает то, что все аргументы ключевого слова помещаются в spec.args. )

[(['foo', 'bar'], None), (['baz', 'goo'], None)]

Заполнение структуры для передачи callFunctions

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

argues = []
positional_index = 0
format = ((('foo', 'bar'), {}), (('baz',), {'goo': 0}))
for pair in format:
    however_many = len(pair[0])
    argues.append((tuple(args[positional_index:however_many]), dict({(k, kwargs[k]) for k in pair[1]})))
    positional_index += however_many

Но я получаю это:

[(('foo', 'bar'), {}), ((), {'goo': 1})]

Почему я не получаю baz?


person 2rs2ts    schedule 10.07.2013    source источник
comment
Я не понимаю вторую часть вашего вопроса. Если вы уже разделили его на ('foo', 'bar'), {} и ('baz',), {'goo': 0}, зачем вам дальнейшая обработка? Каждая из этих пар уже является набором аргументов для одной функции, поэтому вы можете просто перебирать их и передавать каждую.   -  person BrenBarn    schedule 10.07.2013
comment
@BrenBarn Чтобы прояснить пару, я дал предоставленные строки args и kwargs с тем же именем, что и аргументы в двух функциях, но они могут быть любыми. Например, предоставленное args, kwargs в самом начале моего вопроса может быть ((1, 2, 3), {'hello':'world'}).   -  person 2rs2ts    schedule 10.07.2013
comment
А, теперь я вижу. Ваша ошибка там проще, см. мой отредактированный ответ.   -  person BrenBarn    schedule 10.07.2013


Ответы (1)


Что касается первой части вашего вопроса: дело в том, что (в Python 2) нет полного различия между «ключевым словом» и «позиционными» аргументами как таковыми. Любое значение аргумента может быть передано позиционно или по ключевому слову. Просто, если аргумент не имеет значения по умолчанию, этот аргумент должен быть предоставлен --- но его все равно можно передать по ключевому слову. (В Python 3 возможны истинные аргументы, состоящие только из ключевых слов, которые нельзя передавать позиционно.)

Таким образом, способ сопоставить аргументы с их значениями по умолчанию состоит в том, чтобы понять, что значения по умолчанию могут применяться только к последним аргументам. Таким образом, если функция принимает четыре аргумента и имеет два значения аргумента по умолчанию, эти значения должны быть для третьего и четвертого аргументов, причем первые два аргумента не имеют значений по умолчанию. Говоря простым языком, это функция с двумя позиционными аргументами и двумя ключевыми аргументами (хотя, как я уже упоминал, это лишь половина точности, поскольку любой аргумент всегда можно передать по ключевому слову). Как сказано в документации, keywords часть результата getargspec не предназначен для хранения аргументов ключевого слова; он содержит имя ключевого аргумента varargs (например, **kwargs), если оно есть.

Чтобы увидеть, как их сопоставить, просто взгляните на исходный код для inspect.formatargspec, который, по сути, делает это (я немного изменил его, чтобы создать список вместо строкового представления):

args, varargs, varkw, defaults = inspect.getargspec(func)
result = []
if defaults:
    firstdefault = len(args) - len(defaults)
for i, arg in enumerate(args):
    if defaults and i >= firstdefault:
        result.append((arg, defaults[i - firstdefault]))
    else:
        result.append((arg,))

Что касается второй части вашего вопроса, проблема в том, что вы нарезаете positional_index:however_many вместо positional_index:positional_index+however_many. Кроме того, ваша путаница с dict/set связана с тем, что вы используете понимание набора вместо понимания dict. Кроме того, вам не нужен этот вызов tuple. Сделай это так:

for pair in format:
    however_many = len(pair[0])
    argues.append((args[positional_index:positional_index+however_many], {k: kwargs[k] for k in pair[1]}))
    positional_index += however_many

Обратите внимание, однако, что одна из проблем заключается в том, что вы не сможете передавать аргументы ключевого слова с разными значениями с одним и тем же именем в разные функции. Если у вас есть def foo(a, b, c=2) и def bar(a, b, c=8), у вас нет возможности передать {'c': 1} в foo и {'c': 2} в bar, потому что вы передаете только один dict в начале, который может иметь только один ключ 'c'.

В более общем случае, если вы действительно хотите обрабатывать любой допустимый набор аргументов, это будет не так просто, потому что даже «позиционные» аргументы могут быть переданы по ключевому слову. Если у вас есть такая функция, как def foo(a, b, c=3, d=4), ее можно вызывать с помощью foo(1, d=8, c=7, b=6) --- вы можете передавать аргументы ключевого слова не по порядку, а также передавать значение для b по ключевому слову, даже если оно не имеет значения по умолчанию. Таким образом, вы не можете просто захватывать позиционные аргументы на основе количества аргументов без значений по умолчанию; вы должны фактически посмотреть, какие конкретные аргументы были переданы по ключевому слову, и только позиционно передать те, которые не были переданы по ключевому слову. (Конечно, вы могли бы просто сказать, что ваша функция не будет работать для такого случая; это зависит от того, насколько общим вы хотите, чтобы она была.)

person BrenBarn    schedule 10.07.2013
comment
Спасибо за примечание о понимании dict/set. Сейчас пытаюсь докопаться до остального. Между прочим, я согласен не приводить этот фрагмент к tuple, пока я могу использовать синтаксис *, чтобы передать его как *args — могу ли я это сделать? - person 2rs2ts; 10.07.2013
comment
@2rs2ts: Да. Причина, по которой вам не нужно вызывать кортеж, заключается в том, что он уже является кортежем. Когда вы нарезаете кортеж с помощью args[...], вы уже получаете кортеж. - person BrenBarn; 10.07.2013
comment
Заметил про ломтик... кофеина лишенный там оплошности. В ответ на ваше последнее редактирование аргументов ключевого слова с разными значениями с одинаковым именем: я ограничил структуру своего кода, чтобы гарантировать, что аргументы ключевого слова с одним и тем же именем всегда ссылаются на что-то, что будет иметь то же самое имя. значение (что упрощает повторное использование аргументов ключевого слова). - person 2rs2ts; 10.07.2013
comment
Бездельничать с вашим первым фрагментом кода - хотя я могу переделать его сам, я хотел бы отметить, что он создает список кортежей (arg, [value]) вместо того, чтобы группировать соответствующие вместе, как мой ожидаемый результат, в выключенном состоянии шанс, что вы намеревались иначе. - person 2rs2ts; 10.07.2013
comment
@ 2rs2ts: я просто показывал вам, как привести аргументы в соответствие с их значениями по умолчанию, вы можете адаптировать его, чтобы делать с этой информацией все, что захотите. - person BrenBarn; 10.07.2013
comment
Получил это работает. Кстати, для прикола, я добился цели кода в первом блоке в этом (правда, нечитаемом) понимании списка: format = [(spec.args[:len(spec.defaults)], {k: v for k, v in zip(spec.args[len(spec.defaults):], spec.defaults)}) if spec.defaults else [(spec.args),{}] for spec in (inspect.getargspec(func) for func in funcs)]. Я могу работать над исправлением неупорядоченных аргументов ключевых слов и позиционных аргументов, помещаемых в аргументы ключевых слов, поскольку, хотя мой код соответствует этому стандарту, пользовательский ввод может не соответствовать этому слишком хорошо, но в остальном это исправило мои проблемы - спасибо! - person 2rs2ts; 10.07.2013