Передавать дополнительные необязательные аргументы обратным вызовам, не нарушая существующие обратные вызовы - PullRequest
0 голосов
/ 09 января 2019

У меня есть метод API, который принимает обратный вызов. Обратный вызов ожидает один аргумент.

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

Я знаю, что это можно сделать с помощью inspect. Мне интересно, есть ли «идиоматическое» или обычно используемое решение, которое не так уж и тяжело.

Ответы [ 3 ]

0 голосов
/ 09 января 2019

Более простым решением было бы использование блока try, чтобы попытаться вызвать обратный вызов сначала со вторым аргументом, прежде чем вернуться к вызову с одним аргументом в блоке except:

try:
    callback(first, second)
except TypeError as e:
    if e.__traceback__.tb_frame.f_code.co_name != 'func_name':
        raise
    callback(first)
0 голосов
/ 09 января 2019

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

if callback.__code__.co_argcount == 2:
    callback(arg1, arg2)
else:
    callback(arg1)

Этот код не проверен, но он должен работать.

0 голосов
/ 09 января 2019

Использование функции-оболочки:

from inspect import signature, Parameter

def ignore_extra_arguments(function):
    positional_count = 0
    var_positional = False
    keyword_names = set()
    var_keyword = False

    for p in signature(function).parameters.values():
        if p.kind == Parameter.POSITIONAL_ONLY:
            positional_count += 1
        elif p.kind == Parameter.POSITIONAL_OR_KEYWORD:
            positional_count += 1
            keyword_names.add(p.name)
        elif p.kind == Parameter.VAR_POSITIONAL:
            var_positional = True
        elif p.kind == Parameter.KEYWORD_ONLY:
            keyword_names.add(p.name)
        elif p.kind == Parameter.VAR_KEYWORD:
            var_keyword = True

    if var_positional:
        new_args = lambda args: args
    else:
        new_args = lambda args: args[:positional_count]

    if var_keyword:
        new_kwargs = lambda kwargs: kwargs
    else:
        new_kwargs = lambda kwargs: {
            name: value for name, value in kwargs.items()
            if name in keyword_names
        }

    def wrapped(*args, **kwargs):
        return function(
            *new_args(args),
            **new_kwargs(kwargs)
        )

    return wrapped

Это работает, но немного грубо.

Более простая версия, при условии, что function не имеет ключевого слова или переменных параметров:

from inspect import signature

def ignore_simple(function):
    count = len(signature(function).parameters)
    return lambda *args: function(*args[:count])
...