Вызов функции с dict в качестве параметра - PullRequest
1 голос
/ 24 марта 2020

В настоящее время я работаю над интерфейсом командной строки с argparse. Я получаю проанализированные аргументы как Namespace объект от arg_parse. В основном аргументы, поступающие в форме CLI, должны быть переданы до 2 функций. Теперь я ищу умный способ разделить Namespace так, чтобы я мог просто использовать распаковку.

Следующий пример должен проиллюстрировать то, что мне нужно:

import argparse
ns1 = argparse.Namespace(foo=1)
ns2 = argparse.Namespace(bar=2)
ns3 = argparse.Namespace(foo=1, bar=2)

def f(foo): return foo
def g(bar): return 10 * bar

f(**vars(ns1)) # works
g(**vars(ns2)) # works
f(**vars(ns3)) # does not work, 'bar' is not an argument of f

Я мог бы усердно закодируйте фильтр как в

f_args = ["foo"]
g_args = ["bar"]

f(**{k:v for k, v in vars(ns3).items() if k in f_args}) # works
g(**{k:v for k, v in vars(ns3).items() if k in g_args}) # works

Но это может привести к ошибкам (если сигнатура f изменится, я должен также помнить об изменении f_args). Однако я мог бы использовать inspect.getfullargspec для автоматизации этой части, как в:

import inspect
f_args = inspect.getfullargspec(f).args

Но мне все это кажется хаки sh, и мне было интересно, не упускаю ли я из виду легкий образец для этого?


Обновление

Как указал @ doer_uv c, одним из подходов будет обработка моих функций с помощью параметра "catch all" как в:

def f(foo, **kwargs): return foo

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

Ответы [ 2 ]

2 голосов
/ 25 марта 2020

Сокращений для этого нет. Тем не менее, можно легко построить это, используя inspect.signature - это не хаки, Python подписи могут быть очень сложными и inspect существует только для таких целей.

import inspect


def apply(call: 'Callable', kwargs: 'Dict[str, Any]'):
    """Apply all appropriate ``kwargs`` to ``call``"""
    call_kwargs = inspect.signature(call).parameters.keys()
    matching_kwargs = {name: kwargs[name] for name in kwargs.keys() & call_kwargs}
    return call(**matching_kwargs)

Это проверяет параметры, ожидаемые call и выбирает те, которые поставляются kwargs. Если kwargs пропускает какие-либо параметры для call, выдается стандартное исключение пропущенных параметров.

>>> def f(foo):
...     return foo
...
>>> apply(f, {'foo': 2, 'bar': 3})
2
>>> apply(f, {'baz': 2, 'bar': 3})
…
TypeError: f() missing 1 required positional argument: 'foo'
1 голос
/ 24 марта 2020

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

class MissingArgumentError(ValueError):
    pass


def f(**kwargs):
    if not 'foo' in kwargs:
        raise MissingArgumentError('Missing keyword argument `foo`')
    return kwargs['foo']


def g(**kwargs):
    if not 'bar' in kwargs:
       raise MissingArgumentError('Missing keyword argument `bar`')
    return 10 * kwargs['bar']

В этом методе, даже если сигнатура вашей функции изменяется, ничего иначе нужно изменить.


Если вам нужно написать несколько таких функций, вместо того, чтобы повторять логи c проверки аргументов, вы можете использовать decorators.

def argverifier(compulsory_args:list):
    def actual_decorator(function):
        def inner(**kwargs):
            for arg in compulsory_args:
                if arg not in kwargs:
                    raise MissingArgumentError(f'Missing keyword argument : {arg}')
            return function(**kwargs)
        return inner
    return actual_decorator

@argverifier(compulsory_args=['foo'])
def f(**kwargs): return kwargs['foo']

@argverifier(compulsory_args=['bar'])
def g(**kwargs): return 10 * kwargs['bar']

Если вы не хотите sh изменить сигнатуру функции, жизнеспособным решением будет написать функцию, которая извлекает аргументы, используя модуль inspect, как вы предложили.

from functools import partial
import inspect

def f(foo): return foo
def g(bar): return 10 * bar

def argextractor(args, kwargs):
    return {k:v for k, v in kwargs.items() if k in args}

f_argextractor = partial(argextractor, inspect.getfullargspec(f).args) 
g_argextractor = partial(argextractor, inspect.getfullargspec(g).args)

f(**f_argextractor(vars(ns3)))
g(**g_argextractor(vars(ns3)))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...