抽象类方法中具体实现中**kwargs的检查。接口问题?

2024-10-02 12:25:07 发布

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

我正在尝试实现策略设计模式,以创建一个接口,以模块化的方式实现底层算法。在

目前,根据下面的代码,我有一个顶层/父抽象类(ParentAbstractStrategy),它定义了strategy方法的基本接口。在

我还从这个抽象类(ChildAbstractStrategy)向下一级。在

我之所以有两个抽象类是因为它们需要保存属性;请参见__init__方法。 ChildAbstractStrategyParentAbstractStrategy的一个特例,因为它存储了一个附加属性:attr2。 否则它的接口是相同的,如相同的strategy方法签名所示。在

有时,我希望能够直接将ParentAbstractStrategy子类化并实现strategy方法(请参见ConcreteStrategyA),但有时我希望能够将ChildAbstractStrategy子类化,因为需要额外的属性(请参见ConcreteStrategyB)。在

另一个复杂的问题是,在抽象类的某些子类中,我希望能够处理strategy方法中的附加参数。这就是为什么我将**kwargs添加到strategy方法的所有签名中,这样我就可以根据具体情况将我想要的任何附加参数传递给子类。在

这就产生了最后一个问题:这些额外的参数在子类中不是可选的。E、 在strategystrategy方法中,我想确定调用者传递了第三个参数。 我基本上是滥用**kwargs来提供可能应该是位置参数的东西(因为我不能给它们合理的默认值,并且需要强制它们的存在)。在

当前在子类中使用**kwargs进行“方法重载”的解决方案感觉非常混乱,我不确定这是否意味着类继承方案或接口设计有问题,或者两者都有。在

有没有一种方法可以让我以更干净的方式实现这些设计目标。感觉好像我错过了一些重要的东西,也许类/接口设计很糟糕。也许为strategy方法创建两个具有不同签名的不相交抽象类?在

import abc


class ParentAbstractStrategy(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def __init__(self, attr1):
        self.attr1 = attr1

    @abc.abstractmethod
    def strategy(self, arg1, arg2, **kwargs):
        raise NotImplementedError


class ChildAbstractStrategy(ParentAbstractStrategy, metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def __init__(self, attr1, attr2):
        super().__init__(attr1)
        self.attr2 = attr2

    @abc.abstractmethod
    def strategy(self, arg1, arg2, **kwargs):
        raise NotImplementedError


class ConcreteStrategyA(ParentAbstractStrategy):
    def __init__(self, attr1):
        super().__init__(attr1)

    def strategy(self, arg1, arg2, **kwargs):
        print(arg1, arg2)


class ConcreteStrategyB(ChildAbstractStrategy):
    def __init__(self, attr1, attr2):
        super().__init__(attr1, attr2)

    def strategy(self, arg1, arg2, **kwargs):
        print(arg1, arg2)
        arg3 = kwargs.get("arg3", None)

        if arg3 is None:
            raise ValueError("Missing arg3")
        else:
            print(arg3)

下面是一个解释会话,演示它当前的工作方式:

^{pr2}$

Tags: 方法selfinitdef抽象类arg3kwargsabc
1条回答
网友
1楼 · 发布于 2024-10-02 12:25:07

回答我自己的问题。在

在这个场景中,我对**kwargs的使用是“糟糕的”。为什么? 据我所知,**kwargs通常用于:

  1. 包装函数,例如decorators。在
  2. 收集函数所知道的函数的额外关键字参数(例如,参见https://matplotlib.org/api/_as_gen/matplotlib.pyplot.plot.html?highlight=plot#matplotlib.pyplot.plot中的用法)。在这个场景中,**kwargs是可选参数,可以传递到函数中,它们具有正常的默认值。在

在函数调用中将**kwargs设为必需的,这将破坏它们的用途;应该改用需要显式提供的位置参数。这样,函数提供的接口就可以由调用者显式地满足。在

与我一样,在接口中使用**kwargs还有另一个问题。它涉及到LSP(Liskov代换原理,见https://en.wikipedia.org/wiki/Liskov_substitution_principle)。当前的实现正在滥用**kwargs,试图在子cases之间为strategy方法定义一个变量接口。尽管所有strategy方法的函数签名在语法上是匹配的,但在语义上接口是不同的。这违反了LSP,它要求我在考虑ParentAbstractStrategy的任何子代时,对它们的接口都是相同的,例如,我应该能够对ConcreteStrategyA和{}的{}方法进行相同的处理。在

我的解决方案是什么? 我已经将strategy方法的接口更改为不再包含**kwargs,而是将位置参数和关键字参数与默认值混合使用。 E、 g.如果ConcreteStrategyB仍然需要第三个参数arg3,但是ConcreteStrategyA不需要,我可以将这些类改成这样:

class ConcreteStrategyA(ParentAbstractStrategy):
    def __init__(self, attr1):
        super().__init__(attr1)

    def strategy(self, arg1, arg2, arg3=None):
        print(arg1, arg2)


class ConcreteStrategyB(ChildAbstractStrategy):
    def __init__(self, attr1, attr2):
        super().__init__(attr1, attr2)

    def strategy(self, arg1, arg2, arg3=None):
        print(arg1, arg2)
        assert arg3 is not None
        print(arg3)

两个父类的接口都更改为匹配。在

相关问题 更多 >

    热门问题