【问题标题】:Avoid class variable in python subclass when parent class requires to declare it当父类需要声明它时,避免在python子类中使用类变量
【发布时间】:2019-08-09 02:01:09
【问题描述】:

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

(我的一个消息来源:SoftwareEngineering SE: Is it a good practice to declare instance variables as None in a class in Python。)

考虑以下代码:

# 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

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

我知道让它在 app.py 方面工作的唯一方法是将 necessary_var 初始化为类变量。

我遗漏了什么,或者这是唯一的方法吗?

【问题讨论】:

  • 当你尝试访问不存在的self.necessary_var时,为什么不让错误发生?
  • assert 提高 AssertionError是生产中的副作用。
  • 但是,如果有人不打算设置necessary_var,那么是什么阻止他们不从mixin 继承呢?绕过检查就像绕过你要检查的东西一样容易。
  • 请注意,您并没有更改 class 变量 necessary_var 的值,在 my_subclass.__init__ 中,您是 shadowing它带有一个名为 necessary_varinstance 变量。
  • @N0ne 在这种情况下,唯一明智的方法是正确记录您的 api 并允许正常引发异常。作为开发人员,他们应该想要准确的行为,以便他们可以有效地调试他们的编码错误。如果他们仍然抱怨,请礼貌地让他们参考您的文档。

标签: python mixins metaclass class-variables


【解决方案1】:

简答

您应该检查属性和方法是否存在于类的实例化时,而不是之前。这就是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

为了让自己相信在实例化时执行此操作是一种好习惯,我们可以查看使用此行为的 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

如您所见,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 = ...

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

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

【讨论】:

  • 感谢您的回答。抽象类现在完全有意义
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-09
  • 1970-01-01
  • 2018-03-16
  • 1970-01-01
  • 2011-04-08
  • 1970-01-01
相关资源
最近更新 更多