python-3.x 抽象基类的子类方法的修饰行为

piah890a  于 2023-03-20  发布在  Python
关注(0)|答案(1)|浏览(136)

我有一个继承场景,看起来像这样:

import abc

class A(metaclass=abc.ABCMeta):
    def __init__(self, x: int | None = None) -> None:
        self.x = x

    @abc.abstractmethod
    def foo(self, x: int | None = None) -> None:
        pass

class B(A):
    def foo(self, x: int | None = None) -> None:
        x = x or self.x
        if x is None:
            raise ValueError("x must be defined")

        print(x)

class C(B):
    ...  # defines a bunch of other stuff

这个想法是,它将可能为用户(其他开发人员)以两种不同的方式使用我的类。要么他们直接与B交互(或A的另一个等价子类),然后它们必须将某些值传递给它的方法(在本例中,它们需要将x传递给B.foo方法。它们可以改为子类化B(类似于本例中的C),然后直接在init中设置x,然后随后不需要将x传递到C.foo
最后,这两种用法都应该是有效的:

b = B()
b.foo(x=1)

c = C(x=1)
c.foo()

现在的问题是,我希望避免在A的不同方法和不同子类中重复该子句:

x = x or self.x
if x is None:
    ValueError("x must be defined")

是否有任何方法可以在A中执行一些操作,这些操作本质上将运行此检查,并且如果没有为x指定值,则将沿着x的值有效地传递给foo方法?
我实际上希望将self.x设置为x的默认值,但如果可以避免的话,我不希望重写if x is None检查。
我想到的是,如果我能以某种方式自动注册一个装饰器到我的子类的方法上,但我不认为这是可能的?

bxgwgixi

bxgwgixi1#

您要覆盖的不是foo,而是 * foo * 使用的方法。
让我们从更新A开始。

class A(metaclass=abc.ABCMeta):
    def __init__(self, x: int | None = None) -> None:
        self.x = x

    def foo(self, x: int | None = None) -> None:
        if x is None:
            if self.x is None:
                raise ValueError("x must be provided")
            x = self.x
        self.foo_work(x)

    @abstractmethod
    def foo_work(self, x: int) -> None:
        ...

注意,foo_work * 必须 * 接收一个x;它不会接受None作为参数,但这没关系,因为最终用户永远不会直接调用foo_work:它们将只调用foofoo或者找到foo_work的有效参数,或者引发异常。

class B(A):
    def foo_work(self, x: int) -> None:       
        print(x)

B().foo()  # ValueError
B(1).foo()  # OK
B().foo(1)  # OK

这可能看起来有点尴尬:你可以看到A的实现细节作为其公共接口的一部分被公开,这是因为foo实际上是两个方法被合并成一个:
1.将值作为参数的函数
1.从自身内部状态取值的对象
可以说,您应该将这两者分开:

class A(metaclass=ABCMeta):
    def __init__(self, x: int = None):
        self.x = x

    @abstractmethod
    def foo(self, x: int) -> None:
        ...

    def foo_with_default(self) -> None:
        if self.x is None:
            raise ValueError("x must be defined")
        return self.foo(self.x)

现在,这两个方法都是公共接口的一部分,实现foo需要子类,foo_with_default本质上是最终的,不应该被覆盖(尽管abc没有提供一种方法来标记它),代价是最终用户必须知道要使用哪个函数。

相关问题