Проектирование обратных вызовов методом «перегрузка» в Python - PullRequest
1 голос
/ 20 мая 2011

Я разрабатываю API проверки, где обратные вызовы используются для проверки значений.Существует два варианта сигнатур обратного вызова:

def check(self, value):
    pass

def check(self, value, domain_object):          
    pass

Пример вызова реализаций обратного вызова:

for constraint in constraints:
    constraint.check(value) 
    # or constraint.check(value, domain_object) depending on the implementation

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

Было бы лучше

  • всегда использовать подпись с тремя аргументами: check(self, value, domain_object) или
  • использовать другое имя, например check_with_domain_object для второго случая?

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

Ответы [ 3 ]

2 голосов
/ 20 мая 2011

Мне нравится ответ @ Space_C0wb0y, он похож на код, присланный мне Рэймондом Хеттингером для решения аналогичной ситуации в pyparsing (см. Ниже).Для вашего простого случая попробуйте использовать этот класс нормализатора, чтобы обернуть заданные обратные вызовы:

class _ArityNormalizer(object):
    def __init__(self, fn):
        self.baseFn = fn
        self.wrapper = None

    def __call__(self, value, domain_object):
        if self.wrapper is None:
            try:
                self.wrapper = self.baseFn
                return self.baseFn(value, domain_object)
            except TypeError:
                self.wrapper = lambda v,d: self.baseFn(v)
                return self.baseFn(value)
        else:
            return self.wrapper(value, domain_object)

Ваш код теперь может обернуть обратные вызовы в _ArityNormalizer, и во время обратного вызова всегда вызывать с 2 аргументами._ArityNormalizer выполнит вызов методом проб и ошибок "с 2 аргументами", а если это не удастся, вызовет логику с 1 аргументом "только один раз, и с этого момента он перейдет непосредственно к правильной форме.

В режиме pyparsing,Я хотел поддержать обратные вызовы, которые могут быть определены как принимающие 0, 1, 2 или 3 аргумента, и написал код, который обернул бы вызываемую функцию одним из нескольких декораторов в зависимости от того, какой была сигнатура функции обратного вызова.Таким образом, во время выполнения / обратного вызова я просто всегда вызывал бы с 3 аргументами, и декоратор позаботился о том, чтобы сделать фактический вызов с правильным числом аргументов.

Мой код делал много хрупких / непереносимых / чувствительных к версии интроспекций подписи, чтобы сделать это (звучит как то, что сейчас делает OP), пока Raymond Hettinger не прислал мне хороший метод обрезки арности, который делаетпо сути, то, что предлагает ответ @ Space_C0wb0y.В коде RH использовалась очень аккуратная оболочка декоратора с нелокальной переменной, чтобы записать арность успешного вызова, так что вам придется проходить пробу и ошибку только один раз, а не каждый раз, когда вы вызываете обратный вызов.Вы можете увидеть его код в pyparsing SVN-репозитории на SourceForge, в функции _trim_arity - обратите внимание, что его код имеет варианты Py2 / Py3 из-за использования ключевого слова "nonlocal".

_ArityNormalizer Код выше был вдохновлен кодом Р.Х., прежде чем я полностью понял магию его кода.

2 голосов
/ 20 мая 2011

Самый идиоматичный способ - сначала попробовать с двумя аргументами, а в случае неудачи - с одним:

try:
    callback(value_param, domain_object_param)
except TypeError:
    callback(value_param)
0 голосов
/ 24 мая 2011
Идея

"Space_C0wb0y" использовать try ... except TypError выглядит неплохо, но мне не нравится тот факт, что это может поглотить другие исключения. Предложение Пола Макгуайра с _ArityNormalizer по сути то же самое с хорошим интерфейсом.

В конце концов я решил сохранить все как можно более простыми и объектно-ориентированными и всегда использовать два параметра, даже если будет несколько случаев, когда второй параметр не будет использоваться:

Сторона реализации:

def check(self, value, domain_object):          
    pass

вызывающая сторона:

constraint.check(value, domain_object)
...