Зачем использовать абстрактные базовые классы в Python?

StackOverflow https://stackoverflow.com/questions/3570796

  •  01-10-2019
  •  | 
  •  

Вопрос

Потому что я привык к старым способам, набрав в Python, я не понимаю необходимость ABC (абстрактные базовые классы). То помощь Хорошо, как их использовать.

Я пытался прочитать обоснование в Пеп, но он пошел над моей головой. Если бы я искал сметный контейнер последовательности, я бы проверил __setitem__, или более вероятно, попробуйте использовать его (Eafp.). Я не сталкивался с реальной жизнью для числа Модуль, который использует ABCS, но это самый близкий, который я должен понять.

Кто-нибудь может объяснить обоснованное мне, пожалуйста?

Это было полезно?

Решение

Укороченная версия

ABCS предлагает более высокий уровень семантического договора между клиентами и реализованными классами.

Длинная версия

Существует договор между классом и его абонентами. Класс обещает делать определенные вещи и иметь определенные свойства.

Есть разные уровни до договора.

На очень низком уровне договор может включать имя метода или его количество параметров.

На статически наведенном языке этот контракт на самом деле будет применяться компилятором. В Python вы можете использовать EAFP или самоанализ, чтобы подтвердить, что неизвестный объект соответствует этим ожидаемым договоре.

Но есть также более высокий уровень, семантические обещания в договоре.

Например, если есть __str__() Ожидается, что способ, как ожидается, вернет строковое представление объекта. Это мог Удалите все содержимое объекта, совершите транзакцию и выпивайте пустую страницу из принтера ... Но есть общее понимание того, что он должен сделать, описанный в руководстве Python.

Это особый случай, где семантический договор описан в руководстве. Что следует print() Способ сделать? Если он пишет объект на принтер или строку на экран, или что-то еще? Это зависит - вам нужно прочитать комментарии, чтобы понять полный договор здесь. Кусок клиентского кода, который просто проверяет, что print() Метод существует подтвердил часть договора - что может быть сделан вызов метода, но не то, что есть соглашение о семантике более высокого уровня вызова.

Определение абстрактного базового класса (ABC) - это способ получения договора между массовыми носителями и абонентами. Это не просто список имен методов, но общее понимание того, что должны делать эти методы. Если вы унаследовали от этой ABC, вы многообещаете следовать всем правилам, описанным в комментариях, включая семантику print() метод.

Утилизация Python утка имеет много преимуществ в гибкости над статическими наборами, но не решает все проблемы. ABCS предлагает промежуточное решение между свободной формой Python и адиационно-дисциплиной статически напечатанного языка.

Другие советы

@ Одним образом ответ не прав, но я думаю, что он не пропускает настоящий, практично Причина, по которой Python имеет ABCS в мире утки.

Абстрактные методы аккуратны, но, на мой взгляд, они на самом деле не заполняют какие-либо случаи использования, которые уже не покрываются набором утки. Абстрактные базовые классы «Настоящая сила лежит в как они позволяют вам настроить поведение isinstance а также issubclass. (__subclasshook__ в основном дружелюбие API на вершине Python's __instancecheck__ а также __subclasscheck__ Крючки.) Адаптация встроенных конструкций для работы на пользовательских типах очень большая часть философии Питона.

Исходный код Python является примерным. Здесь я показываю collections.Container определяется в стандартной библиотеке (во время написания):

class Container(metaclass=ABCMeta):
    __slots__ = ()

    @abstractmethod
    def __contains__(self, x):
        return False

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Container:
            if any("__contains__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

Это определение __subclasshook__ говорит, что любой класс с __contains__ Атрибут считается подклассом контейнера, даже если оно не подклассно его напрямую. Так что я могу написать это:

class ContainAllTheThings(object):
    def __contains__(self, item):
        return True

>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True

Другими словами, Если вы реализуете правильный интерфейс, вы подкласс! ABCS предоставляет формальный способ определить интерфейсы в Python, оставаясь верным духу набора утки. Кроме того, это работает таким образом, чтобы почитать Открытый принцип.

Объектная модель Python выглядит поверхностно похожей на более «традиционную» систему OO (с помощью которой я имею в виду Java *) - у нас есть классы YER, yer объекты, йер-методы - но когда вы поцарапаете поверхность, вы найдете что-то далеко богаче и более гибкий. Аналогичным образом, понятие Python абстрактных базовых классов может быть узнаваемым для разработчика Java, но на практике они предназначены для очень разных целей.

Иногда я знаю, что пишу полиморфные функции, которые могут действовать на едином элементе или коллекции предметов, и я нахожу isinstance(x, collections.Iterable) быть намного более читаемым, чем hasattr(x, '__iter__') или эквивалент try...except блокировать. (Если вы не знали, что Python, какие из этих трех сделали бы намерение кода, яснее?)

Тем не менее, я считаю, что я редко нужно написать свою собственную азбуку, и я обычно обнаруживаю необходимость в одной рефакторинге. Если я увижу полиморфную функцию, создавая множество проверок атрибутов, или множество функций, делающих те же проверки атрибута, что запах предполагает, что существование ABC ждет, чтобы извлекать.

* Не входя в дискуссию над ли Java «традиционной» системой OO ...


Приложение: Даже если абстрактный базовый класс может переопределить поведение isinstance а также issubclass, это все еще не входит в Мро виртуального подкласса. Это потенциальная ловушка для клиентов: не каждый объект, для которого isinstance(x, MyABC) == True имеет методы, определенные на MyABC.

class MyABC(metaclass=abc.ABCMeta):
    def abc_method(self):
        pass
    @classmethod
    def __subclasshook__(cls, C):
        return True

class C(object):
    pass

# typical client code
c = C()
if isinstance(c, MyABC):  # will be true
    c.abc_method()  # raises AttributeError

К сожалению, этот один из тех «просто не делает это» ловушки (из которых имеет относительно мало немногая!): Избегайте определения ABCS с обоими __subclasshook__ и не абстрактные методы. Более того, вы должны сделать ваше определение __subclasshook__ В соответствии с набором абстрактных методов ваши ABC определяет.

Удобная особенность ABCS заключается в том, что если вы не реализуете все необходимые методы (и свойства), вы получаете ошибку при создании поведения, а не AttributeError, Потенциально намного позже, когда вы на самом деле пытаетесь использовать недостающий метод.

from abc import ABCMeta, abstractmethod

# python2
class Base(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

# python3
class Base(object, metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass

class Concrete(Base):
    def foo(self):
        pass

    # We forget to declare `bar`


c = Concrete()
# TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"

Пример от https://dbader.org/blog/abstract-base-classes-in-python.

Редактировать: Чтобы включить синтаксис Python3, спасибо @Pandasrocks

Он будет определять, поддерживает ли объект заданного протокола без необходимости проверки наличие всех методов протокола или без запуска исключения глубоко в территории «врага» из-за непосредственной организации гораздо проще.

Абстрактный метод Убедитесь, что какой-то метод вы вызываете в родительском классе, должен появиться в детском классе. Ниже приведены и используют абстрактный путь. Программа, написанная в Python3

Нормальный способ звонка

class Parent:
def methodone(self):
    raise NotImplemented()

def methodtwo(self):
    raise NotImplementedError()

class Son(Parent):
   def methodone(self):
       return 'methodone() is called'

c = Son()
c.methodone()

«Методон () называется

c.methodtwo()

NotimplementedError.

С абстрактным методом

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'

c = Son()

TypeError: не может создавать резюме сына абстрактного класса с помощью абстрактных методов MethodTWo.

Поскольку MethodTwo не вызывается в детском классе, мы получили ошибку. Правильная реализация ниже

from abc import ABCMeta, abstractmethod

class Parent(metaclass=ABCMeta):
    @abstractmethod
    def methodone(self):
        raise NotImplementedError()
    @abstractmethod
    def methodtwo(self):
        raise NotImplementedError()

class Son(Parent):
    def methodone(self):
        return 'methodone() is called'
    def methodtwo(self):
        return 'methodtwo() is called'

c = Son()
c.methodone()

«Методон () называется

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top