如何修饰类继承中的最后一个函数?
如果我装饰一个超类函数,那么这个子类函数会覆盖装饰器。我想知道是否有一种巧妙的方法来自动修饰MRO中的top函数。
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我不知道我还能做什么。
发布于 2021-01-29 19:39:57
是的,我记得我曾经遇到过一两次--你走对了路。
但首先要做的是:如果“包装器”中的逻辑可以放在基类中的方法中,那么就像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()因此,经典继承和面向对象解决了这个问题
如果您需要装饰器,请使用:
要做的事情是让装饰器本身,“包装器”,获得一种方法来“知道”它是否已经在外部方法(即调用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 wrapperhttps://stackoverflow.com/questions/65950552
复制相似问题