Як мені реалізувати інтерфейси в python?


182
public interface IInterface
{
    void show();
}

 public class MyClass : IInterface
{

    #region IInterface Members

    public void show()
    {
        Console.WriteLine("Hello World!");
    }

    #endregion
}

Як мені реалізувати еквівалент Python цього коду C #?

class IInterface(object):
    def __init__(self):
        pass

    def show(self):
        raise Exception("NotImplementedException")


class MyClass(IInterface):
   def __init__(self):
       IInterface.__init__(self)

   def show(self):
       print 'Hello World!'

Це гарна ідея ?? Наведіть приклади у своїх відповідях.


Яка була б мета використання інтерфейсу у вашому випадку?
Банді-Т

23
Відверто кажучи, ніякої мети немає! Я просто хочу дізнатися, що робити, коли потрібні інтерфейси в python?
Pratik Deoghare

18
raise NotImplementedErrorте show, що має бути тілом - немає сенсу піднімати абсолютно загальне, Exceptionколи Python визначає ідеально конкретний вбудований! -)
Алекс Мартеллі

2
Чи не повинен init call show () в IInterface (або підняти сам виняток), щоб ви не могли створити абстрактний інтерфейс?
Катастичне подорож

1
Я бачу певне використання для цього ... скажімо, у вас є об'єкт, для якого ви хочете переконатися, що він має певний підпис. Вводячи качку, ви не можете гарантувати, що об’єкт буде мати той підпис, який ви очікуєте. Іноді може бути корисним застосувати певне введення властивостей, що динамічно набираються.
Prime By Design

Відповіді:


151

Як згадують інші тут:

Інтерфейси не потрібні в Python. Це тому, що Python має належне багаторазове успадкування, а також ducktyping, це означає, що місця, де у вас повинні бути інтерфейси на Java, вам не потрібно мати їх у Python.

Однак, інтерфейсів все ще існує декілька. Деякі з них охоплені абстрактними базовими класами Pythons, представленими в Python 2.6. Вони корисні, якщо ви хочете створити базові класи, які неможливо створити, а надавати певний інтерфейс або частину реалізації.

Інше використання - якщо ви хочете якось вказати, що об’єкт реалізує певний інтерфейс, і ви можете використовувати ABC і для цього, підкласируючи з них. Інший спосіб - zope.interface, модуль, який є частиною Zope Component Architecture, справді надзвичайно крутим компонентним каркасом. Тут ви не підклас з інтерфейсів, а натомість позначаєте класи (або навіть екземпляри) як реалізацію інтерфейсу. Це також можна використовувати для пошуку компонентів з реєстру компонентів. Суперхолоди!


11
Не могли б ви детальніше зупинитися на цьому? 1. Як реалізувати такий інтерфейс? 2. Як з його допомогою можна шукати компоненти?
геодезичний

43
"Інтерфейси не потрібні в Python. За винятком випадків, коли вони є."
Батіст Кандилер

8
інтерфейси в основному використовуються для передбачуваного результату / забезпечення правильності членів при передачі об'єктів навколо. було б чудово, якби python підтримував це як варіант. це також дозволить інструментам розробки мати кращу взаємодію
Sonic Soul

1
Приклад значно покращить цю відповідь.
bob

5
"Це тому, що Python має належне багаторазове успадкування", хто сказав, що інтерфейси призначені для багаторазового успадкування?
adnanmuttaleb

76

Використання модуля abc для абстрактних базових класів, здається, робить свою справу.

from abc import ABCMeta, abstractmethod

class IInterface:
    __metaclass__ = ABCMeta

    @classmethod
    def version(self): return "1.0"
    @abstractmethod
    def show(self): raise NotImplementedError

class MyServer(IInterface):
    def show(self):
        print 'Hello, World 2!'

class MyBadServer(object):
    def show(self):
        print 'Damn you, world!'


class MyClient(object):

    def __init__(self, server):
        if not isinstance(server, IInterface): raise Exception('Bad interface')
        if not IInterface.version() == '1.0': raise Exception('Bad revision')

        self._server = server


    def client_show(self):
        self._server.show()


# This call will fail with an exception
try:
    x = MyClient(MyBadServer)
except Exception as exc:
    print 'Failed as it should!'

# This will pass with glory
MyClient(MyServer()).client_show()

11
Югхх, потрібен якийсь модуль для того, що має бути частиною самої мови, або взагалі не використовується ІМО.
Майк де Клерк

Ви маєте в виду: if not server.version() == '1.0': raise ...? Я не дуже розумію цю лінію. Пояснення буде вітатися.
Skandix

1
@MikedeKlerk Я не міг більше погодитися. Так само, як відповідь Python на введення тексту; Мені не потрібно було імпортувати модуль, щоб заявити, що я хочу бути типом. Відповідь на це, як правило, "добре Python динамічно набирається", але це не привід. Java + Groovy вирішують цю проблему. Java для статичних матеріалів, Groovy для динамічних речей.
ubiquibacon

6
@MikedeKlerk, abc модуль справді вбудований у python. Трохи більше роботи над створенням цих зразків, оскільки вони значною мірою непотрібні в Python через альтернативні зразки, які вважаються «більш пітонічними». Для переважної більшості розробників це було б, як ви вже сказали, "взагалі не використовується". Однак, визнаючи, що є деякі дуже нішеві випадки, які справді потребують цих можливостей взаємодії, творці Python надали простий у використанні API, щоб зробити це можливим.
Девід

39

інтерфейс підтримує Python 2.7 та Python 3.4+.

Щоб встановити інтерфейс, потрібно

pip install python-interface

Приклад коду:

from interface import implements, Interface

class MyInterface(Interface):

    def method1(self, x):
        pass

    def method2(self, x, y):
        pass


class MyClass(implements(MyInterface)):

    def method1(self, x):
        return x * 2

    def method2(self, x, y):
        return x + y

7
Головною перевагою цієї бібліотеки, IMHO, є ранній збій, який вона дає вам: Якщо ваш клас неправильно реалізує вказаний інтерфейс, ви отримуєте виняток, як тільки клас читається в ньому - вам навіть не потрібно його використовувати . Маючи власний абстрактний базовий клас Python, ви отримуєте виняток при першому інстанціюванні свого класу, що може бути набагато пізніше.
Ганс

Це непотрібно, ABC пропонує подібний, вбудований функціонал.
Даніель

@DanielCasares пропонує ABC фактичний інтерфейс, чи ти маєш на увазі, що абстрактні класи без стану чи реалізації є рішенням, яке пропонує ABC?
asaf92

36

Реалізація інтерфейсів з абстрактними базовими класами набагато простіше в сучасному Python 3, і вони служать метою як інтерфейсний контракт для розширень плагінів.

Створіть інтерфейс / абстрактний базовий клас:

from abc import ABC, abstractmethod

class AccountingSystem(ABC):

    @abstractmethod
    def create_purchase_invoice(self, purchase):
        pass

    @abstractmethod
    def create_sale_invoice(self, sale):
        log.debug('Creating sale invoice', sale)

Створіть звичайний підклас та замініть всі абстрактні методи:

class GizmoAccountingSystem(AccountingSystem):

    def create_purchase_invoice(self, purchase):
        submit_to_gizmo_purchase_service(purchase)

    def create_sale_invoice(self, sale):
        super().create_sale_invoice(sale)
        submit_to_gizmo_sale_service(sale)

Ви необов'язково можете мати спільну реалізацію в абстрактних методах, як у create_sale_invoice(), викликуючи це з super()явним чином у підкласі, як зазначено вище.

Невміння підкласу, який не реалізує всі абстрактні методи, не вдається:

class IncompleteAccountingSystem(AccountingSystem):
    pass

>>> accounting = IncompleteAccountingSystem()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class IncompleteAccountingSystem with abstract methods
create_purchase_invoice, create_sale_invoice

Ви також можете мати абстрактні властивості, статичні та класичні методи, комбінуючи відповідні анотації із @abstractmethod.

Абстрактні базові класи чудово підходять для впровадження систем на основі плагінів. Усі імпортовані підкласи класу доступні через __subclasses__(), тому якщо ви завантажуєте всі класи з каталогу плагінів importlib.import_module()і якщо вони підкласирують базовий клас, ви маєте прямий доступ до них через, __subclasses__()і ви можете бути впевнені, що контракт інтерфейсу виконується для всіх їх під час інстанції.

Ось реалізація завантаження плагінів для AccountingSystemприкладу вище:

...
from importlib import import_module

class AccountingSystem(ABC):

    ...
    _instance = None

    @classmethod
    def instance(cls):
        if not cls._instance:
            module_name = settings.ACCOUNTING_SYSTEM_MODULE_NAME
            import_module(module_name)
            subclasses = cls.__subclasses__()
            if len(subclasses) > 1:
                raise InvalidAccountingSystemError('More than one '
                        f'accounting module: {subclasses}')
            if not subclasses or module_name not in str(subclasses[0]):
                raise InvalidAccountingSystemError('Accounting module '
                        f'{module_name} does not exist or does not '
                        'subclass AccountingSystem')
            cls._instance = subclasses[0]()
        return cls._instance

Тоді ви можете отримати доступ до об'єкта плагіна системи обліку через AccountingSystemклас:

>>> accountingsystem = AccountingSystem.instance()

(Натхненний цим повідомленням PyMOTW-3 .)


Питання: Що означає назва модуля "ABC"?
Себастьян Нільсен

"ABC" означає "Абстрактні базові класи", дивіться офіційні документи
mrts

31

Існують сторонні реалізації інтерфейсів для Python (найпопулярнішим є Zope , також використовується в Twisted ), але частіше кодери Python вважають за краще використовувати більш багату концепцію, відому як "Абстрактний базовий клас" (ABC), який поєднує інтерфейс з можливість також мати деякі аспекти впровадження. Азбука особливо добре підтримується в Python 2.6 та пізніших версіях, дивіться PEP , але навіть у більш ранніх версіях Python вони зазвичай сприймаються як "шлях" - просто визначте клас, деякі методи якого піднімають NotImplementedErrorтак, що підкласи будуть помітивши, що їм краще перекрити ці методи! -)


3
Існують сторонні реалізації інтерфейсів для Python Що це означає? Чи можете ви поясніть, будь ласка, ABC?
Pratik Deoghare

2
Ну, я візьму на себе питання про те, що ABC "багатший". ;) Є речі zope.interface може зробити це, що ABC не може так само, як і навпаки. Але в іншому випадку ви як завжди праві. +1
Леннарт Регебро

1
@Alfred: Це означає, що такі модулі, як zope.interface, не включені до стандартної бібліотеки, але доступні у pypi.
Леннарт Регебро

Мені ще важко впоратися з концепцією ABC. Чи можна було б комусь переписати twistedmatrix.com/documents/current/core/howto/components.html (IMHO, відмінне пояснення концепції інтерфейсів) з точки зору ABC. Чи має сенс?
mcepl

21

Щось подібне (може не працювати, оскільки у мене немає Python):

class IInterface:
    def show(self): raise NotImplementedError

class MyClass(IInterface):
    def show(self): print "Hello World!"

2
Що мені робити __init__(self)з конструктором?
Pratik Deoghare

1
До вас. Оскільки немає перевірки часу компіляції щодо побудови об'єкта з абстрактного класу, ви не отримаєте жодного захисту під час кодування / компіляції. Буде успадкований конструктор, тому об’єкт буде створений, просто будьте "порожнім". Ви самі вирішуєте, чи вам краще, дозволити це статися і виправити помилки пізніше, або явно зупинити програму тут і там, застосовуючи аналогічний конструктор, який викидає виняток.
Банді-Т

1
Ось чому abc.ABCнабагато краще, ніж підвищення NotImplementedError- інстанція abc.ABCпідкласу, який не реалізує всі абстрактні методи, виходить з ладу рано, тому ви захищені від помилок. Дивіться мою відповідь нижче, як виглядає помилка.
мрц

8

Я розумію, що інтерфейси не такі необхідні в динамічних мовах, як Python. В Java (або C ++ з його абстрактним базовим класом) інтерфейси - це засоби для забезпечення того, що, наприклад, ви передаєте потрібний параметр, здатний виконувати набір завдань.

Наприклад, якщо у вас спостерігач і спостережуваний, спостережуваний зацікавлений у підписці об’єктів, що підтримують інтерфейс IObserver, який, у свою чергу, notifyдіє. Це перевіряється під час компіляції.

У Python немає такого поняття, як compile timeі пошук способів виконується під час виконання. Крім того, можна перекрити пошук за допомогою магічних методів __getattr __ () або __getattribute __ (). Іншими словами, ви можете передати, як спостерігач, будь-який об'єкт, який може повернути дзвінок, що отримує доступ до notifyатрибута.

Це підводить мене до висновку, що інтерфейси в Python існують - це просто їхнє перенесення відкладається до моменту, коли вони фактично використовуються

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.