Получить Kwargs внутри функции - PullRequest
20 голосов
/ 18 января 2010

Если у меня есть такая функция python:

def some_func(arg1, arg2, arg3=1, arg4=2):

Есть ли способ определить, какие аргументы были переданы по ключевому слову изнутри функции?

EDIT

Для тех, кто спрашивает, зачем мне это нужно, у меня нет реальной причины, это возникло в разговоре, и любопытство одолело меня.

Ответы [ 6 ]

16 голосов
/ 18 января 2010

Нет, в коде Python нет способа сделать это с этой подписью - если вам нужна эта информация, вам нужно изменить подпись функции.

Если вы посмотрите на API-интерфейс Python C, то увидите, что фактический способ передачи аргументов в обычную функцию Python всегда является кортежем плюс диктовкой, то есть способом, который является прямым отражением сигнатуры *args, **kwargs. Затем этот кортеж и dict разбираются на конкретные позиционные аргументы и аргументы, которые названы в сигнатуре, даже если они были переданы по имени, а *a и **kw, если они присутствуют, принимают только «переполнение» из этого анализа, если есть - только в этот момент ваш Python-код получает контроль, и к тому времени запрашиваемая вами информация ( как были переданы различные аргументы) больше не существует.

Таким образом, чтобы получить запрошенную вами информацию, измените подпись на *a, **kw и выполните собственный анализ / проверку - это происходит «от яйца до омлета», то есть определенное количество работы, но, безусловно, выполнимое, в то время как то, что вы ищете, будет идти «от омлета к яйцу» ... просто неосуществимо; -).

12 голосов
/ 18 января 2010

Вот мое решение с помощью декораторов:

def showargs(function):
    def inner(*args, **kwargs):
        return function((args, kwargs), *args, **kwargs)
    return inner

@showargs
def some_func(info, arg1, arg2, arg3=1, arg4=2):
    print arg1,arg2,arg3,arg4
    return info

In [226]: some_func(1,2,3, arg4=4)
1 2 3 4
Out[226]: ((1, 2, 3), {'arg4': 4})

Возможно, есть способ убрать это дальше, но это кажется мне минимально навязчивым и не требует никаких изменений в вызывающем коде.

Редактировать: Чтобы на самом деле проверить, были ли определенные аргументы переданы по ключевому слову, затем сделать что-то вроде следующего внутри some_func:

args, kwargs = info
if 'arg4' in kwargs:
    print "arg4 passed as keyword argument"

Отказ от ответственности: вам, вероятно, следует подумать, действительно ли вас волнует, как были переданы аргументы. Весь этот подход может быть ненужным.

3 голосов
/ 18 января 2010

Вам в значительной степени придется пересмотреть свою функцию:

def some_func(*args, **kwargs):

и сделай маршалинг сам. Невозможно определить разницу между передачей по позиции, передачей по ключевому слову и значением по умолчанию.

1 голос
/ 09 марта 2019

Есть ли способ определить, какие аргументы были переданы ключевым словом изнутри функции?

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

Код

Вариант 1 - locals()

def f(a, b=1, c="1"):
    print(locals())


f(0)
# {'c': '1', 'b': 1, 'a': 0}

Вариант 2 - Частичные подсказки типа *

def g(a, b:int=1, c:float="1"):
    pass


keys = g.__annotations__
values = g.__defaults__

dict(zip(keys, values))
# {'b': 1, 'c': '1'}

Вариант 3 - Полные подсказки типа *

def h(a:float, b:int=1, c:str="1") -> int:
    return 0


keys = reversed(list(filter(lambda x: x != "return", h.__annotations__)))
values = reversed(h.__defaults__)

{k: v for k, v in zip(keys, values) if k != "return"}
# {'c': '1', 'b': 1}

Примечание: ни один из этих вариантов не является особенно Pythonic, но они демонстрируют потенциал.


Детали

  1. locals() зависит от вызова функции. Результаты должны иметь значения по умолчанию, но они изменяются со значениями, переданными в вызов, например, f(0) против f(0 2, 3)
  2. Подсказки типа "Частично" означают, что аннотируются только ключевые параметры. Добавление любых других аннотаций не будет работать с этим наивным подходом.
  3. «Полный» или полный тип подсказок может включать в себя другие параметры. Мы повторяем в обратном порядке, чтобы избежать архивирования значений с необязательными позиционными параметрами. Поскольку аннотация "return" является необязательной, мы фильтруем ее во время итерации.

* Эти параметры зависят от подсказок типа и сохранения порядка вставки ключей (Python 3.6+). Они дают только значения по умолчанию и не изменяются со значениями вызова функции. Подсказки к типам являются необязательными прямо сейчас в Python, поэтому их следует использовать с осторожностью в производственном коде.


Предложение

Я бы использовал только последние подходы для отладки или быстрой проверки подписи функции. На самом деле, учитывая аргументы только для ключевых слов, можно использовать inspect.getargspec() для захвата kwonlydefaults dict.

def f(a, *, b=1, c="1"):
    pass


spec = inspect.getfullargspec(f)
spec
# FullArgSpec(args=['a'], varargs=None, varkw=None, defaults=None, 
#             kwonlyargs=['b', 'c'], kwonlydefaults={'b': 1, 'c': '1'}, 
#             annotations={})

spec.kwonlydefaults
# {'b': 1, 'c': '1'}

В противном случае объедините некоторые приемы с атрибутами args и defaults FullArgSpec:

def get_keywords(func):
    """Return a dict of (reversed) keyword arguments from a function."""
    spec = inspect.getfullargspec(func)
    keys = reversed(spec.args)
    values = reversed(spec.defaults)
    return {k: v for k, v in zip(keys, values)}


get_keywords(f)
# {'c': '1', 'b': 1}
1 голос
/ 18 января 2010

Просто сделайте это так:

def some_func ( arg1, arg2, arg3=None, arg4=None ):
    if arg3 is None:
        arg3 = 1 # default value
    if arg4 is None:
        arg4 = 2 # default value

    # do something

Таким образом, вы можете видеть, когда что-то установлено, и , вы также можете работать с более сложными структурами по умолчанию (такими как списки), не сталкиваясь с такими проблемами:

>>> def test( arg=[] ):
        arg.append( 1 )
        print( arg )
>>> test()
[1]
>>> test()
[1, 1]
1 голос
/ 18 января 2010

Вы хотите знать, был ли arg3 1, потому что он был передан извне или потому что это было значение по умолчанию? Нет, я не знаю, как это сделать. Подозреваю, что главная причина в том, что такие знания не нужны. Обычно делается следующее:

>>> def func(a, b=None):
    if b is None:
# here we know that function was called as:
# func('spam') or func('spam', None) or func('spam', b=None) or func(a='spam', b=None)

        b = 42
...