Избегайте переменных класса в подклассе python, когда родительский класс требует объявить его - PullRequest
2 голосов
/ 18 марта 2019

Я прочитал, что создание переменной в пространстве имен класса и последующее изменение ее значения в конструкторе класса считается плохой практикой.

(Один из моих источников: SoftwareEngineering SE:Рекомендуется объявлять переменные экземпляра как None в классе в 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

Чтобы заставить свой подкласс объявлять переменную required_var, класс mixin использует метакласс subclass_validator.

И единственный известный мне способ заставить его работать на стороне app.py, это инициализировать required_var как переменная класса.

Я что-то упустил или это единственный способ сделать это?

1 Ответ

0 голосов
/ 20 марта 2019

Краткий ответ

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

...