简体   繁体   English

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

[英]Avoid class variable in python subclass when parent class requires to declare it

I read that it is considered bad practice to create a variable in the class namespace and then change its value in the class constructor.我读到在类命名空间中创建变量然后在类构造函数中更改其值被认为是不好的做法。

(One of my sources: SoftwareEngineering SE: Is it a good practice to declare instance variables as None in a class in Python .) (我的一个来源: SoftwareEngineering SE:在 Python 的类中将实例变量声明为 None 是一种好习惯。)

Consider the following code:考虑以下代码:

# 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

To force its subclass to declare the variable necessary_var , the class mixin uses the metaclass subclass_validator .要强制其子类来声明变量necessary_var,mixin使用元类subclass_validator

And the only way I know to makes it work on app.py side, is to initialized necessary_var as a class variable.我知道让它在app.py端工作的唯一方法是将必要的变量初始化为类变量。

I am missing something or is it the only way to do so?我错过了什么还是这是唯一的方法?

Short answer简答

You should check that attributes and methods exist at instantiation of a class, not before.您应该在类的实例化时检查属性和方法是否存在,而不是在实例化之前。 This is what the abc module does and it has good reasons to work like this.这就是abc模块所做的,它有很好的理由这样工作。

Long answer长答案

First, I would like to point out that it seems what you want to check is that an instance attribute exists.首先,我想指出的是,您似乎要检查的是实例属性是否存在。

Due to Python dynamic nature, it is not possible to do so before an instance is created, that is after the call to __init__ .由于 Python 的动态特性,在创建实例之前是不可能的,也就是在调用__init__ We could define Mixin.__init__ , but we would then have to rely on the users of your API to have perfect hygiene and to always call super().__init__ .我们可以定义Mixin.__init__ ,但是我们将不得不依赖 API 的用户来保持完美的卫生并始终调用super().__init__

One option is thus to create a metaclass and add a check in its __call__ method.因此,一种选择是创建一个元类并在其__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

To convince yourself that it is good practice to do this at instantiation, we can look toward the abc module which uses this behaviour.为了说服自己在实例化时执行此操作是一种很好的做法,我们可以查看使用此行为的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

As you can see the TypeError was raise at instantiation of Foo() and not at class creation.如您所见, TypeError是在Foo()实例化时引发的,而不是在创建类时引发。

But why does it behave like this?但它为什么会这样呢?

The reason for that is that not every class will be instantiated, consider the example where we want to inherit from Mixin to create a new mixin which checks for some more attributes.原因是不是每个类都会被实例化,考虑我们想要从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 = ...

As you see, the AssertionError was raised at the creation of the MoreMixin class.如您所见,在创建MoreMixin类时引发了AssertionError This is clearly not the desired behaviour since the Foo class is actually correctly built and that is what our mixin was supposed to check.这显然不是我们想要的行为,因为Foo类实际上是正确构建的,而这正是我们的 mixin 应该检查的。

In conclusion, the existence of some attribute or method should be done at instantiation, Otherwise, you are preventing a whole lot of helpful inheritance techniques.总之,某些属性或方法的存在应该在实例化时完成,否则,您将阻止很多有用的继承技术。 This is why the abc module does it like that and this is why we should.这就是abc模块这样做的原因,这也是我们应该这样做的原因。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM