[英]Python typing: Use a class variable's value as return type of a (mixin) method
我如何使用 class 變量的值(這是一個 class 對象)作為帶有 Python typing / 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
的 super class 時,我可以擺脫第二個錯誤,但這並不是我真正想要的。 不過,我現在知道如何正確定義return_type: Type[T]
。
我已經閱讀了 python 打字文檔以及 mypy 文檔,但一無所獲。 web 搜索也沒有產生有用的結果。
我目前正在編寫一個 REST 客戶端,其架構類似於python-gitlab :
用戶使用ApiClient
class,它知道 API URL 並執行所有 HTTP 請求。
API 的端點由作為 ApiClient 屬性的ApiClient
管理器類表示。 根據端點的功能,REST 管理器可以列出端點的對象,獲取單個 object,或者創建、更新和刪除 object。
具體的 REST 管理器將RestManager
基類 class 和用於 HTTP 操作的各種 mixin 子類化,例如,用於通過 ID 獲取單個GetMixin
的 GetMixin。
具體的 REST 管理器有一個 class 變量,該變量保存它將要返回的對象的 class。
在 mixin 類中,我想表達“此方法返回 object class 的實例,子類 restmanager 定義為 class 變量”。
用法示例:
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)
免責聲明:我沒有仔細閱讀您的真實用例,所以我可能是錯的。 以下分析基於您的簡化示例。
我認為mypy
不支持這一點。 目前mypy
假設(並且正確地如此)方法中的self
類型是 class 的子類型。
然而,為了讓你的 mixin 工作,必須對它可以混合的類的種類進行限制。 例如,在您的簡化示例中,class 必須具有return_type
和value
屬性。 我的建議是,您也可以將它們添加為您的 mixin class 的注釋,結果如下:
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__
方法只接受一個參數。 您可以使用typing.Protocol
3.7 中引入的 typing.Protocol 來解決此問題,但這可能有點矯枉過正。 恕我直言,如果您有一些您確信是正確的簡單代碼,有時使用一點# type: ignore
最適合您。
我找到了一個有效的解決方案。 這不是最優的,因為 Mixin 類需要繼承自RestManager
。 但是 mypy 可以成功推導出預期的返回類型。
該代碼需要 Pyhton 3.10。 在 3.11 中,您可以直接從typing
中導入assert_type
。 對於舊版本,您需要使用typing.Type[T]
而不是type[t]
。
from typing import ClassVar, Generic, TypeVar
from typing_extensions import assert_type
T = TypeVar("T")
class Item:
"""Data class that represents objects of the "items" endpoint"""
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 = ItemsManager(self)
# self.cats = CatManager(self)
def http_get(self, path: str) -> "Response":
... # Request the proper url and return a response object
class RestManager(Generic[T]):
"""Base class for REST managers."""
_path: ClassVar[str]
_obj_cls: type[T]
def __init__(self, client: ApiClient):
self.client = client
@property
def path(self) -> str:
return self._path
class GetMixin(RestManager, Generic[T]):
"""Mixin for getting a single object by ID"""
def get(self, iid: int) -> T:
response = self.client.http_get(f"{self.path}/{iid}")
return self._obj_cls(**response.json())
class ItemsManager(GetMixin[Item], RestManager[Item]):
"""Concrete manager for "Item" objects."""
_path = "/items"
_obj_cls = Item
def main() -> None:
client = ApiClient("api")
item = client.items.get(42)
assert_type(item, Item)
assert isinstance(item, Item)
if __name__ == "__main__":
main()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.