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

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

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

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

3 ответа

Решение

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

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

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

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

try:
    callback(first, second)
except TypeError as e:
    if e.__traceback__.tb_frame.f_code.co_name != 'func_name':
        raise
    callback(first)

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

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])
Другие вопросы по тегам