泛型 类 上的类方法
Classmethods on generic classes
我尝试在通用 class 上调用 class 方法:
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]
但是如果我打电话
Genders.all()
我收到错误 TypeError: 'TypeVar' object is not iterable
。所以 TypeVar T
与 Enum Gender
.
没有正确匹配
我该如何解决这个问题?预期的行为是
>>> Genders.all()
'123'
有什么想法吗?或者这是不可能的?
Python 的类型提示系统用于静态类型检查器来验证您的代码,而 T
只是类型系统的 占位符 ,就像模板语言中的槽。它不能用作对特定类型的间接引用。
如果您想生成具体的实现,您需要 subclass 您的通用类型。因为 Gender
是 class 而不是实例,所以您需要告诉类型系统您打算如何在某处使用 Type[T]
,也是。
因为您还希望能够将 T
用作 Enum()
(用 EnumSubclass(int(character))
调用它),我还会 绑定 typevar;这样类型检查器就会明白 Type[T]
的所有具体形式都是可调用的,并且会产生单独的 T
实例,而且那些 T
实例将 always 有一个 .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])
使用上面的通用 class,您现在可以创建一个 具体 实现,使用您的 Gender
IntEnum
安装到 T
插槽并作为 class 属性:
class Gender(IntEnum):
MALE = 1
FEMALE = 2
DIVERS = 3
class Genders(EnumAggregate[Gender]):
enum = Gender
为了能够访问 IntEnum
subclass 作为 class 属性,我们需要使用 typing.ClassVar[]
;否则类型检查器必须假定该属性仅在实例上可用。
并且因为 Gender
IntEnum
subclass 本身就是一个 class,我们需要告诉类型检查器关于这一点,因此使用 typing.Type[]
.
现在 Gender
具体子 class 工作;使用 EnumAggregate[Gender]
作为基础 class 告诉类型检查器用 T
替换所有地方的 Gender
,并且因为实现使用 enum = Gender
,类型检查器看到这确实得到了正确的满足并且代码通过了所有检查:
$ bin/mypy so65064844.py
Success: no issues found in 1 source file
你可以调用 Genders.all()
来生成一个字符串:
>>> Genders.all()
'123'
请注意,我不会将枚举值存储为字符串,而是存储为整数。在这里来回转换它没有什么价值,并且您将自己限制在值介于 0 和 9(个位数)之间的枚举。
另一个答案不再有效,至少在 Python 3.10 中是这样。类型注释 ClassVar[Type[T]]
导致 mypy 错误: ClassVar cannot contain type variables
被抛出。这是因为 ClassVar
只能用于 Protocol
和结构子类型,这不是手头问题的最佳答案。
其他答案的以下修改有效:
class EnumAggregate(Generic[T]):
enum: type[T]
[...]
class Genders(EnumAggregate[Gender]):
enum = Gender
抽象class变量
我还建议以某种方式使 enum
抽象化,因此实例化 EnumAggregate[Gender]
而不是 Genders
将在实例化时引发错误,而不仅仅是在调用 to_list()
或 all()
.
这可以通过两种方式完成:检查 __init__
中的实现:
class EnumAggregate(Generic[T]):
enum: type[T]
def __init__
[...]
if not hasattr(type(self), 'enum'):
raise NotImplementedError("Implementations must define the class variable 'enum'")
或使用摘要 class 属性,参见 this discussion. This makes mypy happy in several situations, but not Pylance (see here):
class EnumAggregate(Generic[T]):
@property
@classmethod
@abstractmethod
def enum(cls) -> type[T]: ...
[...]
class Genders(EnumAggregate[Gender]):
enum = Gender
但是,mypy 和装饰器存在未解决的问题,因此目前存在虚假错误,这些错误将来可能会消失。供参考:
Discussion whether to deprecate chaining classmethod decorators
我尝试在通用 class 上调用 class 方法:
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]
但是如果我打电话
Genders.all()
我收到错误 TypeError: 'TypeVar' object is not iterable
。所以 TypeVar T
与 Enum Gender
.
我该如何解决这个问题?预期的行为是
>>> Genders.all()
'123'
有什么想法吗?或者这是不可能的?
Python 的类型提示系统用于静态类型检查器来验证您的代码,而 T
只是类型系统的 占位符 ,就像模板语言中的槽。它不能用作对特定类型的间接引用。
如果您想生成具体的实现,您需要 subclass 您的通用类型。因为 Gender
是 class 而不是实例,所以您需要告诉类型系统您打算如何在某处使用 Type[T]
,也是。
因为您还希望能够将 T
用作 Enum()
(用 EnumSubclass(int(character))
调用它),我还会 绑定 typevar;这样类型检查器就会明白 Type[T]
的所有具体形式都是可调用的,并且会产生单独的 T
实例,而且那些 T
实例将 always 有一个 .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])
使用上面的通用 class,您现在可以创建一个 具体 实现,使用您的 Gender
IntEnum
安装到 T
插槽并作为 class 属性:
class Gender(IntEnum):
MALE = 1
FEMALE = 2
DIVERS = 3
class Genders(EnumAggregate[Gender]):
enum = Gender
为了能够访问 IntEnum
subclass 作为 class 属性,我们需要使用 typing.ClassVar[]
;否则类型检查器必须假定该属性仅在实例上可用。
并且因为 Gender
IntEnum
subclass 本身就是一个 class,我们需要告诉类型检查器关于这一点,因此使用 typing.Type[]
.
现在 Gender
具体子 class 工作;使用 EnumAggregate[Gender]
作为基础 class 告诉类型检查器用 T
替换所有地方的 Gender
,并且因为实现使用 enum = Gender
,类型检查器看到这确实得到了正确的满足并且代码通过了所有检查:
$ bin/mypy so65064844.py
Success: no issues found in 1 source file
你可以调用 Genders.all()
来生成一个字符串:
>>> Genders.all()
'123'
请注意,我不会将枚举值存储为字符串,而是存储为整数。在这里来回转换它没有什么价值,并且您将自己限制在值介于 0 和 9(个位数)之间的枚举。
另一个答案不再有效,至少在 Python 3.10 中是这样。类型注释 ClassVar[Type[T]]
导致 mypy 错误: ClassVar cannot contain type variables
被抛出。这是因为 ClassVar
只能用于 Protocol
和结构子类型,这不是手头问题的最佳答案。
其他答案的以下修改有效:
class EnumAggregate(Generic[T]):
enum: type[T]
[...]
class Genders(EnumAggregate[Gender]):
enum = Gender
抽象class变量
我还建议以某种方式使 enum
抽象化,因此实例化 EnumAggregate[Gender]
而不是 Genders
将在实例化时引发错误,而不仅仅是在调用 to_list()
或 all()
.
这可以通过两种方式完成:检查 __init__
中的实现:
class EnumAggregate(Generic[T]):
enum: type[T]
def __init__
[...]
if not hasattr(type(self), 'enum'):
raise NotImplementedError("Implementations must define the class variable 'enum'")
或使用摘要 class 属性,参见 this discussion. This makes mypy happy in several situations, but not Pylance (see here):
class EnumAggregate(Generic[T]):
@property
@classmethod
@abstractmethod
def enum(cls) -> type[T]: ...
[...]
class Genders(EnumAggregate[Gender]):
enum = Gender
但是,mypy 和装饰器存在未解决的问题,因此目前存在虚假错误,这些错误将来可能会消失。供参考:
Discussion whether to deprecate chaining classmethod decorators