接口 - python
- 非正式【鸭子类型】
# 接口
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
# 接口的子类
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
# 1. 目前接口的子类没有实现【extract_text】,检查是否为 EmlParser implements InformalParserInterface, 应该为false才对
# 2. 这种非正式的接口对于只有少数开发人员在处理源代码的小型项目来说是很好的。然而,随着项目变得越来越大和团队不断壮大,这可能导致开发人员花费无数时间在代码库中寻找难以发现的逻辑错误!
>>> # Check if both PdfParser and EmlParser implement InformalParserInterface
>>> issubclass(EmlParser, InformalParserInterface)
True
>>> EmlParser.__mro__
(__main__.EmlParser, __main__.InformalParserInterface, object)
- 使用Metaclasses
理想情况下,您希望在实现类未定义所有接口的抽象方法时 issubclass(EmlParser, InformalParserInterface)返回False.
为此,您将创建一个名为ParserMeta. 您将覆盖两个双下方法:
.__instancecheck__()
.__subclasscheck__()
# 接口
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
# 接口的子类
# 子类1
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
# 子类2
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
# 结果
print(issubclass(EmlParserNew, UpdatedInformalParserInterface)) # False :因为没有实现extract_text(),所以不是子类
print(issubclass(PdfParserNew, UpdatedInformalParserInterface)) # True :是子类
# MRO
print(PdfParserNew.__mro__) # (<class '__main__.PdfParserNew'>, <class 'object'>)
# 虽然是子类,但是MRO中却没有出现,
# This unusual behavior is caused by the fact that UpdatedInformalParserInterface is a virtual base class of PdfParserNew.
# The key difference between these and standard subclasses is that virtual base classes use the .__subclasscheck__() dunder method to implicitly check if a class is a virtual subclass of the superclass.
Additionally, virtual base classes don’t appear in the subclass MRO.
- 使用Virtual Base Classes
# 【 creating your virtual base classes:】
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
# 【实现】
# 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
#
Although Friend does not explicitly inherit from Person, it implements .name() and .age(),
so Person becomes a virtual base class of Friend. When you run issubclass(Friend, Person) it should return True, meaning that Friend is a subclass of Person.
- 正式的接口实现【引子】
# Register a Virtual Subclass
class Double(metaclass=abc.ABCMeta):
"""Double precision floating point number."""
pass
Double.register(float) # 注册类的另外一种写法
# 注册虚拟类
@Double.register
class Double64:
"""A 64-bit double-precision floating-point number."""
pass
print(issubclass(Double64, Double)) # True
- 正式的接口实现【实现】
# 1. 不全面
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 # This is not what you wanted since EmlParserNew doesn’t override .extract_text().
# 2. 正式实现
class FormalParserInterface(metaclass=abc.ABCMeta):
@classmethod
def __subclasshook__(cls, subclass): # 原因 .__subclasshook__() in place of .__instancecheck__() and .__subclasscheck__(), as it creates a more reliable implementation of these dunder methods.
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
pdf_parser = PdfParserNew()
eml_parser = EmlParserNew() # 报错
https://realpython.com/python-interface/