Оглавление
- Обзор интерфейса Python
- Неформальные интерфейсы
- Использование метаклассов
- Использование виртуальных базовых классов
- Формальные Интерфейсы
- Использование abc.ABCMeta
- Использование .__ subclasshook __ ()
- Использование abc для регистрации виртуального подкласса
- Использование обнаружения подкласса с регистрацией
- Использование объявления абстрактного метода
- Интерфейсы на других языках
- Java
- C ++
- Go
- Вывод
- Интерфейсы играют важную роль в разработке программного обеспечения. По мере роста приложения управлять обновлениями и изменениями в кодовой базе становится все сложнее. Чаще всего у вас возникают классы, которые выглядят очень похожими, но не связаны между собой, что может привести к некоторой путанице. В этом руководстве вы увидите, как вы можете использовать интерфейс Python, чтобы определить, какой класс следует использовать для решения текущей проблемы.
В этом уроке вы сможете:
- Понять, как работают интерфейсы, и предостережения при создании интерфейса Python.
- Понять, насколько полезны интерфейсы в динамическом языке, таком как Python
- Реализовать неформальный интерфейс Python
- Используйте
abc.ABCMeta
и@abc.abstractmethod
для реализации формального интерфейса Python
Интерфейсы в Python обрабатываются иначе, чем в большинстве других языков, и они могут различаться по сложности своего дизайна. К концу этого урока вы будете лучше понимать некоторые аспекты модели данных Python, а также то, как интерфейсы в Python сравниваются с интерфейсами в таких языках, как Java, C ++ и Go.
Обзор интерфейса Python
На высоком уровне интерфейс выступает в качестве проекта для разработки классов. Как и классы, интерфейсы определяют методы. В отличие от классов, эти методы являются абстрактными. Абстрактный метод является одним , что интерфейс просто определяет. Это не реализует методы. Это делается классами, которые затем реализуют интерфейс и придают конкретное значение абстрактным методам интерфейса.
Подход Python к дизайну интерфейса несколько отличается по сравнению с такими языками, как Java , Go и C ++. Все эти языки имеют interface
ключевое слово, а Python – нет. Python далее отклоняется от других языков в одном другом аспекте. Не требуется, чтобы класс, реализующий интерфейс, определял все абстрактные методы интерфейса.
Неформальные интерфейсы
В определенных обстоятельствах вам могут не понадобиться строгие правила формального интерфейса Python. Динамическая природа Python позволяет реализовать неформальный интерфейс . Неформальный интерфейс Python – это класс, который определяет методы, которые могут быть переопределены, но строгого контроля не существует.
В следующем примере вы познакомитесь с инженером по обработке данных, которому необходимо извлекать текст из различных неструктурированных типов файлов, таких как PDF-файлы и электронные письма. Вы создадите неформальный интерфейс, который определяет методы, которые будут присутствовать как в конкретном классе, так PdfParser
и в EmlParser
конкретном:
class InformalParserInterface:
def load_data_source(self, path: str, file_name: str) -> str:
"""Load in the file for extracting text."""
pass
def extract_text(self, full_file_name: str) -> dict:
"""Extract text from the currently loaded file."""
pass
InformalParserInterface
определяет два метода .load_data_source()
и .extract_text()
. Эти методы определены, но не реализованы. Реализация произойдет, как только вы создадите конкретные классы, которые наследуются от InformalParserInterface
.
Как видите, InformalParserInterface
выглядит идентично стандартному классу Python. Вы полагаетесь на утку, чтобы сообщить пользователям, что это интерфейс и должен использоваться соответствующим образом.
Примечание: не слышали о печати утки ? Этот термин говорит, что если у вас есть объект, который выглядит как утка, ходит как утка и крякает как утка, то это должна быть утка! Чтобы узнать больше, проверьте Duck Typing .
Имея в виду утки, вы определяете два класса, которые реализуют InformalParserInterface
. Чтобы использовать ваш интерфейс, вы должны создать конкретный класс. Класс бетона подкласс интерфейса , который обеспечивает реализацию методов интерфейса. Вы создадите два конкретных класса для реализации вашего интерфейса. Во-первых PdfParser
, вы будете использовать для анализа текста из файлов PDF:
class PdfParser(InformalParserInterface):
"""Extract text from a PDF"""
def load_data_source(self, path: str, file_name: str) -> str:
"""Overrides InformalParserInterface.load_data_source()"""
pass
def extract_text(self, full_file_path: str) -> dict:
"""Overrides InformalParserInterface.extract_text()"""
pass
Конкретная реализация InformalParserInterface
теперь позволяет извлекать текст из файлов PDF.
Второй конкретный класс EmlParser
, который вы будете использовать для анализа текста из электронных писем:
class EmlParser(InformalParserInterface):
"""Extract text from an email"""
def load_data_source(self, path: str, file_name: str) -> str:
"""Overrides InformalParserInterface.load_data_source()"""
pass
def extract_text_from_email(self, full_file_path: str) -> dict:
"""A method defined only in EmlParser.
Does not override InformalParserInterface.extract_text()
"""
pass
Конкретная реализация InformalParserInterface
теперь позволяет извлекать текст из файлов электронной почты.
До сих пор, вы уже определили два конкретных реализаций из InformalPythonInterface
. Тем не менее, обратите внимание, что EmlParser
не может правильно определить .extract_text()
. Если бы вы проверяли, EmlParser
реализует ли инструмент InformalParserInterface
, вы бы получили следующий результат: >>>
>>> # Check if both PdfParser and EmlParser implement InformalParserInterface
>>> issubclass(PdfParser, InformalParserInterface)
True
>>> issubclass(EmlParser, InformalParserInterface)
True
Это вернулось бы True
, что представляет собой небольшую проблему, поскольку нарушает определение интерфейса!
Теперь проверьте порядок разрешения методы (MRO) из PdfParser
и EmlParser
. Это говорит вам о суперклассах рассматриваемого класса, а также о порядке, в котором они ищут для выполнения метода. Вы можете просмотреть MRO класса, используя метод dunder cls.__mro__
: >>>
>>> PdfParser.__mro__
(__main__.PdfParser, __main__.InformalParserInterface, object)
>>> EmlParser.__mro__
(__main__.EmlParser, __main__.InformalParserInterface, object)
Такие неформальные интерфейсы хороши для небольших проектов, где только несколько разработчиков работают над исходным кодом. Однако, по мере того, как проекты становятся больше, а команды растут, это может привести к тому, что разработчики будут тратить бесчисленные часы на поиск трудно выявляемых логических ошибок в базе кода!
Использование метаклассов
В идеале вы хотели issubclass(EmlParser, InformalParserInterface
бы вернуться, False
когда реализующий класс не определяет все абстрактные методы интерфейса. Для этого вы создадите метакласс с именем ParserMeta
. Вы будете переопределять два более сложных метода:
.__instancecheck__()
.__subclasscheck__()
В приведенном ниже блоке кода вы создаете класс с именем, UpdatedInformalParserInterface
который строится из ParserMeta
метакласса:
class ParserMeta(type):
"""A Parser metaclass that will be used for parser class creation.
"""
def __instancecheck__(cls, instance):
return cls.__subclasscheck__(type(instance))
def __subclasscheck__(cls, subclass):
return (hasattr(subclass, 'load_data_source') and
callable(subclass.load_data_source) and
hasattr(subclass, 'extract_text') and
callable(subclass.extract_text))
class UpdatedInformalParserInterface(metaclass=ParserMeta):
"""This interface is used for concrete classes to inherit from.
There is no need to define the ParserMeta methods as any class
as they are implicitly made available via .__subclasscheck__().
"""
pass
Теперь, когда это ParserMeta
и UpdatedInformalParserInterface
было создано, вы можете создавать свои конкретные реализации.
Сначала создайте новый класс для разбора PDF-файлов PdfParserNew
:
class PdfParserNew:
"""Extract text from a PDF."""
def load_data_source(self, path: str, file_name: str) -> str:
"""Overrides UpdatedInformalParserInterface.load_data_source()"""
pass
def extract_text(self, full_file_path: str) -> dict:
"""Overrides UpdatedInformalParserInterface.extract_text()"""
pass
Здесь PdfParserNew
переопределяет .load_data_source()
и .extract_text()
поэтому issubclass(PdfParserNew, UpdatedInformalParserInterface)
должен вернуться True
.
В следующем блоке кода у вас есть новая реализация анализатора электронной почты EmlParserNew
:
class EmlParserNew:
"""Extract text from an email."""
def load_data_source(self, path: str, file_name: str) -> str:
"""Overrides UpdatedInformalParserInterface.load_data_source()"""
pass
def extract_text_from_email(self, full_file_path: str) -> dict:
"""A method defined only in EmlParser.
Does not override UpdatedInformalParserInterface.extract_text()
"""
pass
Здесь у вас есть метакласс, который используется для создания UpdatedInformalParserInterface
. Используя метакласс, вам не нужно явно определять подклассы. Вместо этого подкласс должен определять требуемые методы . Если этого не произойдет, то issubclass(EmlParserNew, UpdatedInformalParserInterface)
вернется False
.
Запуск issubclass()
на ваших конкретных классах приведет к следующему: >>>
>>> issubclass(PdfParserNew, UpdatedInformalParserInterface)
True
>>> issubclass(EmlParserNew, UpdatedInformalParserInterface)
False
Как и ожидалось, EmlParserNew
не является подклассом, UpdatedInformalParserInterface
поскольку .extract_text()
не был определен в EmlParserNew
.
Теперь давайте посмотрим на MRO: >>>
>>> PdfParserNew.__mro__
(<class '__main__.PdfParserNew'>, <class 'object'>)
Как видите, UpdatedInformalParserInterface
это суперкласс PdfParserNew
, но в MRO его нет. Это необычное поведение вызвано тем , что UpdatedInformalParserInterface
представляет собой виртуальный базовый класс из PdfParserNew
.
Использование виртуальных базовых классов
В предыдущем примере issubclass(EmlParserNew, UpdatedInformalParserInterface)
вернулось True
, хотя UpdatedInformalParserInterface
и не появилось в EmlParserNew
MRO. Это потому , что UpdatedInformalParserInterface
это виртуальный базовый класс из EmlParserNew
.
Основное различие между этими и стандартными подклассами состоит в том, что виртуальные базовые классы используют .__subclasscheck__()
метод dunder, чтобы неявно проверить, является ли класс виртуальным подклассом суперкласса. Кроме того, виртуальные базовые классы не отображаются в подклассе MRO.
Посмотрите на этот блок кода:
class PersonMeta(type):
"""A person metaclass"""
def __instancecheck__(cls, instance):
return cls.__subclasscheck__(type(instance))
def __subclasscheck__(cls, subclass):
return (hasattr(subclass, 'name') and
callable(subclass.name) and
hasattr(subclass, 'age') and
callable(subclass.age))
class PersonSuper:
"""A person superclass"""
def name(self) -> str:
pass
def age(self) -> int:
pass
class Person(metaclass=PersonMeta):
"""Person interface built from PersonMeta metaclass."""
pass
Здесь у вас есть настройки для создания ваших виртуальных базовых классов:
- Метакласс
PersonMeta
- Базовый класс
PersonSuper
- Интерфейс Python
Person
Теперь, когда настройка для создания виртуальных базовых классов завершена, вы определите два конкретных класса, Employee
и Friend
. В Employee
класс наследует от PersonSuper
, в то время как Friend
неявно наследует от Person
:
# Inheriting subclasses
class Employee(PersonSuper):
"""Inherits from PersonSuper
PersonSuper will appear in Employee.__mro__
"""
pass
class Friend:
"""Built implicitly from Person
Friend is a virtual subclass of Person since
both required methods exist.
Person not in Friend.__mro__
"""
def name(self):
pass
def age(self):
pass
Хотя Friend
не явно наследоваться от Person
, он реализует .name()
и .age()
, таким образом , Person
становится виртуальным базовым классом из Friend
. Когда вы запустите, issubclass(Friend, Person)
он должен вернуть True
, то Friend
есть это подкласс Person
.
Следующие UML диаграмма показывает , что происходит , когда вы звоните issubclass()
по Friend
классу:

PersonMeta
Взглянув на это , вы заметите, что есть еще один метод, который называется dunder .__instancecheck__()
. Этот метод используется, чтобы проверить Friend
, созданы ли экземпляры из Person
интерфейса. Ваш код будет звонить, .__instancecheck__()
когда вы используете isinstance(Friend, Person)
.
Формальные Интерфейсы
Неформальные интерфейсы могут быть полезны для проектов с небольшой базой кода и ограниченным числом программистов. Однако неформальные интерфейсы были бы неправильным подходом для более крупных приложений. Чтобы создать формальный интерфейс Python , вам понадобится еще несколько инструментов из abc
модуля Python .
С помощью abc.ABCMeta
Для того, чтобы обеспечить соблюдение подкласса экземпляра абстрактных методов, вы будете использовать встроенную команду Питона ABCMeta
из abc
модуля. Возвращаясь к своему UpdatedInformalParserInterface
интерфейсу, вы создали свой собственный метакласс ParserMeta
с переопределенными методами dunder .__instancecheck__()
и .__subclasscheck__()
.
Вместо того, чтобы создавать свой собственный метакласс, вы будете использовать его abc.ABCMeta
как метакласс. Затем вы перезапишете .__subclasshook__()
вместо .__instancecheck__()
и .__subclasscheck__()
, поскольку это создает более надежную реализацию этих более сложных методов.
С помощью .__subclasshook__()
Вот реализация FormalParserInterface
использования в abc.ABCMeta
качестве вашего метакласса:
import abc
class FormalParserInterface(metaclass=abc.ABCMeta):
@classmethod
def __subclasshook__(cls, subclass):
return (hasattr(subclass, 'load_data_source') and
callable(subclass.load_data_source) and
hasattr(subclass, 'extract_text') and
callable(subclass.extract_text))
class PdfParserNew:
"""Extract text from a PDF."""
def load_data_source(self, path: str, file_name: str) -> str:
"""Overrides FormalParserInterface.load_data_source()"""
pass
def extract_text(self, full_file_path: str) -> dict:
"""Overrides FormalParserInterface.extract_text()"""
pass
class EmlParserNew:
"""Extract text from an email."""
def load_data_source(self, path: str, file_name: str) -> str:
"""Overrides FormalParserInterface.load_data_source()"""
pass
def extract_text_from_email(self, full_file_path: str) -> dict:
"""A method defined only in EmlParser.
Does not override FormalParserInterface.extract_text()
"""
pass
Если вы бежите issubclass()
на PdfParserNew
и EmlParserNew
, затем issubclass()
вернется True
и False
, соответственно.
Использование abc
для регистрации виртуального подкласса
После того, как вы импортировали abc
модуль, вы можете напрямую зарегистрировать виртуальный подкласс , используя .register()
метаметод. В следующем примере вы регистрируете интерфейс Double
как виртуальный базовый класс встроенного __float__
класса:
class Double(metaclass=abc.ABCMeta):
"""Double precision floating point number."""
pass
Double.register(float)
Вы можете проверить эффект использования .register()
: >>>
>>> issubclass(float, Double)
True
>>> isinstance(1.2345, Double)
True
Используя .register()
мета-метод, вы успешно зарегистрировались Double
как виртуальный подкласс float
.
После того как вы зарегистрировались Double
, вы можете использовать его как декоратор класса, чтобы установить украшенный класс как виртуальный подкласс:
@Double.register
class Double64:
"""A 64-bit double-precision floating-point number."""
pass
print(issubclass(Double64, Double)) # True
Метод регистра декоратора помогает вам создать иерархию наследования пользовательских виртуальных классов.
Использование обнаружения подкласса с регистрацией
Вы должны быть осторожны, когда объединяетесь .__subclasshook__()
с .register()
, поскольку .__subclasshook__()
имеет приоритет над регистрацией виртуального подкласса. Чтобы убедиться, что зарегистрированные виртуальные подклассы приняты во внимание, вы должны добавить NotImplemented
в .__subclasshook__()
метод dunder. FormalParserInterface
Будет обновлен к следующему:
class FormalParserInterface(metaclass=abc.ABCMeta):
@classmethod
def __subclasshook__(cls, subclass):
return (hasattr(subclass, 'load_data_source') and
callable(subclass.load_data_source) and
hasattr(subclass, 'extract_text') and
callable(subclass.extract_text) or
NotImplemented)
class PdfParserNew:
"""Extract text from a PDF."""
def load_data_source(self, path: str, file_name: str) -> str:
"""Overrides FormalParserInterface.load_data_source()"""
pass
def extract_text(self, full_file_path: str) -> dict:
"""Overrides FormalParserInterface.extract_text()"""
pass
@FormalParserInterface.register
class EmlParserNew:
"""Extract text from an email."""
def load_data_source(self, path: str, file_name: str) -> str:
"""Overrides FormalParserInterface.load_data_source()"""
pass
def extract_text_from_email(self, full_file_path: str) -> dict:
"""A method defined only in EmlParser.
Does not override FormalParserInterface.extract_text()
"""
pass
print(issubclass(PdfParserNew, FormalParserInterface)) # True
print(issubclass(EmlParserNew, FormalParserInterface)) # True
Поскольку вы использовали регистрацию, вы можете видеть, что EmlParserNew
это считается виртуальным подклассом вашего FormalParserInterface
интерфейса. Это не то, что вы хотели, так EmlParserNew
как не отменяет .extract_text()
. Пожалуйста, соблюдайте осторожность при регистрации виртуального подкласса!
Использование объявления абстрактного метода
Абстрактный метод является методом , который объявлен интерфейсом Python, но это не может быть полезной реализация. Абстрактный метод должен быть переопределен конкретным классом, который реализует рассматриваемый интерфейс.
Чтобы создать абстрактные методы в Python, вы добавляете @abc.abstractmethod
декоратор к методам интерфейса. В следующем примере вы обновляете, FormalParserInterface
чтобы включить абстрактные методы .load_data_source()
и .extract_text()
:
class FormalParserInterface(metaclass=abc.ABCMeta):
@classmethod
def __subclasshook__(cls, subclass):
return (hasattr(subclass, 'load_data_source') and
callable(subclass.load_data_source) and
hasattr(subclass, 'extract_text') and
callable(subclass.extract_text) or
NotImplemented)
@abc.abstractmethod
def load_data_source(self, path: str, file_name: str):
"""Load in the data set"""
raise NotImplementedError
@abc.abstractmethod
def extract_text(self, full_file_path: str):
"""Extract text from the data set"""
raise NotImplementedError
class PdfParserNew(FormalParserInterface):
"""Extract text from a PDF."""
def load_data_source(self, path: str, file_name: str) -> str:
"""Overrides FormalParserInterface.load_data_source()"""
pass
def extract_text(self, full_file_path: str) -> dict:
"""Overrides FormalParserInterface.extract_text()"""
pass
class EmlParserNew(FormalParserInterface):
"""Extract text from an email."""
def load_data_source(self, path: str, file_name: str) -> str:
"""Overrides FormalParserInterface.load_data_source()"""
pass
def extract_text_from_email(self, full_file_path: str) -> dict:
"""A method defined only in EmlParser.
Does not override FormalParserInterface.extract_text()
"""
pass
В приведенном выше примере вы наконец создали формальный интерфейс, который будет вызывать ошибки, когда абстрактные методы не переопределяются. PdfParserNew
Экземпляра pdf_parser
, не возникает каких – либо ошибок, так как PdfParserNew
правильно перекрывая FormalParserInterface
абстрактные методы. Однако EmlParserNew
возникнет ошибка: >>>
>>> pdf_parser = PdfParserNew()
>>> eml_parser = EmlParserNew()
Traceback (most recent call last):
File "real_python_interfaces.py", line 53, in <module>
eml_interface = EmlParserNew()
TypeError: Can't instantiate abstract class EmlParserNew with abstract methods extract_text
Как видите, сообщение трассировки говорит вам, что вы не переопределили все абстрактные методы. Это поведение, которое вы ожидаете при создании формального интерфейса Python.
Интерфейсы на других языках
Интерфейсы появляются на многих языках программирования, и их реализация сильно отличается от языка к языку. В следующих нескольких разделах вы будете сравнивать интерфейсы в Python с Java, C ++ и Go.
Java
В отличие от Python, Java содержит interface
ключевое слово. Следуя примеру с анализатором файлов, вы объявляете интерфейс в Java следующим образом:
public interface FileParserInterface {
// Static fields, and abstract methods go here ...
public void loadDataSource();
public void extractText();
}
Теперь вы создадите два конкретных класса PdfParser
и EmlParser
, чтобы реализовать FileParserInterface
. Для этого вы должны использовать implements
ключевое слово в определении класса, например так:
public class EmlParser implements FileParserInterface {
public void loadDataSource() {
// Code to load the data set
}
public void extractText() {
// Code to extract the text
}
}
Продолжая ваш пример разбора файла, полнофункциональный интерфейс Java будет выглядеть примерно так:
import java.util.*;
import java.io.*;
public class FileParser {
public static void main(String[] args) throws IOException {
// The main entry point
}
public interface FileParserInterface {
HashMap<String, ArrayList<String>> file_contents = null;
public void loadDataSource();
public void extractText();
}
public class PdfParser implements FileParserInterface {
public void loadDataSource() {
// Code to load the data set
}
public void extractText() {
// Code to extract the text
}
}
public class EmlParser implements FileParserInterface {
public void loadDataSource() {
// Code to load the data set
}
public void extractText() {
// Code to extract the text
}
}
}
Как вы можете видеть, интерфейс Python дает вам гораздо больше гибкости при создании, чем интерфейс Java.
C ++
Как и Python, C ++ использует абстрактные базовые классы для создания интерфейсов. При определении интерфейса в C ++ вы используете ключевое слово virtual
для описания метода, который должен быть перезаписан в конкретном классе:
class FileParserInterface {
public:
virtual void loadDataSource(std::string path, std::string file_name);
virtual void extractText(std::string full_file_name);
};
Когда вы захотите реализовать интерфейс, вы дадите конкретное имя класса, затем двоеточие ( :
), а затем имя интерфейса. В следующем примере демонстрируется реализация интерфейса C ++:
class PdfParser : FileParserInterface {
public:
void loadDataSource(std::string path, std::string file_name);
void extractText(std::string full_file_name);
};
class EmlParser : FileParserInterface {
public:
void loadDataSource(std::string path, std::string file_name);
void extractText(std::string full_file_name);
};
Интерфейс Python и интерфейс C ++ имеют некоторые сходства в том, что они оба используют абстрактные базовые классы для имитации интерфейсов.
Go
Хотя синтаксис Go напоминает Python, язык программирования Go содержит interface
ключевое слово, например Java. Давайте создадим fileParserInterface
в Go:
type fileParserInterface interface {
loadDataSet(path string, filename string)
extractText(full_file_path string)
}
Большая разница между Python и Go состоит в том, что в Go нет классов. Скорее Go похож на C тем, что использует struct
ключевое слово для создания структур. Структура похожа на класс в том , что структура содержит данные и методы. Однако, в отличие от класса, все данные и методы общедоступны. Бетонные конструкции в Go будут использованы для реализации fileParserInterface
.
Вот пример того, как Go использует интерфейсы:
package main
type fileParserInterface interface {
loadDataSet(path string, filename string)
extractText(full_file_path string)
}
type pdfParser struct {
// Data goes here ...
}
type emlParser struct {
// Data goes here ...
}
func (p pdfParser) loadDataSet() {
// Method definition ...
}
func (p pdfParser) extractText() {
// Method definition ...
}
func (e emlParser) loadDataSet() {
// Method definition ...
}
func (e emlParser) extractText() {
// Method definition ...
}
func main() {
// Main entrypoint
}
В отличие от интерфейса Python, интерфейс Go создается с использованием структур и явного ключевого слова interface
.
Вывод
Python предлагает большую гибкость при создании интерфейсов. Неформальный интерфейс Python полезен для небольших проектов, где вы вряд ли запутаетесь в том, что представляют собой возвращаемые типы методов. По мере роста проекта потребность в формальном интерфейсе Python становится все более важной, поскольку становится все труднее выводить возвращаемые типы. Это гарантирует, что конкретный класс, который реализует интерфейс, перезаписывает абстрактные методы.
Теперь вы можете:
- Понять, как работают интерфейсы, и предостережения по созданию интерфейса Python.
- Понять полезность интерфейсов в динамическом языке, таком как Python
- Реализовать формальные и неформальные интерфейсы в Python
- Сравните интерфейсы Python с интерфейсами таких языков, как Java, C ++ и Go
Теперь, когда вы познакомились с тем, как создать интерфейс Python, добавьте интерфейс Python в ваш следующий проект, чтобы увидеть его полезность в действии!