在抽象 class 方法的具体实现中检查 **kwargs。接口问题?
Checking of **kwargs in concrete implementation of abstract class method. Interface issue?
我正在尝试实施策略设计模式,为要以模块化方式实施的基础算法创建接口。
目前,根据下面的代码,我有一个 top-level/parent 抽象 class (ParentAbstractStrategy
) 定义了 strategy
方法的基本接口。
我还从这个摘要 class (ChildAbstractStrategy
) 下一级。
我有两个抽象 classes 的原因是因为它们需要包含的属性;请参阅 __init__
方法。
ChildAbstractStrategy
是 ParentAbstractStrategy
的特例,因为它存储了一个附加属性:attr2
。
否则它的接口是相同的,正如相同的 strategy
方法签名所见。
有时,我希望能够直接subclass ParentAbstractStrategy
并实现strategy
方法(见ConcreteStrategyA
),但其他时候我想成为能够子class ChildAbstractStrategy
,因为需要额外的属性(参见ConcreteStrategyB
)。
另一个复杂的问题是,在任一抽象 class 的 some subclasses 中,我希望能够处理 strategy
方法。这就是为什么我将 **kwargs
添加到 strategy
方法的所有签名中,以便我可以根据具体情况将我想要的任何其他参数传递给 subclass基础。
这产生了最后一个问题:这些额外的参数在子classes 中不是可选的。例如。在 ConcreteStrategyB
的 strategy
方法中,我想确定调用者传入了第三个参数。
我基本上是在滥用 **kwargs
来提供可能应该是位置参数的内容(因为我不能给它们合理的默认值并且需要强制执行它们的存在)。
当前在 subclasses 中使用 **kwargs
for "method overloading" 的解决方案感觉真的很乱,我不确定这是否意味着 [=] 有问题59=]继承方案或接口设计,或两者兼而有之。
有没有一种方法可以让我以更简洁的方式实现这些设计目标。感觉好像我在这里遗漏了一些大图,也许 class/interface 设计不好。也许为 strategy
方法创建两个具有不同签名的不相交的抽象 classes?
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)
这是一个演示其当前工作方式的解释器会话:
>>> a = ConcreteStrategyA(1)
>>> a.attr1
1
>>> a.strategy("a", "b")
a b
>>> b = ConcreteStrategyB(1, 2)
>>> b.attr1
1
>>> b.attr2
2
>>> b.strategy("a", "b")
a b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/space/strategy.py", line 42, in strategy
raise ValueError("Missing arg3")
ValueError: Missing arg3
>>> b.strategy("a", "b", arg3="c")
a b
c
回答我自己的问题。
在这种情况下,我对 **kwargs
的用法是 'bad'。为什么?
据我所知,**kwargs
通常用于:
- 包装函数,例如装饰器。
- 为函数收集额外的关键字参数该函数知道(例如,参见https://matplotlib.org/api/_as_gen/matplotlib.pyplot.plot.html?highlight=plot#matplotlib.pyplot.plot中的用法)。在这种情况下,
**kwargs
是可选参数,可以传递给函数 并且 它们具有合理的默认值。
在函数调用中使 **kwargs
成为 必需的 违背了他们的目的;应该使用需要显式提供的位置参数。这样,函数 提供的接口必须 由调用者显式满足。
在界面中使用 **kwargs
还有另一个问题,就像我一样。它涉及到LSP(Liskov Substitution Principle,见https://en.wikipedia.org/wiki/Liskov_substitution_principle)。当前的实现滥用 **kwargs
试图为子类中的 strategy
方法定义一个变量接口。尽管在语法上所有 strategy
方法的函数签名都匹配,但在语义上接口是不同的。这违反了 LSP,这要求我可以,例如在考虑它们的接口时,对 ParentAbstractStrategy
的任何后代都一视同仁,例如我应该能够同样对待 ConcreteStrategyA
和 ConcreteStrategyB
的 strategy
方法。
我的解决方案是什么?
我已将 strategy
方法的接口更改为不再包含 **kwargs
,而是混合使用位置参数和具有默认值的关键字参数。
例如。如果 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)
与父代 类 的接口相匹配。
我正在尝试实施策略设计模式,为要以模块化方式实施的基础算法创建接口。
目前,根据下面的代码,我有一个 top-level/parent 抽象 class (ParentAbstractStrategy
) 定义了 strategy
方法的基本接口。
我还从这个摘要 class (ChildAbstractStrategy
) 下一级。
我有两个抽象 classes 的原因是因为它们需要包含的属性;请参阅 __init__
方法。
ChildAbstractStrategy
是 ParentAbstractStrategy
的特例,因为它存储了一个附加属性:attr2
。
否则它的接口是相同的,正如相同的 strategy
方法签名所见。
有时,我希望能够直接subclass ParentAbstractStrategy
并实现strategy
方法(见ConcreteStrategyA
),但其他时候我想成为能够子class ChildAbstractStrategy
,因为需要额外的属性(参见ConcreteStrategyB
)。
另一个复杂的问题是,在任一抽象 class 的 some subclasses 中,我希望能够处理 strategy
方法。这就是为什么我将 **kwargs
添加到 strategy
方法的所有签名中,以便我可以根据具体情况将我想要的任何其他参数传递给 subclass基础。
这产生了最后一个问题:这些额外的参数在子classes 中不是可选的。例如。在 ConcreteStrategyB
的 strategy
方法中,我想确定调用者传入了第三个参数。
我基本上是在滥用 **kwargs
来提供可能应该是位置参数的内容(因为我不能给它们合理的默认值并且需要强制执行它们的存在)。
当前在 subclasses 中使用 **kwargs
for "method overloading" 的解决方案感觉真的很乱,我不确定这是否意味着 [=] 有问题59=]继承方案或接口设计,或两者兼而有之。
有没有一种方法可以让我以更简洁的方式实现这些设计目标。感觉好像我在这里遗漏了一些大图,也许 class/interface 设计不好。也许为 strategy
方法创建两个具有不同签名的不相交的抽象 classes?
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)
这是一个演示其当前工作方式的解释器会话:
>>> a = ConcreteStrategyA(1)
>>> a.attr1
1
>>> a.strategy("a", "b")
a b
>>> b = ConcreteStrategyB(1, 2)
>>> b.attr1
1
>>> b.attr2
2
>>> b.strategy("a", "b")
a b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/space/strategy.py", line 42, in strategy
raise ValueError("Missing arg3")
ValueError: Missing arg3
>>> b.strategy("a", "b", arg3="c")
a b
c
回答我自己的问题。
在这种情况下,我对 **kwargs
的用法是 'bad'。为什么?
据我所知,**kwargs
通常用于:
- 包装函数,例如装饰器。
- 为函数收集额外的关键字参数该函数知道(例如,参见https://matplotlib.org/api/_as_gen/matplotlib.pyplot.plot.html?highlight=plot#matplotlib.pyplot.plot中的用法)。在这种情况下,
**kwargs
是可选参数,可以传递给函数 并且 它们具有合理的默认值。
在函数调用中使 **kwargs
成为 必需的 违背了他们的目的;应该使用需要显式提供的位置参数。这样,函数 提供的接口必须 由调用者显式满足。
在界面中使用 **kwargs
还有另一个问题,就像我一样。它涉及到LSP(Liskov Substitution Principle,见https://en.wikipedia.org/wiki/Liskov_substitution_principle)。当前的实现滥用 **kwargs
试图为子类中的 strategy
方法定义一个变量接口。尽管在语法上所有 strategy
方法的函数签名都匹配,但在语义上接口是不同的。这违反了 LSP,这要求我可以,例如在考虑它们的接口时,对 ParentAbstractStrategy
的任何后代都一视同仁,例如我应该能够同样对待 ConcreteStrategyA
和 ConcreteStrategyB
的 strategy
方法。
我的解决方案是什么?
我已将 strategy
方法的接口更改为不再包含 **kwargs
,而是混合使用位置参数和具有默认值的关键字参数。
例如。如果 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)
与父代 类 的接口相匹配。