繁体   English   中英

基于 Python 中的 class 变量的 class 方法中的返回类型

[英]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 的任何特定子类RelatedName显式设置_INDIVIDUAL 这在__init_subclass__的子类化过程中会自动处理。 (如果你对细节感兴趣,我会在这篇文章中解释它们。)
  • 不鼓励直接访问_INDIVIDUAL属性。 取而代之的是get_individual getter。 如果额外的括号惹恼了您,我想您可以使用描述符来为_INDIVIDUAL构建类似属性的情况。 (注意:您仍然可以只使用cls._INDIVIDUALself._INDIVIDUAL ,只是可能会出现None类型的问题。)
  • 基础 class 这种方式显然要复杂一些,但另一方面,在我看来,特定子类的创建要好得多。

希望这可以帮助。

你能用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM