假设我想创建一个名为
Document
的抽象基类,并且希望其所有子类实现名为 from_paragraphs
的类方法,该方法从一系列 Paragraph
对象构造文档。但是, LegalDocument
只能从 LegalParagraph
对象构造,而 AcademicDocument
只能从 AcademicParagraph
对象构造。
我的直觉是这样做:
from abc import ABC, abstractmethod
from typing import Sequence
class Document(ABC):
@classmethod
@abstractmethod
def from_paragraphs(cls, paragraphs: Sequence["Paragraph"]):
pass
class LegalDocument(Document):
@classmethod
def from_paragraphs(cls, paragraphs: Sequence["LegalParagraph"]):
return # some logic here...
class AcademicDocument(Document):
@classmethod
def from_paragraphs(cls, paragraphs: Sequence["AcademicParagraph"]):
return # some logic here...
class Paragraph:
text: str
class LegalParagraph(Paragraph):
pass
class AcademicParagraph(Paragraph):
pass
然而,Pyright 对此有所抱怨,因为派生类上的
from_paragraphs
违反了里氏替换原则。如何表达我想要的?
这种模式称为工厂模式:根据输入,您会得到不同类型的对象。你那里的东西不起作用,因为:
# Because Document should not have knowledge of derived class:
doc = Document.from_paragraphs(...)
# Because type mismatch
doc = LegalDocument.from_paragraphs([AcademicParagraphs()])
这是我解决这个问题的方法:
class Document:
def __init__(self, paragraphs):
self.paragraphs = paragraphs
class LegalDocument(Document):
pass
class AcademicDocument(Document):
pass
class Paragraph:
def __init__(self, text):
self.text = text
class LegalParagraph(Paragraph):
pass
class AcademicParagraph(Paragraph):
pass
def create_document(*paragraphs):
# assume that all paragraphs are of the same type
if isinstance(paragraphs[0], LegalParagraph):
klass = LegalDocument
elif isinstance(paragraphs[0], AcademicParagraph):
klass = AcademicDocument
else:
raise TypeError()
return klass(paragraphs)
d1 = create_document(LegalParagraph("foo"), LegalParagraph("bar"))
assert isinstance(d1, LegalDocument)
d2 = create_document(AcademicParagraph("moo"))
assert isinstance(d2, AcademicDocument)
注释
__ini__()
就足够了create_document
,它将创建一个文档,其中类型取决于输入Document.__new__()
方法,但这需要一些关于 __new__()
工作原理的知识,但并不是每个人都知道这一点。