Краткий ответ
Вы должны проверить, что атрибуты и методы существуют при создании экземпляра класса, а не до.Это то, что делает модуль 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
, чтобы создать новый миксин, который проверяет еще некоторые атрибуты.
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
на самом деле правильно построен, и именно это должен был проверить наш миксин.
В заключение, существование некоторого атрибута или метода должно быть сделано при создании экземпляра,В противном случае вы предотвращаете множество полезных методов наследования.Вот почему модуль abc
делает это так, и именно поэтому мы должны.