簡體   English   中英

在抽象類方法的具體實現中檢查** kwargs。 接口問題?

[英]Checking of **kwargs in concrete implementation of abstract class method. Interface issue?

我正在嘗試實現策略設計模式,以便為要以模塊化方式實現的基礎算法創建接口。

目前,按照下面的代碼,我有一個頂級/父抽象類( ParentAbstractStrategy ),它定義了strategy方法的基本接口。

我還從這個抽象類( ChildAbstractStrategy )上進行了下一級的ChildAbstractStrategy

我有兩個抽象類的原因是因為它們需要擁有屬性。 請參見__init__方法。 ChildAbstractStrategy是一個特例ParentAbstractStrategy ,它存儲的附加屬性: attr2 否則,其接口是相同的,如相同的strategy方法簽名所示。

有時,我希望能夠直接ParentAbstractStrategy並實現strategy方法(請參見ConcreteStrategyA ),但是有時我希望能夠ChildAbstractStrategy ,因為需要額外的屬性(請參見ConcreteStrategyB )。

另一個復雜之處是,在任何一個抽象類的某些子類中,我都希望能夠處理strategy方法中的其他參數。 這就是為什么我在strategy方法的所有簽名中添加**kwargs原因,以便我可以根據需要將想要添加的任何其他參數傳遞給子類。

這就產生了最后一個問題:這些額外的參數在子類中不是可選的。 例如,在ConcreteStrategyBstrategy方法中,我想確定調用者傳入了第三個參數。 我基本上是在濫用**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)

這是一個解釋器會話,演示其當前的工作方式:

>>> 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是“不好的”。 為什么? 據我所知, **kwargs通常用於:

  1. 包裝功能,例如裝飾器。
  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 ,以試圖為子場景之間的strategy方法定義變量接口。 盡管在語法上所有strategy方法的功能簽名都匹配,但是在語義上接口是不同的。 這違反了LSP,要求我在考慮ParentAbstractStrategy的接口時例如可以將其同等對待,例如,我應該能夠將ConcreteStrategyAConcreteStrategyBstrategy方法相同。

我該怎么辦? 我已將strategy方法的接口更改為不再包含**kwargs ,而是將位置參數和關鍵字參數與默認值混合使用。 例如,如果ConcreteStrategyB仍需要第三個參數arg3ConcreteStrategyA不需要,則可以將類更改為如下形式:

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)

兩個父類的接口都更改為匹配。

暫無
暫無

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

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