簡體   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