所以,除了现有的模式:我不知道这是否有一个特定的名称,你需要的是,一个"模式"是"插槽"的使用:也就是说,你记录了特殊命名的方法,这些方法将作为另一个方法执行的一部分被调用.然后,另一个方法执行其设置代码,判断slotted方法(通常可通过名称识别)是否存在,用简单的方法调用它们,这将运行它的最专门版本,即使调用slots的特殊方法在基类中,并且您处于大类继承层次 struct 中.
这种模式的一个简单例子是Python实例化对象的方式:使用与函数调用(MyClass()
)相同的语法调用类时,实际调用的是该类的class(其元类)__call__
方法.(通常为type.__call__
).在Python的type.__call__
代码中,调用类'__new__
方法,然后调用类'__init__
方法,最后返回第一次调用返回到__new__
的值.自定义元类可以修改__call__
,以便在这两个调用之前、之间或之后运行它想要的任何代码.
因此,如果这不是Python,那么您只需要对其进行详细说明,并记录这些方法不应该直接调用,而是通过一个"入口点"方法来调用——该方法可以简单地使用一个"ep_2;"前缀.这些必须在基类上进行固定和硬编码,并且每个方法都需要一个前缀/后缀代码.
class Base(ABC):
def ep_first_method(self, *args, **kw);
# prefix code...
ret_val = self.first_method(*args, **kw)
# postfix code...
return ret_val
@abstractmethod
def first_method(self):
pass
class Child(Base):
def first_method(self, ...):
...
由于是Python,所以更容易添加一些魔法来避免代码重复并保持简洁.
一种可能的方法是使用一个特殊的类,当在一个子类中检测到一个应该作为包装器方法的插槽调用的方法时,如上图所示,自动重命名that方法:这样入口点方法可以与子方法具有相同的名称——更妙的是,一个简单的修饰符可以标记打算作为"入口点"的方法,遗产甚至对他们有用.
基本上,在构建一个新类时,我们会判断所有方法:如果其中任何一个方法在调用层次 struct 中有一个对应的部分标记为入口点,则会进行重命名.
如果任何入口点方法都将作为第二个参数(第一个参数为self
),则更实用,这是要调用的时隙方法的参考.
经过一些修改:好消息是不需要custommetaclass——基类中的__init_subclass__
个特殊方法足以启用decorator .
坏消息是:由于在入口点中由对最终方法的"super()"的潜在调用触发的重新进入迭代,因此需要一种在中间类中调用原始方法的复杂启发式方法.我还注意了一些多线程保护——尽管这不是100%防弹的.
import sys
import threading
from functools import wraps
def entrypoint(func):
name = func.__name__
slotted_name = f"_slotted_{name}"
recursion_control = threading.local()
recursion_control.depth = 0
lock = threading.Lock()
@wraps(func)
def wrapper(self, *args, **kw):
slotted_method = getattr(self, slotted_name, None)
if slotted_method is None:
# this check in place of abstractmethod errors. It is only raised when the method is called, though
raise TypeError("Child class {type(self).__name__} did not implement mandatory method {func.__name__}")
# recursion control logic: also handle when the slotted method calls "super",
# not just straightforward recursion
with lock:
recursion_control.depth += 1
if recursion_control.depth == 1:
normal_course = True
else:
normal_course = False
try:
if normal_course:
# runs through entrypoint
result = func(self, slotted_method, *args, **kw)
else:
# we are within a "super()" call - the only way to get the renamed method
# in the correct subclass is to recreate the callee's super, by fetching its
# implicit "__class__" variable.
try:
callee_super = super(sys._getframe(1).f_locals["__class__"], self)
except KeyError:
# callee did not make a "super" call, rather it likely is a recursive function "for real"
callee_super = type(self)
slotted_method = getattr(callee_super, slotted_name)
result = slotted_method(*args, **kw)
finally:
recursion_control.depth -= 1
return result
wrapper.__entrypoint__ = True
return wrapper
class SlottedBase:
def __init_subclass__(cls, *args, **kw):
super().__init_subclass__(*args, **kw)
for name, child_method in tuple(cls.__dict__.items()):
#breakpoint()
if not callable(child_method) or getattr(child_method, "__entrypoint__", None):
continue
for ancestor_cls in cls.__mro__[1:]:
parent_method = getattr(ancestor_cls, name, None)
if parent_method is None:
break
if not getattr(parent_method, "__entrypoint__", False):
continue
# if the code reaches here, this is a method that
# at some point up has been marked as having an entrypoint method: we rename it.
delattr (cls, name)
setattr(cls, f"_slotted_{name}", child_method)
break
# the chaeegs above are inplace, no need to return anything
class Parent(SlottedBase):
@entrypoint
def meth1(self, slotted, a, b):
print(f"at meth 1 entry, with {a=} and {b=}")
result = slotted(a, b)
print("exiting meth1\n")
return result
class Child(Parent):
def meth1(self, a, b):
print(f"at meth 1 on Child, with {a=} and {b=}")
class GrandChild(Child):
def meth1(self, a, b):
print(f"at meth 1 on grandchild, with {a=} and {b=}")
super().meth1(a,b)
class GrandGrandChild(GrandChild):
def meth1(self, a, b):
print(f"at meth 1 on grandgrandchild, with {a=} and {b=}")
super().meth1(a,b)
c = Child()
c.meth1(2, 3)
d = GrandChild()
d.meth1(2, 3)
e = GrandGrandChild()
e.meth1(2, 3)