Python: метакласс + упакованные методы + наследование = проблемы - PullRequest
5 голосов
/ 07 марта 2011

У меня есть проблема в Python, для которой я не могу найти никакого чистого решения ...

При вызове некоторых методов я хочу выполнить некоторый код до выполнения метода и после.Для того, чтобы (помимо всего прочего) автоматически установить и очистить переменную context.

Для этого я объявил следующий метакласс:

class MyType(type):
    def __new__(cls, name, bases, attrs):
        #wraps the 'test' method to automate context management and other stuff
        attrs['test'] = cls.other_wrapper(attrs['test'])
        attrs['test'] = cls.context_wrapper(attrs['test'])
        return super(MyType, cls).__new__(cls, name, bases, attrs)

    @classmethod
    def context_wrapper(cls, operation):
        def _manage_context(self, *args, **kwargs):
            #Sets the context to 'blabla' before the execution
            self.context = 'blabla'
            returned = operation(self, *args, **kwargs)
            #Cleans the context after execution
            self.context = None
            return returned
        return _manage_context

    @classmethod
    def other_wrapper(cls, operation):
        def _wrapped(self, *args, **kwargs):
            #DO something with self and *args and **kwargs
            return operation(self, *args, **kwargs)
        return _wrapped

Это работает следующим образомОчарование:

class Parent(object):

    __metaclass__ = MyType

    def test(self):
        #Here the context is set:
        print self.context #prints blabla

Но как только я хочу создать подкласс Parent, возникают проблемы, когда я вызываю родительский метод с помощью super:

class Child(Parent):
    def test(self):
        #Here the context is set too
        print self.context #prints blabla
        super(Child, self).test()
        #But now the context is unset, because Parent.test is also wrapped by _manage_context
        #so this prints 'None', which is not what was expected
        print self.context

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

Действительно, (подождите, это трудно объяснить), вызывается родительский метод, оберткивыполняется, но они получают *args и **kwargs, адресованные Parent.test, в то время как self является экземпляром Child, поэтому атрибуты self имеют нерелевантные значения, если я хочу проверить их с помощью *args и **kwargs (например, для целей автоматической проверки), пример:

@classmethod
def validation_wrapper(cls, operation):
    def _wrapped(self, *args, **kwargs):
        #Validate the value of a kwarg
        #But if this is executed because we called super(Child, self).test(...
        #`self.some_minimum` will be `Child.some_minimum`, which is irrelevant
        #considering that we called `Parent.test`
        if not kwarg['some_arg'] > self.some_minimum:
            raise ValueError('Validation failed')
        return operation(self, *args, **kwargs)
    return _wrapped

Таким образом, в основном, чтобы решить эту проблему, я вижу два решения:

  1. , предотвращающих использование оболочеквыполняется, когда метод вызывается с super(Child, self)

  2. с self, который всегда имеет «правильный» тип

Оба решения кажутся мне невозможными ...у кого-нибудь есть идеи как это решить?Предложение?

Ответы [ 3 ]

1 голос
/ 07 марта 2011

Ну, а вы не можете просто проверить, установлен ли контекст в _manage_context?Например:

def _manage_context(self, *args, **kwargs):
    #Sets the context to 'blabla' before the execution
    if self.context is None:
        self.context = 'blabla'
        returned = operation(self, *args, **kwargs)
        #Cleans the context after execution
        self.context = None
        return returned
    else:
        return operation(self, *args, **kwargs)

Кроме того, это, вероятно, следует поместить в блок try-catch, чтобы обеспечить сброс контекста в случае исключений.

0 голосов
/ 18 июля 2015

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

Первый - это неявный «вопрос»: почему вы не используете менеджеры контекста Python? Они дают вам гораздо приятнее синтаксис и управление ошибками практически бесплатно. Посмотрите модуль contextlib, он может вам сильно помочь. Особенно смотрите раздел о повторном входе .

Тогда вы увидите, что у людей обычно возникают проблемы при попытке собрать менеджеры контекста. Это неудивительно, поскольку для правильной поддержки рекурсии необходим стек значений, а не одно значение. [Вы можете увидеть источник для некоторого входящего cm, например redirect_stdout, чтобы увидеть, как он обрабатывается.] Так что ваш context_wrapper должен либо:

  • (очиститель) сохраняет список self.context с, добавляет его при входе в контекст и извлекает из него при выходе. Таким образом, вы всегда получаете свой контекст.

  • (больше похоже на то, что вы хотите) оставьте единичное self.context, но также и глобальное значение DEPTH, увеличенное на единицу при входе, уменьшенное на единицу при выходе, а self.context сбрасывается в None при ГЛУБИНА - 0.

Что касается вашего второго вопроса, я должен сказать, что не совсем вас понимаю. self - это правильного типа. Если A является подклассом B, а self является экземпляром A, то это также является экземпляром B. Если self.some_minimum является «неправильным», считаете ли вы себя экземпляром A или B, это означает, что some_minimum на самом деле не является Атрибут экземпляра self, но атрибут класса A или B. Они могут свободно различаться на A и B, потому что A и B - это разные объекты (их метакласса).

0 голосов
/ 07 марта 2011

На самом деле я нашел способ предотвратить запуск оболочек при вызове метода с помощью super(Child, self):

class MyType(type):
    def __new__(cls, name, bases, attrs):
        #wraps the 'test' method to automate context management and other stuff
        new_class = super(MyType, cls).__new__(cls, name, bases, attrs)
        new_class.test = cls.other_wrapper(new_class.test, new_class)

    @classmethod
    def other_wrapper(cls, operation, new_class):
        def _wrapped(self, *args, **kwargs):
            #DO something with self and *args and **kwargs ...
            #ONLY if self is of type *new_class* !!!
            if type(self) == new_class:
                pass #do things
            return operation(self, *args, **kwargs)
        return _wrapped

Таким образом, при вызове:

super(Child, self).a_wrapped_method

Код упаковки обойден !!!Это довольно глупо, но работает ...

...