Python: metaclass + wrapped methods + inheritance = problems

I have a problem in Python for which I cannot find any clean solution ...

When calling some methods, I want to execute some code before and after the method is executed. To (among many others) automatically set and clear a variable context.

To achieve this, I declared the following metaclass:

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

It works like a charm:

class Parent(object):

    __metaclass__ = MyType

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

But as soon as I want to subclass Parent, problems arise when I call the parent method with 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

I thought about saving the context before setting it to a new value, but this partially solves the problem ...

, (, ), , , *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

, , :

  • super(Child, self)

  • self, ""

... - , ? ?

+5
3

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

!!! , ...

0

, , _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, .

+1

, -, "" , , , .:-) .

-, "": Python? . . contextlib, . .

, . , , . [ , redirect_stdout, , .] , context_wrapper :

  • () self.context s, . , .

  • ( , ), self.context, DEPTH, , , self.context - reset None, DEPTH 0.

As for your second question, I must say that I do not quite understand you. selfhas the correct type. If A is a subclass of B, and self is an instance of A, then it is also an instance of B. If self.some_minimum is "wrong", do you consider yourself an instance of A or B, this means that some_minimum is not really an attribute of the instance of self, but an attribute of class A or B. Correct? They can freely differ from A and B, because A and B are different objects (of their metaclass).

0
source

All Articles