当父类需要声明它时,避免在python子类中使用类变量

N0n*_*0ne 5 python metaclass mixins class-variables

我读到在类命名空间中创建变量然后在类构造函数中更改其值被认为是不好的做法。

(我的一个来源:SoftwareEngineering SE:在 Python 的类中将实例变量声明为 None 是一种好习惯。)

考虑以下代码:

# lib.py
class mixin:
    def __init_subclass__(cls, **kwargs):
        cls.check_mixin_subclass_validity(cls)
        super().__init_subclass__(**kwargs)

    def check_mixin_subclass_validity(subclass):
        assert hasattr(subclass, 'necessary_var'), \
            'Missing necessary_var'

    def method_used_by_subclass(self):
        return self.necessary_var * 3.14


# app.py
class my_subclass(mixin):
    necessary_var = None

    def __init__(self, some_value):
        self.necessary_var = some_value

    def run(self):
        # DO SOME STUFF
        self.necessary_var = self.method_used_by_subclass()
        # DO OTHER STUFF
Run Code Online (Sandbox Code Playgroud)

为了强制其子类声明变量required_var,该类mixin使用元类subclass_validator

我所知道的让它同时工作的唯一方法app.py是将必要的变量初始化为类变量。

我错过了什么还是这是唯一的方法?

Oli*_*çon 2

简短回答

您应该检查属性和方法是否存在于类实例化时,而不是之前。这就是该abc模块的作用,并且它有充分的理由这样工作。

长答案

首先,我想指出,您似乎想要检查的是实例属性是否存在。

由于 Python 的动态特性,在创建实例之前(即调用__init__. 我们可以定义Mixin.__init__,但是我们必须依赖 API 的用户来保证完美的卫生并始终调用super().__init__

因此,一种选择是创建一个元类并在其方法中添加检查__call__

class MetaMixin(type):
    def __call__(self, *args, **kwargs):
        instance = super().__call__(*args, **kwargs)
        assert hasattr(instance, 'necessary_var')

class Mixin(metaclass=MetaMixin):
    pass

class Foo(Mixin):
    def __init__(self):
        self.necessary_var = ...

Foo() # Works fine

class Bar(Mixin):
    pass

Bar() # AssertionError
Run Code Online (Sandbox Code Playgroud)

为了让自己相信在实例化时执行此操作是一种很好的做法,我们可以查看abc使用此行为的模块。

from abc import abstractmethod, ABC

class AbstractMixin(ABC):
    @abstractmethod
    def foo(self):
        ...

class Foo(AbstractMixin):
    pass

# Right now, everything is still all good

Foo() # TypeError: Can't instantiate abstract class Foo with abstract methods foo
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,是TypeError在实例化时而Foo()不是在类创建时引发的。

但为什么它会这样呢?

原因是并不是每个类都会被实例化,考虑一下我们想要继承以Mixin创建一个新的 mixin 来检查更多属性的示例。

class Mixin:
    def __init_subclass__(cls, **kwargs):
        assert hasattr(cls, 'necessary_var')
        super().__init_subclass__(**kwargs)

class MoreMixin(Mixin):
    def __init_subclass__(cls, **kwargs):
        assert hasattr(cls, 'other_necessary_var')
        super().__init_subclass__(**kwargs)

# AssertionError was raised at that point

class Foo(MoreMixin):
    necessary_var = ...
    other_necessary_var = ...
Run Code Online (Sandbox Code Playgroud)

如您所见, 是AssertionError在创建班级时提出的MoreMixin。这显然不是期望的行为,因为该类Foo实际上是正确构建的,而这正是我们的 mixin 应该检查的。

总之,某些属性或方法的存在应该在实例化时完成,否则,您将阻止大量有用的继承技术。这就是abc模块这样做的原因,也是我们应该这样做的原因。