当父 class 需要声明它时,避免 python subclass 中的 class 变量
Avoid class variable in python subclass when parent class requires to declare it
我读到在 class 命名空间中创建变量然后在 class 构造函数中更改其值被认为是不好的做法。
考虑以下代码:
# 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
为了强制其子class声明变量necessary_var,classmixin
使用metaclass subclass_validator
.
我知道让它在 app.py
端工作的唯一方法是初始化 necessary_var 作为 class 变量。
我遗漏了什么或者这是唯一的方法吗?
简答
您应该检查 class 实例化时属性和方法是否存在,而不是之前。这就是 abc
模块所做的,它有充分的理由像这样工作。
长答案
首先,我想指出,您要检查的似乎是实例属性是否存在。
由于 Python 动态特性,不可能在创建实例之前,即在调用 __init__
之后这样做。我们可以定义 Mixin.__init__
,但我们将不得不依赖 API 的用户保持完美的卫生习惯并始终调用 super().__init__
.
因此,一种选择是创建元class 并在其__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()
实例化时引发,而不是在 class 创建时引发。
但为什么会这样呢?
原因是并非每个 class 都会被实例化,请考虑我们要从 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
class 时引发的。这显然不是期望的行为,因为 Foo
class 实际上是正确构建的,而这正是我们的 mixin 应该检查的。
总而言之,某些属性或方法的存在应该在实例化时完成,否则,您将阻止大量有用的继承技术。这就是 abc
模块这样做的原因,也是我们应该这样做的原因。
我读到在 class 命名空间中创建变量然后在 class 构造函数中更改其值被认为是不好的做法。
考虑以下代码:
# 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
为了强制其子class声明变量necessary_var,classmixin
使用metaclass subclass_validator
.
我知道让它在 app.py
端工作的唯一方法是初始化 necessary_var 作为 class 变量。
我遗漏了什么或者这是唯一的方法吗?
简答
您应该检查 class 实例化时属性和方法是否存在,而不是之前。这就是 abc
模块所做的,它有充分的理由像这样工作。
长答案
首先,我想指出,您要检查的似乎是实例属性是否存在。
由于 Python 动态特性,不可能在创建实例之前,即在调用 __init__
之后这样做。我们可以定义 Mixin.__init__
,但我们将不得不依赖 API 的用户保持完美的卫生习惯并始终调用 super().__init__
.
因此,一种选择是创建元class 并在其__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()
实例化时引发,而不是在 class 创建时引发。
但为什么会这样呢?
原因是并非每个 class 都会被实例化,请考虑我们要从 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
class 时引发的。这显然不是期望的行为,因为 Foo
class 实际上是正确构建的,而这正是我们的 mixin 应该检查的。
总而言之,某些属性或方法的存在应该在实例化时完成,否则,您将阻止大量有用的继承技术。这就是 abc
模块这样做的原因,也是我们应该这样做的原因。