Python:元类+包装方法+继承=问题

时间:2011-03-07 12:38:03

标签: python inheritance metaclass

我在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,而selfChild个实例,因此如果我想使用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

基本上,为了解决这个问题,我看到了两个解决方案:

  1. 阻止在使用{{1}}

  2. 调用方法时执行包装器
  3. {{1}}始终为“正确”类型

  4. 这两种解决方案对我来说都是不可能的......有人对如何解决这个问题有所了解吗?一个建议?

3 个答案:

答案 0 :(得分:1)

那么,你不能只检查_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 :(得分:0)

实际上我找到了一种方法来阻止在使用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

包装代码被绕过!!!这是相当hackish,但它的确有效...

答案 2 :(得分:0)

好的,首先,你的"解决方案"真的很难看,但我想你知道的。 :-)所以,让我们试着回答你的问题。

首先是一个隐含的"问题":为什么不使用Python的上下文管理器?它们实际上是免费提供更好的语法和错误管理。请参阅contextlib模块,它可以帮助您。特别是section about reentrancy

然后,您会发现人们在尝试堆叠上下文管理器时通常会遇到问题。这并不奇怪,因为要正确支持递归,您需要一堆值,而不是单个值。 [你可以看到一些重入cm的源代码,例如redirect_stdout,看看它是如何处理的。]所以你的context_wrapper应该:

  • (清洁)保留self.context的列表,在输入上下文时附加到它,并在退出时从中弹出。这样你总能得到你的背景。

  • (更像是你想要的)保持一个self.context,但也是一个全局值DEPTH,在进入时增加1,在退出时增加1,self.context为当DEPTH为0时,重置为无。

至于你的第二个问题,我必须说我不太了解你。 self 的正确类型。如果A是B的子类,而self是A的实例,那么它也是B的实例。如果self.some_minimum是"错误"你是否认为self是A或B的实例,这意味着some_minimum实际上不是self的实例属性,而是A或B的class属性。对吗? 他们在A和B上可以自由地不同,因为A和B是不同的对象(他们的元类)。