【问题标题】:Python typing: Use a class variable's value as return type of a (mixin) methodPython类型:使用类变量的值作为(mixin)方法的返回类型
【发布时间】:2020-09-07 00:51:34
【问题描述】:

总结

如何使用类变量的值(它是一个类对象)作为 Python 类型 / mypy 的(mixin)方法的返回类型?

这是一个最小的示例,您将在下面找到更复杂的真实用例:

from typing import Generic, Type, TypeVar


T = TypeVar('T')


class Base(Generic[T]):
    return_type: Type[T]
    value: T  # This attribute is only needed for this minimal example


class Mixin:
    def get(self: Base[T]) -> T:  # mypy: The erased type of self "Base" is not a supertype of its class "Mixin"
        return self.return_type(self.value)  # mypy: Too many arguments for "object"


class Concrete(Mixin, Base[int]):
    return_type = int

    def __init__(self):
        self.value = 3


c = Concrete()
x: str = c.get()  # mypy: expected incompatible types (str vs int) error :)

Base 设置为Mixin 的超类时,我可以摆脱第二个错误,但这并不是我真正想要的。不过,我现在知道如何正确定义return_type: Type[T]

我已经阅读了 python 类型文档以及 mypy 文档,但什么也没找到。网络搜索也没有产生有用的结果。

我真正想做的事

我目前正在编写一个架构类似于python-gitlab的REST客户端:

  • 用户使用知道 API URL 并执行所有 HTTP 请求的 ApiClient 类。

  • API 的端点由作为ApiClient 属性的REST 管理器类表示。根据端点的功能,REST 管理器可以列出端点的对象、获取单个对象或创建、更新和删除对象。

  • RestManager 返回和接收“哑”数据类(例如,attrspydantic 模型)

  • 具体的 REST 管理器子类化 RestManager 基类和用于 HTTP 操作的各种 mixin,例如用于通过 ID 获取单个对象的 GetMixin

  • 一个具体的 REST 管理器有一个类变量,它保存它要返回的对象的类。

  • 在mixin类中,我想表达“这个方法返回一个对象类的实例,即子类化restmanager定义为类变量”。

示例用法:

client = ApiClient('https://example.com/myapi/v1')
item = client.items.get(42)
assert isinstance(item, Item)

实施:

from typing import ClassVar, Type, TypeVar


T = TypeVar(T)


class Item:
    """Data class that represents objects of the "items" endpoint"""
    pass


class ApiClient:
    """Main object that the user works with."""
    def __init__(self, url: str):
        self.url = url
        # There is one manager instance for each endpoint of the API
        self.items = ItemManager(self) 
        # self.cats = CatManager(self)

    def http_get(self, path: str) -> 'Response':
        ...  # Request the proper url and return a response object


class RestManager:
    """Base class for REST managers."""
    _path: ClassVar[str]
    _obj_cls: ClassVar[Type[T]]  # Concrete subclasses set this with an object class, e.g., "Item"

    def __init__(self, client: ApiClient):
        self.client = client

    @property
    def path(self) -> str:
        return self._path


class GetMixin:
    """Mixin for getting a single object by ID"""
    def get(self: RestManager, id: int) -> T:  # Return type is the value the subclass' "_obj_cls" attribute
        response = self.client.http_get(f'{self.path}/{id}')
        return self._obj_cls(**response.json())


class ItemsManager(GetMixin, RestManager):
    """Concrete manager for "Item" objects."""
    _path = '/items'
    _obj_cls = Item  # This is the return type of ItemsManager.get()


client = ApiClient()
item = client.items.get(42)
assert isinstance(item, Item)

【问题讨论】:

    标签: python type-hinting mypy


    【解决方案1】:

    免责声明:我没有仔细阅读您的真实用例,所以我可能是错的。以下分析基于您的简化示例。

    我认为mypy 不支持这一点。目前mypy 假设(并且理所当然地)方法中self 的类型是类的子类型。

    但是,要使您的 mixin 工作,必须对可以混合的类的种类进行限制。例如,在您的简化示例中,该类必须具有 return_typevalue 属性。我的建议是您也可以将它们添加为您的 mixin 类的注释,结果如下:

    T = TypeVar('T')
    
    
    class Base(Generic[T]):
        return_type: Type[T]
        value: T  # This attribute is only needed for this minimal example
    
    
    class Mixin(Generic[T]):
        return_type: Type[T]
        value: T
    
        def get(self) -> T:  # annotation on `self` can be dropped
            return self.return_type(self.value)
    

    注意最后一行在mypy 中仍然是一个错误,因为它不能证明self.return_type__init__ 方法只接受一个参数。您可以使用 Python 3.7 中引入的 typing.Protocol 来解决此问题,但这可能有点矫枉过正。恕我直言,如果您有一些您确信是正确的简单代码,那么有时一点# type: ignore 最适合您。

    【讨论】:

    • 感谢您的友好回复。但它对我没有帮助。我猜目前唯一(不完美)工作得很好的解决方案是让 Mixin 从 Base 继承。 :-/
    • 也许,正确的方法是创建一个 Protocol 作为 Mixin 类的基础
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-08
    • 1970-01-01
    • 1970-01-01
    • 2015-07-18
    相关资源
    最近更新 更多