MRO中top函数的装饰

2024-06-26 11:03:23 发布

您现在位置:Python中文网/ 问答频道 /正文

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

如果我装饰一个超类函数,子类函数将覆盖装饰器。 我想知道是否有一种简洁的方法可以自动装饰MRO中的顶部功能

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()不调用包装器,尽管我希望它调用

Calling A:
In wrapper
In class A

Calling B:
In class B

以下是我迄今为止所做的尝试。一个元类,它保存所有的装饰器并在类实例化期间注入它们

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)

这项工作:

class A(metaclass=WrapperMetaClass):
    _wrappers = {
        "f": wrapper
    }

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


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

输出就是我想要的

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()再次被包装

class A(metaclass=WrapperMetaClass):
    _wrappers = {
        "f": wrapper
    }

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


class B(A):
    pass

输出变为:

Calling A:
In wrapper
In class A

Calling B:
In wrapper
In wrapper
In class A

我不知道我还能做什么


Tags: nameinselfdefargs装饰wrapperkwargs
1条回答
网友
1楼 · 发布于 2024-06-26 11:03:23

是的,我记得我曾经面对过一两次这样的情况——而你正走在正确的道路上

但首先,如果“包装器”中的逻辑是 可以放在基类中的方法中,然后分解这些方法 在较小的任务中,并有一个“方法槽”系统更可取, 正如user 2357112 supports monica在评论中所说的那样。如果你发现你真的需要或更喜欢装饰,完整的代码如下

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

因此,经典继承和OO解决了这个问题


如果您需要装饰师安威:

要做的事情是让装饰器本身,即“包装器”,得到 一种“知道”是否已在外部方法(即方法)中调用的方法 在一个调用super())的子类中,只作为一个透明的 在这种情况下,包装

当我们需要一个健壮的解决方案时,它会变得更加复杂: 可用于同一类中不同方法的包装器, 如果同时调用它们,则不会感到混淆 (在不同的线程中,或在调用另一个方法的方法中, 不是super(),这将触发包装器)

最后,其机制非常复杂 它们不应该妨碍你的实际包装-所以, 理想情况下,他们应该作为一个装饰自己,这将 装饰你的包装

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

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

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

输出:

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

相关问题 更多 >