Метапрограммирование Python: генерировать сигнатуру функции с аннотацией типа - PullRequest
0 голосов
/ 17 мая 2018

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

Поэтому я ищу способ генерации функций с аннотациями типов из параметров, переданных генерирующей функции.:

def gen_fn(args: Dict[str, Any]) -> Callable:
    def new_fn(???):
        pass
    return new_fn

, чтобы

inspect.signature(gen_fn({'a': int}))

вернул

<Signature (a:int)>

Есть ли что-то, что я могу поставить вместо ???, что будет делать то, что япотребность.

Я также посмотрел на Signature.replace() в модуле inspect, но не нашел способа присоединить новую подпись к новой или существующей функции.

Я не решаюсь использовать ast , потому что:

Сам абстрактный синтаксис может меняться с каждым выпуском Python

Так что мой вопрос: Какой (если таковой имеется) разумный способ создания функции с аннотацией типа Python 3 на основе dict, переданного генерирующей функции?


Редактировать: while @ Aran-Fey решение ответить на мой вопроспрямо, похоже, мое предположение было неверным.Изменение подписи не позволяет звонить на new_fn с использованием новой подписи.То есть gen_fn({'a': int})(a=42) вызывает TypeError: ... `получил неожиданный аргумент ключевого слова 'a'.

1 Ответ

0 голосов
/ 17 мая 2018

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

  • inspect.signature проверяет наличие атрибута __signature__, прежде чем проверяет фактическую сигнатуру функции, поэтому мы можем создать соответствующий объект inspect.Signature и назначьте его там:

    params = [inspect.Parameter(param,
                                inspect.Parameter.POSITIONAL_OR_KEYWORD,
                                annotation=type_)
                            for param, type_ in args.items()]
    new_fn.__signature__ = inspect.Signature(params)
    
  • typing.get_type_hints не не с уважением __signature__, поэтому мы должны также обновить атрибут __annotations__:

    new_fn.__annotations__ = args
    

Соединяя их вместе:

def gen_fn(args: Dict[str, Any]) -> Callable:
    def new_fn():
        pass

    params = [inspect.Parameter(param,
                                inspect.Parameter.POSITIONAL_OR_KEYWORD,
                                annotation=type_)
                            for param, type_ in args.items()]
    new_fn.__signature__ = inspect.Signature(params)
    new_fn.__annotations__ = args

    return new_fn

print(inspect.signature(gen_fn({'a': int})))  # (a:int)
print(get_type_hints(gen_fn({'a': int})))  # {'a': <class 'int'>}

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

Вы можете определить функцию с помощью varargs , чтобы объединить все аргументы в кортеж и dict:

def new_fn(*args, **kwargs):
    ...

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

...