Java аннотация / дизайн интерфейса в Python - PullRequest
34 голосов
/ 18 ноября 2011

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

Ответы [ 4 ]

59 голосов
/ 18 ноября 2011

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

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

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

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

В областиВ объектно-ориентированном программировании шаблоны использования для взаимодействия с объектом можно разделить на две основные категории: «вызов» и «проверка».

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

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

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

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

В частности, часто существуетнеобходимо обрабатывать объекты так, как этого не ожидал создатель класса объектов.Это не всегда лучшее решение для встраивания в каждый объект методов, которые удовлетворяют потребности каждого возможного пользователя этого объекта.Более того, существует много мощных философий диспетчеризации, которые находятся в прямом противоречии с классическим требованием ООП о поведении строго инкапсулированного в объекте, например, логикой, управляемой правилом или сопоставлением с шаблоном.Критика проверки со стороны классических теоретиков ООП заключается в отсутствии формализма и особого характера проверяемого.В таком языке, как Python, в котором практически любой аспект объекта может быть отражен и напрямую доступен для внешнего кода, существует множество различных способов проверить, соответствует ли объект определенному протоколу или нет.Например, если спрашивать «Является ли этот объект контейнером изменяемой последовательности?», Можно искать базовый класс «list» или метод « getitem ».Но обратите внимание, что хотя эти тесты могут показаться очевидными, ни один из них не является правильным, так как один генерирует ложные отрицания, а другие ложные положительные.

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

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

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

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

4 голосов
/ 18 ноября 2011

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

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

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

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

Примечание - приветствуются правки из более осведомленных Pythonistas.

3 голосов
/ 18 ноября 2011

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

class Abstract:

    def func1(self):
        raise NotImplementedError("The method not implemented")
2 голосов
/ 28 мая 2017

Я написал библиотеку в 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'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...