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

发布于 2024-10-20 13:30:57 字数 2858 浏览 3 评论 0原文

我在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

我曾想过在将上下文设置为新值之前保存上下文,但这只能解决部分问题...

确实,(等等,这很难解释),调用了父方法,包装器被执行,但它们接收寻址到 Parent.test*args**kwargs,而 self 是一个 Child 实例,因此如果我想使用 *args**kwargs 挑战它们,self 属性具有不相关的值> (例如用于自动验证目的),示例:

@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 right”类型

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

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 the method execution and after. In order (among many other things) to automatically set and clean a context variable.

In order to achieve this, I have 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

This 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 appear, 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 have thought of saving the context before setting it to a new value, but that only solves partially the problem...

Indeed, (hang on, this is hard to explain), the parent method is called, the wrappers are executed, but they receive *args and **kwargs addressed to Parent.test, while self is a Child instance, so self attributes have irrelevant values if I want to challenge them with *args and **kwargs (for example for automated validation purpose), example :

@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

So basically, to solve this problem I see two solutions :

  1. preventing the wrappers to be executed when the method was called with super(Child, self)

  2. having a self that is always of the "right" type

Both solutions seem impossible to me ... Do somebody has an idea on how to solve this ? A suggestion ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

揪着可爱 2024-10-27 13:30:57

那么,您不能只检查 _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 块中,以确保在发生异常时重置上下文。

Well, can't you just check if the context is already set in _manage_context? Like this:

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)

Also, this should probably be wrapped in a try-catch block, to ensure resetting of the context in case of exceptions.

生生漫 2024-10-27 13:30:57

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

包装代码被绕过!这很黑客,但它有效......

Actually I have found out a way to prevent the wrappers to be executed when the method was called with 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

That way, when calling :

super(Child, self).a_wrapped_method

The wrapping code is by-passed !!! That's quite hackish, but it works ...

中性美 2024-10-27 13:30:57

好吧,首先,你的“解决方案”真的很难看,但我想你知道这一点。 :-) 那么让我们尝试回答您的问题。

首先是一个隐含的“问题”:为什么不使用 Python 的上下文管理器?它们几乎免费为您提供更好的语法和错误管理。请参阅 contextlib 模块,它可以为您提供很大帮助。特别是请参阅有关重入的部分

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

  • (更干净)保留 self.context 的列表,进入上下文时附加到它,并在退出时从它弹出。这样你就总能获得你的上下文。

  • (更像你想要的)保留一个self.context,同时也是一个全局值DEPTH,进入时加一,退出时减一,当 DEPTH 为 0 时,self.context 被重置为 None。

。至于你的第二个问题,我必须说我不太明白你的意思。 self 的正确类型。如果 A 是 B 的子类,并且 self 是 A 的实例,那么它也是 B 的实例。如果 self.some_minimum 是“错误的”,无论您认为 self 是 A 的实例还是 B 的实例,这意味着 some_minimum 并不是真正的实例self 的实例属性,而是 A 或 B 的类属性。对吗? 它们在 A 和 B 上可以自由不同,因为 A 和 B 是不同的对象(属于它们的元类)。

Ok, first, your "solution" is really ugly, but I suppose you know that. :-) So let's try to answer your questions.

First is an implicit "question": why don't you use Python's context managers? They give you much nicer syntax and error management practically for free. See contextlib module, it can help you greatly. Especially see section about reentrancy.

Then you'll see that people usually have problems when trying to stack context managers. That's not surprising, since to properly support recursion you need a stack of values, not a single value. [You could see the source for some reentrant cm, for example redirect_stdout, to see how it's handled.] So your context_wrapper should either:

  • (cleaner) keep a list of self.contexts, append to it when entering context, and pop from it when exiting. That way you always get your context.

  • (more like what you want) keep a single self.context, but also a global value DEPTH, increased by one on entering, decreased by one on exiting, and self.context being reset to None when DEPTH is 0.

As for your second question, I must say I don't quite understand you. self is of the right type. If A is subclass of B, and self is instance of A, then it is also instance of B. If self.some_minimum is "wrong" whether you consider self an instance of A or of B, that means that some_minimum is not really an instance attribute of self, but a class attribute of A or B. Right? They can be freely different on A and on B, because A and B are different objects (of their metaclass).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文