«Интерфейсы» в Python: да или нет?

Поэтому я начинаю проект, используя Python, потратив значительное количество времени на статическую землю. Я видел некоторые проекты, которые создают «интерфейсы», которые на самом деле являются просто классами без каких-либо реализаций. Раньше я издевался над этой идеей и игнорировал эту часть этих проектов. Но сейчас я начинаю согреваться до этой идеи.

Просто мы поняли, что интерфейс в Python будет выглядеть примерно так:

class ISomething(object): def some_method(): pass def some_other_method(some_argument): pass 

Обратите внимание, что вы не передаете себя ни одному из методов, тем самым требуя, чтобы метод был переопределен для вызова. Я рассматриваю это как хорошую форму документации и полноте тестирования.

Итак, каково мнение всех насчет этой идеи? Могу ли я промыть мозги всем программированием на C #, которое я сделал, или это хорошая идея?

В некоторых случаях интерфейсы могут быть очень удобными. Twisted довольно широко использует интерфейсы Zope , а в проекте, который я работал над интерфейсами Zope, работал очень хорошо. Черты Enthought упаковали недавно добавленные интерфейсы , но у меня нет опыта с ними.

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

Я не уверен, в чем дело. Интерфейсы (такой формы, во всяком случае) в значительной степени работают вокруг отсутствия множественного наследования. Но у Python есть MI, так почему бы просто не сделать абстрактный класс?

 class Something(object): def some_method(self): raise NotImplementedError() def some_other_method(self, some_argument): raise NotImplementedError() 

В Python 2.6 и более поздних версиях вместо этого вы можете использовать абстрактные базовые классы . Они полезны, потому что затем вы можете проверить, что-то реализует данный ABC, используя «isinstance». Как обычно в Python, концепция не так строго соблюдается, как это было бы на строгом языке, но это удобно. Кроме того, есть хорошие идиоматические способы объявления абстрактных методов с декораторами – см. Приведенную выше ссылку для примеров.

Питонический путь – «Просить прощения, а не получать разрешения». Интерфейсы связаны с получением разрешения на выполнение некоторой операции над объектом. Python предпочитает это:

 def quacker(duck): try: duck.quack(): except AttributeError: raise ThisAintADuckException 

Я не думаю, что интерфейсы ничего не добавят в среду кода.

  • Внедрение определения метода происходит без них. Если объект, который, как ожидается, будет иметь Foo и имеет метод bar() , и он не будет, он будет вызывать AttributeError .
  • Просто убедитесь, что метод интерфейса определен, не гарантирует его правильность; в любом случае должны проводиться тесты на основе поведения.
  • Так же эффективно писать страницу «прочитать это или умереть», описывая, какие методы должен поддерживать ваш объект, чтобы быть совместимым с тем, что вы подключаете к нему, поскольку у вас есть сложные док-последовательности в классе интерфейса, так как вы, вероятно, будете иметь тесты для него в любом случае. Один из этих тестов может быть стандартным для всех совместимых объектов, которые будут проверять тип вызова и возврата каждого базового метода.

Кажется мне ненужным – когда я пишу такие классы, я обычно просто делаю базовый класс (ваш ISomething ) без методов и упоминаю в реальной документации, какие подклассы методов должны переопределять.

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

Я собираюсь сделать что-то подобное с моим проектом Python, единственное, что я бы добавил:

  • Дополнительные длинные, глубинные строки для каждого интерфейса и все абстрактные методы.
  • Я бы добавил все необходимые аргументы, чтобы был окончательный список.
  • Вызовите исключение вместо «pass».
  • Префикс всех методов, поэтому они, очевидно, являются частью интерфейса – интерфейс Foo: def foo_method1 ()

Я лично использую интерфейсы много в сочетании с Zope Component Architecture (ZCA). Преимущество состоит не столько в том, чтобы иметь интерфейсы, сколько в том, чтобы использовать их с адаптерами и утилитами (одноточечными).

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

Первоначальный класс:

 class MyClass(object): implements(ISomething) def do_something(self): return "foo" 

Тогда представьте интерфейс ISomethingElse имеет метод do_something_else (). Адаптер может выглядеть так:

 class SomethingElseAdapter(object): implements(ISomethingElse) adapts(ISomething) def __init__(self, context): self.context = context def do_something_else(): return self.context.do_something()+"bar" 

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

 >>> obj = MyClass() >>> print obj.do_something() "foo" >>> adapter = ISomethingElse(obj) >>> print adapter.do_something_else() "foobar" 

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

Это, конечно, в основном полезно для фреймворков / библиотек.

Я думаю, что для того, чтобы привыкнуть к этому, требуется некоторое время, но я больше не хочу жить без него. Но, как сказано ранее, также верно, что вам нужно точно подумать, где это имеет смысл, а где нет. Разумеется, интерфейсы сами по себе также могут быть полезны для документации API. Это также полезно для модульных тестов, где вы можете проверить, действительно ли ваш класс реализует этот интерфейс. И последнее, но не в последнюю очередь мне нравится начинать с написания интерфейса и некоторых доктрин, чтобы получить представление о том, что я на самом деле собираюсь кодировать.

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

Глиф Лефковиц (из витой славы) недавно написал статью по этой теме . Лично я не чувствую необходимости в интерфейсах, но YMMV.

Вы смотрели на PyProtocols? он имеет приятную реализацию интерфейса, на которую вы должны смотреть.