Подтвердить что ты не робот

Дизайн абстрактного/интерфейса Java в Python

У меня есть несколько классов, которые все используют одни и те же методы, только с разными реализациями. В Java было бы целесообразно, чтобы каждый из этих классов реализовал интерфейс или расширил абстрактный класс. У Python есть что-то подобное этому, или я должен использовать альтернативный подход?

4b9b3361

Ответ 1

В Python немного рассказывается о интерфейсах. Первоначальное отношение, которое продолжалось много лет, заключается в том, что они вам не нужны: Python работает над принципом EAFP (проще просить прощения, чем разрешение). То есть вместо того, чтобы указывать, что вы принимаете объект Ilunable, ICloseable, вы просто пытаетесь close объект, когда вам нужно, и если он вызывает исключение, тогда возникает исключение.

Итак, в этом менталитете вы просто напишете свои классы отдельно и будете использовать их так, как вы. Если один из них не соответствует требованиям, ваша программа вызовет исключение; наоборот, если вы пишете другой класс с правильными методами, тогда он будет работать, без необходимости указывать, что он реализует ваш конкретный интерфейс.

Это работает очень хорошо, но есть определенные варианты использования интерфейсов, особенно с более крупными проектами программного обеспечения. Окончательное решение в Python состояло в том, чтобы предоставить модуль abc, который позволяет вам писать абстрактные базовые классы, т.е. классы, которые невозможно создать, если вы не отмените все их методы, Это ваше решение относительно того, думаете ли вы, используя их, того стоит.

PEP, представляющий ABC, объясняет намного лучше, чем я могу:

В области объектно-ориентированного программирования используются шаблоны использования для взаимодействие с объектом можно разделить на две основные категории, которые являются "вызовом" и "проверкой".

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

Проверка означает возможность для внешнего кода (вне объектные методы) для изучения типа или свойств этого объекта, и принимать решения о том, как относиться к этому объекту на основе этого информация.

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

В классической теории ООП, обращение является предпочтительным шаблоном использования, и инспекция активно обескуражена, считаясь реликвией ранее, процедурный стиль программирования. Однако на практике это мнение просто слишком догматична и негибкая, и приводит к своего рода дизайну жесткость, которая очень сильно расходится с динамическим характером язык, такой как Python.

В частности, часто приходится обрабатывать объекты таким образом, чтобы не был создан создателем класса объектов. Это не всегда лучшее решение для построения всех методов объектов, которые удовлетворяют потребности всех возможных пользователей этого объекта. Более того, есть много важных философий отправки, которые находятся в прямой в отличие от классического требования ООП к строгому инкапсулированный внутри объекта, примеры - правило или соответствие шаблону управляемая логика.

С другой стороны, одна из критических замечаний по проверке классическим ООП теоретиками является отсутствие формализма и особый характер того, что проверяются. На языке, таком как Python, в котором почти любой аспект объекта может быть отражен и напрямую доступен внешними кода существует множество способов проверить, соответствует ли объект к определенному протоколу или нет. Например, если спрашивать: "Это объект является контейнером с изменяемой последовательностью? ', можно искать базовый класс из" списка ", или можно искать метод с именем" getitem". Но заметьте что хотя эти тесты могут показаться очевидными, ни одна из них не является правильно, поскольку генерирует ложные негативы, а другой ложный позитивы.

Общепринятое решение состоит в стандартизации тестов и сгруппируйте их в официальную договоренность. Это проще всего сделать сопоставляя каждому классу множество стандартных проверяемых свойств, либо через механизм наследования, либо каким-либо другим способом. Каждый тест несет с собой набор promises: он содержит обещание о общее поведение класса и обещание относительно того, что другой класс методы будут доступны.

Этот PEP предлагает конкретную стратегию для организации этих тестов известных как абстрактные базовые классы или ABC. ABC - это просто классы Python которые добавляются в дерево наследования объектов, чтобы сигнализировать определенные функции этого объекта для внешнего инспектора. Тесты проводятся с использованием isinstance(), а наличие конкретной ABC означает, что тест прошло.

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

Как и все другие вещи на Python, эти promises имеют характер джентльменское соглашение, которое в этом случае означает, что язык выполняет некоторые из promises, сделанные в ABC, он встает для исполнителя конкретного класса, чтобы гарантировать, что оставшиеся сохраняются.

Ответ 2

Я не знаком с Python, но я бы рискнул предположить, что это не так.

Причина, по которой интерфейсы существуют в Java, заключается в том, что они указывают контракт. Например, что-то, реализующее java.util.List, гарантировано, что метод add() будет соответствовать общему поведению, определенному на интерфейсе. Вы можете отказаться от любой (разумной) реализации List, не зная своего конкретного класса, вызвать последовательность методов, определенных на интерфейсе, и получить одно и то же общее поведение.

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

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

Примечание. Добро пожаловать в редакцию более опытных Pythonistas.

Ответ 3

Может быть, вы можете использовать что-то вроде этого. Это будет действовать как абстрактный класс. Поэтому каждый подкласс вынужден реализовать func1()

class Abstract:

    def func1(self):
        raise NotImplementedError("The method not implemented")

Ответ 4

Я написал library в 3.5+, что позволяет писать интерфейсы в Python.

Суть заключается в том, чтобы написать декоратор класса с помощью inspect.

import inspect


def implements(interface_cls):
    def _decorator(cls):
        verify_methods(interface_cls, cls)
        verify_properties(interface_cls, cls)
        verify_attributes(interface_cls, cls)
        return cls

    return _decorator


def verify_methods(interface_cls, cls):
    methods_predicate = lambda m: inspect.isfunction(m) or inspect.ismethod(m)
    for name, method in inspect.getmembers(interface_cls, methods_predicate):
        signature = inspect.signature(method)
        cls_method = getattr(cls, name, None)
        cls_signature = inspect.signature(cls_method) if cls_method else None
        if cls_signature != signature:
            raise NotImplementedError(
                "'{}' must implement method '{}({})' defined in interface '{}'"
                .format(cls.__name__, name, signature, interface_cls.__name__)
            )


def verify_properties(interface_cls, cls):
    prop_attrs = dict(fget='getter', fset='setter', fdel='deleter')
    for name, prop in inspect.getmembers(interface_cls, inspect.isdatadescriptor):
        cls_prop = getattr(cls, name, None)
        for attr in prop_attrs:
            # instanceof doesn't work for class function comparison
            if type(getattr(prop, attr, None)) != type(getattr(cls_prop, attr, None)):
                raise NotImplementedError(
                    "'{}' must implement a {} for property '{}' defined in interface '{}'"  # flake8: noqa
                    .format(cls.__name__, prop_attrs[attr], name, interface_cls.__name__)
                )


def verify_attributes(interface_cls, cls):
    interface_attributes = get_attributes(interface_cls)
    cls_attributes = get_attributes(cls)
    for missing_attr in (interface_attributes - cls_attributes):
        raise NotImplementedError(
            "'{}' must have class attribute '{}' defined in interface '{}'"
            .format(cls.__name__, missing_attr, interface_cls.__name__)
        )


def get_attributes(cls):
    boring = dir(type('dummy', (object,), {}))
    return set(item[0] for item in inspect.getmembers(cls)
               if item[0] not in boring and not callable(item[1]))

Затем вы можете писать классы следующим образом:

class Quackable:
    def quack(self) -> bool:
        pass


@implements(Quackable)
class MallardDuck:    
    def quack(self) -> bool:
        pass

Ниже вы получите сообщение об ошибке:

@implements(Quackable)
class RubberDuck:    
    def quack(self) -> str:
        pass

NotImplementedError: 'RubberdDuck' must implement method 'quack((self) -> bool)' defined in interface 'Quackable'