首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在MRO中装饰top函数

在MRO中装饰top函数
EN

Stack Overflow用户
提问于 2021-01-29 15:47:41
回答 1查看 49关注 0票数 1

如何修饰类继承中的最后一个函数?

如果我装饰一个超类函数,那么这个子类函数会覆盖装饰器。我想知道是否有一种巧妙的方法来自动修饰MRO中的top函数。

代码语言:javascript
复制
def wrapper(f):
    def _wrap(*args, **kwargs):
        print("In wrapper")
        return f(*args, **kwargs)
    return _wrap


class A:

    @wrapper
    def f(self):
        print("In class A")


class B(A):
    def f(self):
        print("In class B")


if __name__ == '__main__':
    a = A()
    b = B()
    print("Calling A:")
    a.f()
    print("Calling B:")
    b.f()

以下是输出。正如预期的那样,B.f()不会调用包装器,尽管我希望它这样做。

代码语言:javascript
复制
Calling A:
In wrapper
In class A

Calling B:
In class B

这是我到目前为止所尝试过的。一个包含所有装饰器并在类实例化期间注入它们的元类。

代码语言:javascript
复制
from abc import ABCMeta


class WrapperMetaClass(ABCMeta):
    def __init__(cls, *args, **kwargs):
        wrappers_dict = getattr(cls, "_wrappers")
        for attr_name in dir(cls):
            if attr_name not in wrappers_dict:
                continue
            else:
                wrapper = wrappers_dict[attr_name]
            attr = getattr(cls, attr_name)
            if not hasattr(attr, '__call__'):
                raise Exception("What you're trying to wrap is not a function!")
            attr = wrapper(attr)
            setattr(cls, attr_name, attr)

        super().__init__(*args, **kwargs)

这是可行的:

代码语言:javascript
复制
class A(metaclass=WrapperMetaClass):
    _wrappers = {
        "f": wrapper
    }

    def f(self):
        print("In class A")


class B(A):
    def f(self):
        print("In class B")

输出结果就是我想要的。

代码语言:javascript
复制
Calling A:
In wrapper
In class A

Calling B:
In wrapper
In class B

然而,这遇到了一个不同的问题。如果B不覆盖f,元类将包装A.f()两次。这是有道理的,因为A和B都继承了WrapperMetaClass,所以首先包装A.f(),然后再包装B.f()。

代码语言:javascript
复制
class A(metaclass=WrapperMetaClass):
    _wrappers = {
        "f": wrapper
    }

    def f(self):
        print("In class A")


class B(A):
    pass

输出变为:

代码语言:javascript
复制
Calling A:
In wrapper
In class A

Calling B:
In wrapper
In wrapper
In class A

我不知道我还能做什么。

EN

回答 1

Stack Overflow用户

发布于 2021-01-29 19:39:57

是的,我记得我曾经遇到过一两次--你走对了路。

但首先要做的是:如果“包装器”中的逻辑可以放在基类中的方法中,那么就像user 2357112 supports monica在注释中所说的那样,在较小的任务中拆分方法,并拥有一个“方法槽”系统比这更可取。如果你发现你真的需要或者更喜欢装饰器,完整的代码如下

代码语言:javascript
复制
class A:
    def do_things(self):
        create_connection()  # <- this is the code you'd are putting in the wrapper in the other approach
        do_thing_1()

class B(A):
    def do_things(self):
        # here we have to do thing_1 and thing_2, but
        # the connection is created in the superclass method... 
        # this could be the origin of your question
        
# Refactor to:

class A:
    def do_things(self):
        create_connection()
        self.internal_do_things()
        
    def internal_do_things(self):
        do_thing_1()
        
class B(A):
    def internal_do_things(self):
        super().internal_do_things()
        do_thing_2()

因此,经典继承和面向对象解决了这个问题

如果您需要装饰器,请使用

要做的事情是让装饰器本身,“包装器”,获得一种方法来“知道”它是否已经在外部方法(即调用super()的子类中的方法)中被调用,并在这种情况下充当透明包装器。

当我们想要一个健壮的解决方案时,它会变得更复杂:一个包装器,它可以为同一个类中的不同方法工作,并且如果它们被并发调用(在不同的线程中,或者一个方法调用另一个方法,而不是super(),它应该触发包装器),也不会被混淆。

最后,这种机制非常复杂,它们不应该妨碍实际的包装器-所以,理想情况下,它们应该被构建为装饰器本身,它将装饰您的包装器。

几个小时后,如果它看起来不“整洁”,很抱歉-事实证明实现上面描述的东西比我最初想象的要复杂一些-我们需要一个中间装饰器级别(在代码中称为meta_wrapper_applier ),以便元类可以在每次重新声明方法时重新包装它们。

我希望代码和变量名中的注释足以理解这个想法:

代码语言:javascript
复制
from abc import ABCMeta
from functools import wraps
import threading


class WrapperMetaClass(ABCMeta):
    def __init__(cls, name, bases, ns, **kw):
        super().__init__(name, bases, ns, **kw)

        # Get the wrapped methods for all the superclasses
        super_wrappers = {}
        for supercls in cls.__mro__[::-1]:
            super_wrappers.update(supercls.__dict__.get("_wrappers", {}))

        # unconditionally install a wrappers dict for each subclass:
        sub_wrappers =  cls._wrappers = {}

        for attrname, attr in ns.items():
            if attrname in super_wrappers:
                # Applies the wrapper in the baseclass to the subclass method:
                setattr(cls, attrname, super_wrappers[attrname]._run_once_wrapper(attr))
            elif hasattr(attr, "_run_once_wrapper"):
                # Store the wrapper information in the cls for use of the subclasses:
                sub_wrappers[attrname] = attr


def run_once_method_decorator(original_wrapper):
    re_entering_stacks = {}

    # This is the callable used to place a wrapper on the original
    # method and on each overriden method.
    # All methods with the same name in the subclasses  will share the same original wrapper and the
    # "re_entering_stacks" data structure.
    def meta_wrapper_applier(raw_method):
        wrapped_method_in_subclass = None
        @wraps(original_wrapper)
        def meta_wrapper(*args, **kw):
            nonlocal wrapped_method_in_subclass

            # uses a plain list to keep track of re-entering the same-named method
            # in each thread:
            re_entering_stack = re_entering_stacks.setdefault(threading.current_thread(), [])
            re_entering = bool(re_entering_stack)
            try:
                re_entering_stack.append(1)
                if re_entering:
                    result = raw_method(*args, **kw)
                else:
                    if wrapped_method_in_subclass is None:
                        # Applies the original decorator lazily, and caches the result:
                        wrapped_method_in_subclass = original_wrapper(raw_method)

                    result = wrapped_method_in_subclass(*args, **kw)
            finally:
                re_entering_stack.pop()
            return result
        # registry = original_wrapper.__dict__.setdefault("_run_once_registry", {})


        meta_wrapper._run_once_wrapper = meta_wrapper_applier
        return meta_wrapper

    return meta_wrapper_applier

# From here on, example code only;

@run_once_method_decorator
def wrapper(f):
    @wraps(f)
    def _wrap(*args, **kwargs):
        print("Entering wrapper")
        result = f(*args, **kwargs)
        print("Leaving wrapper\n")
        return result
    return _wrap

@run_once_method_decorator
def other_wrapper(f):
    @wraps(f)
    def _wrap(*args, **kwargs):
        print("Entering other wrapper")
        result = f(*args, **kwargs)
        print("Leaving other wrapper\n")
        return result
    return _wrap


class A(metaclass=WrapperMetaClass):

    @wrapper
    def f(self):
        print("In class A")

    def g(self):
        print("g in A")

class B(A):
    def f(self):
        print("In class B")
        super().f()

    @other_wrapper
    def g(self):
        print("g in B")
        super().g()

class C(B):
    def g(self):
        print("g in C")
        super().g()


if __name__ == '__main__':
    a = A()
    b = B()
    print("Calling A:")
    a.f()
    a.g()
    print("Calling B:")
    b.f()
    b.g()
    print("Calling C:")
    C().g()

输出:

代码语言:javascript
复制
Calling A:
Entering wrapper
In class A
Leaving wrapper

g in A
Calling B:
Entering wrapper
In class B
In class A
Leaving wrapper

Entering other wrapper
g in B
g in A
Leaving other wrapper

Calling C:
Entering other wrapper
g in C
g in B
g in A
Leaving other wrapper
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65950552

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档