Python 3.10中list[SoftBack]与list[Book]类型不兼容的Pylance报错问题
解决Pylance中List子类类型不兼容的报错
问题背景
代码运行正常,但VS Code的Pylance对以下代码报错:
books: list[SoftBack] = [softback_book_1, softback_book_2] processed_books = BookProcessor(books).process()
报错原因是BookProcessor声明接收list[Book]并返回list[Book],但实际传入list[SoftBack](SoftBack是Book的子类),且期望返回list[SoftBack]。报错信息如下:
(variable) books: list[SoftBack] Argument of type "list[SoftBack]" cannot be assigned to parameter "books" of type "list[Book]" in function "__init__" "list[SoftBack]" is incompatible with "list[Book]" TypeVar "_T@list" is invariant "SoftBack" is incompatible with "Book" Pylancereport(GeneralTypeIssues)
附带完整代码:
""" Book Testing """ from abc import ABC, abstractmethod from copy import deepcopy from typing import Any class Book(ABC): """ A generic book. """ name: str @abstractmethod def __init__(self, *args: Any | None, **kwargs: Any | None) -> None: """ Abstract initialiser. """ raise NotImplementedError class SoftBack(Book): """ A softback book. """ name: str def __init__(self, name: str) -> None: self.name = name class BookProcessor: """ A simple book processor. """ books: list[Book] def __init__(self, books: list[Book]) -> None: self.books = books def process(self) -> list[Book]: """ Add the string '_processed' to book names, returning a new list of books. """ processed_books: list[Book] = [] for book in self.books: new_book = deepcopy(book) new_book.name += '_processed' processed_books.append(new_book) return processed_books def main(): """ Main function. """ softback_book_1 = SoftBack(name='book_01') softback_book_2 = SoftBack(name='book_02') books: list[SoftBack] = [softback_book_1, softback_book_2] processed_books = BookProcessor(books).process() for processed_book in processed_books: print(processed_book.name) if __name__ == '__main__': main()
问题原因
Pylance的报错是合理的,因为Python的list是不变类型(invariant):list[SubType]不能被视为list[SuperType]的子类,反之亦然。这是因为如果允许将list[SoftBack]传给接收list[Book]的参数,理论上代码可以向这个列表添加其他Book子类(比如HardBack),导致列表中混入非SoftBack类型,破坏类型安全性。
解决方案:使用泛型实现协变
要让BookProcessor支持接收任意Book子类的列表,并返回对应子类的列表,需要用typing.TypeVar定义一个协变的类型变量,结合泛型来修改BookProcessor的类型提示。
修改步骤:
- 导入
TypeVar和Generic - 定义一个协变的类型变量
T,绑定到Book - 将
BookProcessor改为泛型类,使用T作为列表元素的类型 - 修改
__init__和process方法的类型提示,使其使用T
修改后的代码:
""" Book Testing """ from abc import ABC, abstractmethod from copy import deepcopy from typing import Any, TypeVar, Generic # 定义协变的类型变量T,绑定到Book类型 T = TypeVar('T', bound='Book', covariant=True) class Book(ABC): """ A generic book. """ name: str @abstractmethod def __init__(self, *args: Any | None, **kwargs: Any | None) -> None: """ Abstract initialiser. """ raise NotImplementedError class SoftBack(Book): """ A softback book. """ name: str def __init__(self, name: str) -> None: self.name = name class BookProcessor(Generic[T]): """ A simple book processor. """ books: list[T] def __init__(self, books: list[T]) -> None: self.books = books def process(self) -> list[T]: """ Add the string '_processed' to book names, returning a new list of books. """ processed_books: list[T] = [] for book in self.books: new_book = deepcopy(book) new_book.name += '_processed' processed_books.append(new_book) return processed_books def main(): """ Main function. """ softback_book_1 = SoftBack(name='book_01') softback_book_2 = SoftBack(name='book_02') books: list[SoftBack] = [softback_book_1, softback_book_2] processed_books = BookProcessor(books).process() for processed_book in processed_books: print(processed_book.name) if __name__ == '__main__': main()
说明:
covariant=True表示T是协变的,即如果SoftBack是Book的子类,那么list[SoftBack]可以适配list[T](当T绑定到Book时)- 泛型类
BookProcessor[T]会根据传入的列表元素类型自动推断T的具体类型,这样process方法会返回与输入同类型的列表 - 这样修改后,Pylance不会再报错,同时保留了类型安全性:
BookProcessor只能处理Book及其子类的列表,且返回的列表元素类型与输入一致
内容的提问来源于stack exchange,提问作者Jak
相关产品推荐
相关产品推荐

