簡體   English   中英

是否有任何設計模式來實現抽象 class 的子類方法的裝飾?

[英]Is there any design pattern for implementing decoration of methods of child classes of an abstract class?

這種情況是這樣的,我有一個抽象的 class 和一些實現它的子類。

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 class 的所有孩子。喜歡:

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!')

我想到的第一件事是使用 Parent class 方法實現:只需刪除“raise NotImplementedError()”並添加一些功能,然后在子類中我會調用,例如,super().first_method(self, *args , **kwargs) 在每個方法的開頭。 這很好,但我也想從 Parent 方法返回一些數據,當父方法和子方法在聲明中返回不同的東西時看起來很奇怪。 更不用說我可能想在方法之后做一些后處理工作,所以我需要 2 個不同的函數:開始和執行腳本之后。

接下來我想到的是制作 MetaClass。 只需在創建 class 期間在新的 MetaClass 中實現所有方法的裝飾,並將在子方法中使用的新生成的數據在 kwargs 中傳遞給它們。

這是最接近我的目標的解決方案,但無論如何感覺不對。 因為一些 kwargs 將被傳遞給子方法並不明確,如果你是這段代碼的新手,那么你需要做一些研究來理解它是如何工作的。 我覺得我過度設計了。

所以問題是:是否有任何模式或類似的東西來實現這個功能? 也許您可以為我的情況提供更好的建議? 非常感謝您!

所以,除了現有的模式:我不知道這是否有一個特定的名稱,你需要什么,那將是一個“模式”是“插槽”的使用:也就是說 - 你記錄了特殊的命名方法,這些方法將被稱為執行另一個方法的一部分。 這個另一個方法然后執行它的設置代碼,檢查是否有槽方法(通常可以通過名稱識別),調用它們,用一個簡單的方法調用,它將運行它的最專業版本,即使調用slots 位於 base class 中,並且您位於一個大的類繼承層次結構中。

這種模式的一個簡單示例是 Python 實例化對象的方式:使用與用於 function 調用 ( MyClass() ) 相同的語法實際調用調用 class 的是該類的 class(其元類)__ __call__方法。 (通常type.__call__ )。 type.__call__的 Python 代碼中,調用類的__new__方法,然后調用類的__init__方法,最后返回第一次調用__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,更容易添加一些魔法來避免代碼重復並保持簡潔。

一個可能的事情是有一個特殊的 class,當在子 class 中檢測到應該作為包裝方法的槽調用的方法時,像上面一樣,自動重命名方法:這樣入口點方法可以具有相同的特征命名為子方法——更好的是,一個簡單的裝飾器可以將方法標記為“入口點”,inheritance 甚至可以為它們工作。

基本上,當構建一個新的 class 時,我們會檢查所有方法:如果它們中的任何一個在調用層次結構中有對應的部分被標記為入口點,則會進行重命名。

如果任何入口點方法將作為第二個參數(第一個是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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM