![](/img/trans.png)
[英]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.