泛型类的类方法

Dar*_*ath 3 python generics class-method python-3.x

我尝试在泛型类上调用类方法:

from typing import List, Union, TypeVar, Generic
from enum import IntEnum

class Gender(IntEnum):
    MALE = 1
    FEMALE = 2
    DIVERS = 3


T = TypeVar('T')

class EnumAggregate(Generic[T]):
    def __init__(self, value: Union[int, str, List[T]]) -> None:
        if value == '':
            raise ValueError(f'Parameter "value" cannot be empty!')

        if isinstance(value, list):
            self._value = ''.join([str(x.value) for x in value])
        else:
            self._value = str(value)

    def __contains__(self, item: T) -> bool:
        return item in self.to_list

    @property
    def to_list(self) -> List[T]:
        return [T(int(character)) for character in self._value]

    @property
    def value(self) -> str:
        return self._value

    @classmethod
    def all(cls) -> str:
        return ''.join([str(x.value) for x in T])

Genders = EnumAggregate[Gender]
Run Code Online (Sandbox Code Playgroud)

但如果我打电话

Genders.all()
Run Code Online (Sandbox Code Playgroud)

我得到了错误TypeError: 'TypeVar' object is not iterable。因此 TypeVarT与 Enum 不正确匹配Gender

我怎样才能解决这个问题?预期的行为是

>>> Genders.all()
'123'
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?或者这是不可能的?

Mar*_*ers 5

Python 的类型提示系统用于静态类型检查器来验证代码,并且T只是类型系统的占位符,就像模板语言中的槽一样。它不能用作对特定类型的间接引用。

如果您想生成具体的实现,则需要对泛型类型进行子类化。因为Gender是一个而不是实例,所以您还需要告诉类型系统您计划如何Type[T]在某个地方使用 a 。

因为您还希望能够用作(TEnum()调用它EnumSubclass(int(character))),所以我还绑定了 typevar;这样,类型检查器将理解 的所有具体形式Type[T]都是可调用的,并将生成单独的T实例,而且这些T实例将始终具有一个.value属性:

from typing import ClassVar, List, Union, Type, TypeVar, Generic
from enum import IntEnum

T = TypeVar('T', bound=IntEnum)  # only IntEnum subclasses

class EnumAggregate(Generic[T]):
    # Concrete implementations can reference `enum` *on the class itself*,
    # which will be an IntEnum subclass.
    enum: ClassVar[Type[T]]

    def __init__(self, value: Union[int, str, List[T]]) -> None:
        if not value:
            raise ValueError('Parameter "value" cannot be empty!')

        if isinstance(value, list):
            self._value = ''.join([str(x.value) for x in value])
        else:
            self._value = str(value)

    def __contains__(self, item: T) -> bool:
        return item in self.to_list

    @property
    def to_list(self) -> List[T]:
        # the concrete implementation needs to use self.enum here
        return [self.enum(int(character)) for character in self._value]

    @property
    def value(self) -> str:
        return self._value

    @classmethod
    def all(cls) -> str:
        # the concrete implementation needs to reference cls.enum here
        return ''.join([str(x.value) for x in cls.enum])
Run Code Online (Sandbox Code Playgroud)

通过上面的通用类,您现在可以创建一个具体的实现,使用您Gender IntEnum安装到T插槽中并作为类属性:

class Gender(IntEnum):
    MALE = 1
    FEMALE = 2
    DIVERS = 3


class Genders(EnumAggregate[Gender]):
    enum = Gender
Run Code Online (Sandbox Code Playgroud)

为了能够将IntEnum子类作为类属性访问,我们需要使用typing.ClassVar[]; 否则类型检查器必须假设该属性仅在实例上可用。

因为Gender IntEnum子类本身就是一个,所以我们也需要告诉类型检查器这一点,因此使用typing.Type[].

现在Gender具体子类可以工作了;EnumAggregate[Gender]作为基类的使用告诉类型检查器在任何地方T都替换Gender,并且由于实现使用enum = Gender,类型检查器发现这确实正确满足并且代码通过了所有检查:

class Gender(IntEnum):
    MALE = 1
    FEMALE = 2
    DIVERS = 3


class Genders(EnumAggregate[Gender]):
    enum = Gender
Run Code Online (Sandbox Code Playgroud)

你可以调用Genders.all()来生成一个字符串:

$ bin/mypy so65064844.py
Success: no issues found in 1 source file
Run Code Online (Sandbox Code Playgroud)

请注意,我不会将枚举值存储为字符串,而是存储为整数。在这里来回转换它没有什么价值,并且您将自己限制为值在 0 到 9(个位数)之间的枚举。