Реализация интерфейса в Python

30 апреля, 2020

Подписывайся на наш канал в Telegram, чтобы ежедневно совершенствоваться в Python. Там выходят задачи, полезные советы и сливы платных курсов - перейти

Оглавление

  • Обзор интерфейса 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. Вы будете переопределять два более сложных метода:

  1. .__instancecheck__()
  2. .__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и не появилось в EmlParserNewMRO. Это потому , что 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

Здесь у вас есть настройки для создания ваших виртуальных базовых классов:

  1. Метакласс PersonMeta
  2. Базовый класс PersonSuper
  3. Интерфейс 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 в ваш следующий проект, чтобы увидеть его полезность в действии!


Совершенствуй знания каждый день у нас в Телеграм-каналах

Вопросы, реклама — VK | Telegram