[英]Python typing, mypy infer return type based on class method return type
[英]Return type in a class method based on a class variable in Python
我正在使用一個代碼庫,其中有幾個類,總是一個數據類和另一個執行 class。 數據類用作數據收集器(顧名思義)。
為了將數據類“連接”到另一個 class,我在另一個 class 中設置了一個 class 變量,以明確相關的數據類是什么。 這很好用——我可以隨意使用這個 class 變量來實例化數據 class。 但是,我不清楚如何使用它來為給定方法指定它將返回鏈接數據 class 的實例。
以這個例子(可執行)為例:
from abc import ABC
from dataclasses import dataclass
from typing import ClassVar
@dataclass
class Name(ABC):
name: str
class RelatedName(ABC):
_INDIVIDAL: ClassVar[Name]
def return_name(self, **properties) -> Name:
# There is a typing issue here too but you can ignore that for now
return self._INDIVIDAL(**properties)
@dataclass
class BiggerName(Name):
other_name: str
class RelatedBiggerName(RelatedName):
_INDIVIDAL: ClassVar[Name] = BiggerName
if __name__ == "__main__":
biggie = RelatedBiggerName()
biggiename = biggie.return_name(name="Alfred", other_name="Biggie").other_name
print(biggiename)
該腳本工作正常,但存在打字問題。 在最后一行中,您將看到Name
class 的屬性other_name
未定義的問題。 這是意料之中的,但我不確定如何更改 output 類型的return_name
,以便它將使用 _INDIVIDUAL 中定義的_INDIVIDUAL
。
我試過def return_name(self, **properties) -> _INDIVIDAL
但這自然會導致name '_INDIVIDAL' is not defined
。
也許我所追求的不可能。 是否有可能在取決於 class 變量的 class 中輸入? 我對 Python 3.8 及更高版本感興趣。
我同意@cherrywoods 的觀點,即自定義通用基礎 class 似乎是 go 的方式。
我想添加我自己的變體,應該做你想要的:
from abc import ABC
from dataclasses import dataclass
from typing import Any, Generic, Optional, Type, TypeVar, get_args, get_origin
T = TypeVar("T", bound="Name")
@dataclass
class Name(ABC):
name: str
class RelatedName(ABC, Generic[T]):
_INDIVIDUAL: Optional[Type[T]] = None
@classmethod
def __init_subclass__(cls, **kwargs: Any) -> None:
"""Identifies and saves the type argument"""
super().__init_subclass__(**kwargs)
for base in cls.__orig_bases__: # type: ignore[attr-defined]
origin = get_origin(base)
if origin is None or not issubclass(origin, RelatedName):
continue
type_arg = get_args(base)[0]
# Do not set the attribute for GENERIC subclasses!
if not isinstance(type_arg, TypeVar):
cls._INDIVIDUAL = type_arg
return
@classmethod
def get_individual(cls) -> Type[T]:
"""Getter ensuring that we are not dealing with a generic subclass"""
if cls._INDIVIDUAL is None:
raise AttributeError(
f"{cls.__name__} is generic; type argument unspecified"
)
return cls._INDIVIDUAL
def __setattr__(self, name: str, value: Any) -> None:
"""Prevent instances from overwriting `_INDIVIDUAL`"""
if name == "_INDIVIDUAL":
raise AttributeError("Instances cannot modify `_INDIVIDUAL`")
super().__setattr__(name, value)
def return_name(self, **properties: Any) -> T:
return self.get_individual()(**properties)
@dataclass
class BiggerName(Name):
other_name: str
class RelatedBiggerName(RelatedName[BiggerName]):
pass
if __name__ == "__main__":
biggie = RelatedBiggerName()
biggiename = biggie.return_name(name="Alfred", other_name="Biggie").other_name
print(biggiename)
工作沒有問題或來自mypy --strict
的投訴。
_INDIVIDUAL
屬性不再標記為ClassVar
,因為( 沒有充分理由)不允許類型變量。__setattr__
方法的簡單自定義。RelatedName
顯式設置_INDIVIDUAL
。 這在__init_subclass__
的子類化過程中會自動處理。 (如果你對細節感興趣,我會在這篇文章中解釋它們。)_INDIVIDUAL
屬性。 取而代之的是get_individual
getter。 如果額外的括號惹惱了您,我想您可以使用描述符來為_INDIVIDUAL
構建類似屬性的情況。 (注意:您仍然可以只使用cls._INDIVIDUAL
或self._INDIVIDUAL
,只是可能會出現None
類型的問題。)希望這可以幫助。
你能用generics嗎?
from abc import ABC
from dataclasses import dataclass
from typing import ClassVar, TypeVar, Generic, Type
T = TypeVar("T", bound="Name")
@dataclass
class Name(ABC):
name: str
class RelatedName(ABC, Generic[T]):
# This would resolve what juanpa.arrivillaga pointed out, but mypy says:
# ClassVar cannot contain type variables, so I guess your use-case is unsupported
# _INDIVIDAL: ClassVar[Type[T]]
# One option:
# _INDIVIDAL: ClassVar
# Second option to demonstrate Type[T]
_INDIVIDAL: Type[T]
def return_name(self, **properties) -> T:
return self._INDIVIDAL(**properties)
@dataclass
class BiggerName(Name):
other_name: str
class RelatedBiggerName(RelatedName[BiggerName]):
# see above
_INDIVIDAL: Type[BiggerName] = BiggerName
if __name__ == "__main__":
biggie = RelatedBiggerName()
biggiename = biggie.return_name(name="Alfred", other_name="Biggie").other_name
print(biggiename)
mypy 對此沒有報告任何錯誤,我認為從概念上講這就是您想要的。 我在 python 3.10 上進行了測試。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.