在 Python 类型中指定 TypeVar 的附加子类边界
Specifying additional subclassed bounds of a TypeVar in Python typing
当结合使用类型提示和 mypy 时,我想定义一个泛型 class,它允许访问仅对某些泛型 classes 可用的属性。我想知道如何在 Python 类型提示中定义它。
例如,让我们举个例子:
class SomeObject:
pass
class SomeObjectWithAttr:
def __init__(self, attr):
self.attr = attr
class AppendListWithAttrs:
def __init__(self):
self._list = []
self._attrs = []
def append(self, obj, record_attr=False):
self._list.append(obj)
if record_attr:
self._attrs.append(obj.attr)
l = AppendListWithAttrs()
l.append(SomeObject())
l.append(SomeObjectWithAttr())
l.append(SomeObjectWithAttr(), record_attr=True)
我已经尝试对此进行注释,但 mypy 似乎不太高兴。这是我最好的尝试:
from typing import Generic, TypeVar, Protocol, overload, Literal, Union
T = TypeVar("T")
class HasAttr(Protocol):
attr: str
class AppendListWithAttrs(Generic[T]):
def __init__(self):
self._list: list[T] = []
self._attrs: list[str] = []
@overload
def append(self: 'AppendListWithAttrs[HasAttr]', obj: HasAttr, record_attr: Literal[True]): ...
@overload
def append(self, obj: T, record_attr: Literal[False]): ...
def append(self, obj: T, record_attr: bool = False):
self._list.append(obj)
if record_attr:
self._attrs.append(obj.attr)
这也不完全正确,但我正在有效地寻找一种方法来告诉 mypy T
可以是任何东西,但如果它受 HasAttr
限制,它可能会访问这个attr
。也许像 subclassing T
with HasAttr
这样的东西,但也许我看错了。
你会如何注释?
这个怎么样?它passes MyPy.
from typing import overload, Literal, Union, Any, Protocol, Generic, TypeVar
class GenericObjectProto(Protocol):
pass
class ObjectWithAttrProto(GenericObjectProto, Protocol):
attr: str
T = TypeVar('T', bound=GenericObjectProto)
class AppendListWithAttrs(Generic[T]):
def __init__(self) -> None:
self._list: list[T] = []
self._attrs: list[str] = []
@overload
def append(self, obj: GenericObjectProto, record_attr: Literal[False] = False) -> None: ...
@overload
def append(self, obj: ObjectWithAttrProto, record_attr: Literal[True] = ...) -> None: ...
def append(self, obj, record_attr: bool = False) -> None:
self._list.append(obj)
if record_attr:
self._attrs.append(obj.attr)
class SomeObject:
pass
class SomeObjectWithAttr:
def __init__(self, attr: str) -> None:
self.attr = attr
mylist = AppendListWithAttrs[SomeObject]()
mylist.append(SomeObject())
mylist.append(SomeObjectWithAttr('hello'))
mylist.append(SomeObjectWithAttr('hello'), record_attr=True)
reveal_type(mylist._list) # Revealed type is "builtins.list[__main__.SomeObject*]"
工作原理
通过让 ObjectWithAttr
协议继承自 GenericObject
协议,我们可以获得一些特殊的类型提示魔法。通过这样做,我们将 ObjectWithAttr
定义为 GenericObject
的 结构 子类型。这意味着当我们将 SomeObjectWithAttr
的实例附加到 SomeObject
实例的列表时,MyPy 不会抱怨。由于我们定义的“结构继承”方案,MyPy 理解 SomeObjectWithAttr
与 SomeObject
实例列表完全兼容,即使没有 actual 继承正在进行中。
注意事项
这通过了 MyPy,但前提是您不将它与 --strict
设置一起使用,因为我没有在 .append
的具体实现中注释 obj
。 MyPy 似乎能够使用现有注释完美地推断出类型,但如果您在 --strict
设置中按原样 运行 上面的代码,它仍然会抱怨您遗漏了注释。即使有重载,它也不喜欢你用 T
或 Union
类型注释 obj
(这对我来说似乎有点错误),所以如果你是 运行ning MyPy on --strict
,你可以在具体实现中用Any
注释obj
,或者在行尾放一个# type: ignore[no-untyped-def]
。
当结合使用类型提示和 mypy 时,我想定义一个泛型 class,它允许访问仅对某些泛型 classes 可用的属性。我想知道如何在 Python 类型提示中定义它。
例如,让我们举个例子:
class SomeObject:
pass
class SomeObjectWithAttr:
def __init__(self, attr):
self.attr = attr
class AppendListWithAttrs:
def __init__(self):
self._list = []
self._attrs = []
def append(self, obj, record_attr=False):
self._list.append(obj)
if record_attr:
self._attrs.append(obj.attr)
l = AppendListWithAttrs()
l.append(SomeObject())
l.append(SomeObjectWithAttr())
l.append(SomeObjectWithAttr(), record_attr=True)
我已经尝试对此进行注释,但 mypy 似乎不太高兴。这是我最好的尝试:
from typing import Generic, TypeVar, Protocol, overload, Literal, Union
T = TypeVar("T")
class HasAttr(Protocol):
attr: str
class AppendListWithAttrs(Generic[T]):
def __init__(self):
self._list: list[T] = []
self._attrs: list[str] = []
@overload
def append(self: 'AppendListWithAttrs[HasAttr]', obj: HasAttr, record_attr: Literal[True]): ...
@overload
def append(self, obj: T, record_attr: Literal[False]): ...
def append(self, obj: T, record_attr: bool = False):
self._list.append(obj)
if record_attr:
self._attrs.append(obj.attr)
这也不完全正确,但我正在有效地寻找一种方法来告诉 mypy T
可以是任何东西,但如果它受 HasAttr
限制,它可能会访问这个attr
。也许像 subclassing T
with HasAttr
这样的东西,但也许我看错了。
你会如何注释?
这个怎么样?它passes MyPy.
from typing import overload, Literal, Union, Any, Protocol, Generic, TypeVar
class GenericObjectProto(Protocol):
pass
class ObjectWithAttrProto(GenericObjectProto, Protocol):
attr: str
T = TypeVar('T', bound=GenericObjectProto)
class AppendListWithAttrs(Generic[T]):
def __init__(self) -> None:
self._list: list[T] = []
self._attrs: list[str] = []
@overload
def append(self, obj: GenericObjectProto, record_attr: Literal[False] = False) -> None: ...
@overload
def append(self, obj: ObjectWithAttrProto, record_attr: Literal[True] = ...) -> None: ...
def append(self, obj, record_attr: bool = False) -> None:
self._list.append(obj)
if record_attr:
self._attrs.append(obj.attr)
class SomeObject:
pass
class SomeObjectWithAttr:
def __init__(self, attr: str) -> None:
self.attr = attr
mylist = AppendListWithAttrs[SomeObject]()
mylist.append(SomeObject())
mylist.append(SomeObjectWithAttr('hello'))
mylist.append(SomeObjectWithAttr('hello'), record_attr=True)
reveal_type(mylist._list) # Revealed type is "builtins.list[__main__.SomeObject*]"
工作原理
通过让 ObjectWithAttr
协议继承自 GenericObject
协议,我们可以获得一些特殊的类型提示魔法。通过这样做,我们将 ObjectWithAttr
定义为 GenericObject
的 结构 子类型。这意味着当我们将 SomeObjectWithAttr
的实例附加到 SomeObject
实例的列表时,MyPy 不会抱怨。由于我们定义的“结构继承”方案,MyPy 理解 SomeObjectWithAttr
与 SomeObject
实例列表完全兼容,即使没有 actual 继承正在进行中。
注意事项
这通过了 MyPy,但前提是您不将它与 --strict
设置一起使用,因为我没有在 .append
的具体实现中注释 obj
。 MyPy 似乎能够使用现有注释完美地推断出类型,但如果您在 --strict
设置中按原样 运行 上面的代码,它仍然会抱怨您遗漏了注释。即使有重载,它也不喜欢你用 T
或 Union
类型注释 obj
(这对我来说似乎有点错误),所以如果你是 运行ning MyPy on --strict
,你可以在具体实现中用Any
注释obj
,或者在行尾放一个# type: ignore[no-untyped-def]
。