情況是這樣的,我有一個抽象類和幾個實作它的子類。
class Parent(metaclass=ABCMeta):
@abstract_method
def first_method(self, *args, **kwargs):
raise NotImplementedError()
@abstract_method
def second_method(self, *args, **kwargs):
raise NotImplementedError()
class Child(Parent):
def first_method(self, *args, **kwargs):
print('First method of the child class called!')
def second_method(self, *args, **kwargs):
print('Second method of the child class called!')
我的目標是制作某種裝飾器,它將用于 Parent 類的任何子類的方法。我需要這個,因為每個方法在實際做某事之前都會做一些準備,而這種準備在 Parent 類的所有子類的所有方法中都是完全相同的。像:
class Child(Parent):
def first_method(self, *args, **kwargs):
print('Preparation!')
print('First method of the child class called!')
def second_method(self, *args, **kwargs):
print('Preparation!')
print('Second method of the child class called!')
我首先想到的是使用父類方法實作:只需洗掉“raise NotImplementedError()”并添加一些功能,然后在子類中呼叫,例如 super().first_method(self, *args , **kwargs) 在每個方法的開頭。這很好,但我也想從 Parent 方法回傳一些資料,當父方法和子方法在宣告中回傳不同的東西時,它看起來會很奇怪。更不用說我可能想在方法之后做一些后處理作業,所以我需要兩個不同的功能:開始和執行腳本之后。
我想出的下一件事是制作 MetaClass。只需在創建類的程序中實作新 MetaClass 中方法的所有修飾,并將新生成的用于子方法的資料在 kwargs 中傳遞給它們。
這是最接近我目標的解決方案,但無論如何感覺不對。因為某些 kwargs 將被傳遞給子方法并不明確,如果您不熟悉此代碼,那么您需要做一些研究以了解它是如何作業的。我覺得我過度設計了。
所以問題是:是否有任何模式或類似的東西來實作這個功能?也許您可以為我的情況提出更好的建議?非常感謝您!
uj5u.com熱心網友回復:
所以,現有的模式分開:我不知道這是否有一個特定的名稱,你需要什么,那將是一個“模式”是“插槽”的使用:也就是說 - 你記錄特殊命名的方法,這些方法將被稱為另一個方法執行的一部分。然后這個其他方法執行它的設定代碼,檢查槽位方法(通常可以通過名稱識別)是否存在,呼叫它們,使用簡單的方法呼叫,它將運行它的最專業版本,即使呼叫slot 在基類中,并且您處于一個大的類繼承層次結構中。
這種模式的一個簡單示例是 Python 實體化物件的方式:使用與函式呼叫 ( ) 相同的語法呼叫類的實際呼叫MyClass()是該類的類(它的元類)__call__方法。(通常type.__call__)。在 Python 代碼中呼叫type.__call__類的__new__方法,然后呼叫類的__init__方法,最后回傳第一次呼叫回傳的值 to __new__。自定義元類可以修改__call__為在這兩個呼叫之前、之間或之后運行它想要的任何代碼。
因此,如果這不是 Python,您所需要的只是指定它,并記錄不應直接呼叫這些方法,而是通過“入口點”方法 - 它可以簡單地具有“ep_”前綴。這些必須在基類上進行固定和硬編碼,并且您需要為每個要為其添加前綴/后綴代碼的方法使用一個。
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,更容易添加更多的魔法來避免代碼重復并保持簡潔。
一個可能的事情是有一個特殊的類,當檢測到子類中應該作為包裝方法的槽呼叫的方法時,就像上面一樣,自動重命名該方法:這樣入口點方法可以具有相同的功能命名為子方法 - 更好的是,一個簡單的裝飾器可以標記作為“入口點”的方法,繼承甚至可以為它們作業。
基本上,當構建一個新類時,我們會檢查所有方法:如果它們中的任何一個在呼叫層次結構中有對應的部分并被標記為入口點,就會發生重命名。
如果任何入口點方法將作為第二個引數(第一個是self)作為要呼叫的開槽方法的參考,則更為實用。
經過一番擺弄:好訊息是不需要自定義元類 - 基類中的__init_subclass__特殊方法足以啟用裝飾器。
壞訊息:由于在最終方法上對“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)
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/459973.html
上一篇:在Ruby中使用委托維護相同的類
