英文:
Inherit/subclass from `tuple` with correct indexing and slicing
问题
Here's the translated code without the code parts:
我正在尝试继承 tuple
并让 mypy
满意。我想让切片和索引工作。
这是我尝试过的内容:
from functools import singledispatchmethod
from typing import Iterable
class MyIntTuple(tuple[int, ...]):
def __new__(cls, iterable: Iterable[int]) -> "MyIntTuple":
return super().__new__(cls, iterable) # type: ignore
@singledispatchmethod
def __getitem__(self, value: slice, /) -> "MyIntTuple":
return MyIntTuple(super().__getitem__(value))
@__getitem__.register
def __getitem__(self, value: int, /) -> int:
return super().__getitem__(value)
这几乎可以工作,除了切片:
a = MyIntTuple(range(12))
print(a) # (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
print(a[3]) # 3
print(a[3:6]) # (3, 4, 5)
b = a[5:8]
print(type(a)) # <class '__main__.MyIntTuple'>
print(type(b)) # <class 'tuple'>
问题在于切片应该返回 MyIntTuple
的实例。我似乎弄错了。
另外,mypy
对此不满意:
error: Name "__getitem__" already defined on line 488 [no-redef]
如何正确重载这些?functools.singledispatchmethod
是错误的方法吗?还是我使用它不正确?
英文:
i am trying to sublass tuple
and make mypy
happy in the process. i would like to make slicing and indexing work.
here is what i have tried:
from functools import singledispatchmethod
from typing import Iterable
class MyIntTuple(tuple[int, ...]):
def __new__(cls, iterable: Iterable[int]) -> "MyIntTuple":
return super().__new__(cls, iterable) # type: ignore
@singledispatchmethod
def __getitem__(self, value: slice, /) -> "MyIntTuple":
return MyIntTuple(super().__getitem__(value))
@__getitem__.register
def __getitem__(self, value: int, /) -> int:
return super().__getitem__(value)
this almost works except for slicing:
a = MyIntTuple(range(12))
print(a) # (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
print(a[3]) # 3
print(a[3:6]) # (3, 4, 5)
b = a[5:8]
print(type(a)) # <class '__main__.MyIntTuple'>
print(type(b)) # <class 'tuple'>
the problem here is that slicing should return an instance of MyIntTuple
. i seem to have gotten that wrong.
also mypy
is not happy about this:
error: Name "__getitem__" already defined on line 488 [no-redef]
the subs in tuple
are
Superclass:
@overload
def __getitem__(self, SupportsIndex, /) -> int
@overload
def __getitem__(self, slice, /) -> Tuple[int, ...]
how do i correctly overload those? is functools.singledispatchmethod
the wrong way to go? or am i using it incorrectly?
答案1
得分: 2
在函数签名中的mypy
中的"overload"按预期呈现给您的不是一些抽象的东西,而是指向typing
中的装饰器的引用。
以下是您可以执行的操作以匹配超类型定义:
from typing import Iterable, SupportsIndex, overload
from typing_extensions import reveal_type
class MyIntTuple(tuple[int, ...]):
# 您的主要问题在于这里:'int'比'SupportsIndex'要严格得多
@overload
def __getitem__(self, value: SupportsIndex, /) -> int: ...
@overload
def __getitem__(self, value: slice, /) -> "MyIntTuple": ...
def __getitem__(self, value: SupportsIndex | slice, /) -> "MyIntTuple | int":
if isinstance(value, slice):
return MyIntTuple(super().__getitem__(value))
return super().__getitem__(value)
t = MyIntTuple((1,2,3))
reveal_type(t) # N: Revealed type is "__main__.MyIntTuple"
reveal_type(t[0]) # N: Revealed type is "builtins.int"
reveal_type(t[1:]) # N: Revealed type is "__main__.MyIntTuple"
# 这是为什么要使用'SupportsIndex'的原因
class MySomething:
def __index__(self) -> int:
return 1
reveal_type(t[MySomething()]) # N: Revealed type is "builtins.int"
上面的代码显示了使用int
存在问题:这种限制应该被放宽,因为较窄的参数类型违反了LSP(Liskov Substitution Principle)。实际的tuple
可以使用slice
或具有定义了__index__
dunder方法的任何东西,而不仅仅是在__getitem__
中使用int
。
有了overload
和SupportsIndex
,您几乎已经准备好了。唯一需要的更改是交换重载的顺序:mypy
强制执行相同的重载顺序,而Sequence
将SupportsIndex
的签名放在第一位(尝试交换它们并观察错误)。在这种情况下,这并不是关键,但通常情况下,不同的重载顺序可能会更改类型解析。重载按照定义顺序尝试,最上面的胜出。如果签名重叠,通常会看到额外的mypy
错误 - 但最好还是保持重载的顺序。以下是逆序会生成什么的示例:
main.py:6: error: Signature of "__getitem__" incompatible with supertype "tuple" [override]
main.py:6: note: Overload variants must be defined in the same order as they are in "tuple"
上面的代码在使用--strict
选项进行typechecks时没有问题。
英文:
overload
in the signature mypy
presents to you as expected is not some abstract thing, but a reference to decorator from typing
.
Here's what you can do to match supertype definition:
from typing import Iterable, SupportsIndex, overload
from typing_extensions import reveal_type
class MyIntTuple(tuple[int, ...]):
# Your main problem was here: `int` is much stricter than `SupportsIndex`
@overload
def __getitem__(self, value: SupportsIndex, /) -> int: ...
@overload
def __getitem__(self, value: slice, /) -> "MyIntTuple": ...
def __getitem__(self, value: SupportsIndex | slice, /) -> "MyIntTuple | int":
if isinstance(value, slice):
return MyIntTuple(super().__getitem__(value))
return super().__getitem__(value)
t = MyIntTuple((1,2,3))
reveal_type(t) # N: Revealed type is "__main__.MyIntTuple"
reveal_type(t[0]) # N: Revealed type is "builtins.int"
reveal_type(t[1:]) # N: Revealed type is "__main__.MyIntTuple"
# And here's why "SupportsIndex"
class MySomething:
def __index__(self) -> int:
return 1
reveal_type(t[MySomething()]) # N: Revealed type is "builtins.int"
The snippet above shows a problem with using int
: this restriction should be weakened, because narrower argument types are violating LSP. Actual tuple
can use a slice
or anything with __index__
dunder method defined, not just int
in __getitem__
.
With overload
and SupportsIndex
in place, you are almost ready. The only change you need is to swap overloads order: same overloads order is enforced by mypy
, and Sequence
puts SupportsIndex
signature first (try to swap them back and watch the error). In this case it is not critical, but in general different overloads order may change the type resolution. Overloads are tried in the definition order, the topmost wins. If signatures overlap, usually you'll see an extra mypy
error - but better keep overloads in order anyway. Here's what inverse order would generate:
main.py:6: error: Signature of "__getitem__" incompatible with supertype "tuple" [override]
main.py:6: note: Overload variants must be defined in the same order as they are in "tuple"
The code above typechecks with --strict
.
答案2
得分: 1
你需要给各个具体实现函数起不同的名称,与 "main" 函数区分开:
@__getitem__.register
def _(self, value: int, /) -> int:
return super().__getitem__(value)
另外,切片的实现应该与 "main" 函数分开("main" 函数用作未识别参数类型的后备),而整数实现应该接受任何支持 __index__
的类型。
因为 MyIntTuple
还没有定义,你需要在切片实现中显式传递一个类型给 register
,这样它不必查看注释。此外,你可以移除你的 __new__
实现 - 继承的实现将正常工作:
from functools import singledispatchmethod
from typing import SupportsIndex
class MyIntTuple(tuple[int, ...]):
@singledispatchmethod
def __getitem__(self, arg, /):
raise TypeError("无法使用类型为 {!r} 的参数索引 MyIntTuple".format(type(arg)))
@__getitem__.register(slice)
def _(self, value: slice, /) -> "MyIntTuple":
return MyIntTuple(super().__getitem__(value))
@__getitem__.register
def _(self, value: SupportsIndex, /) -> int:
return super().__getitem__(value)
英文:
You need to give the individual implementations a different name from the "main" function:
@__getitem__.register
def _(self, value: int, /) -> int:
return super().__getitem__(value)
Also, the implementation for slices should be separate from the "main" function (which is used as the fallback for unrecognized argument types), and the int implementation should really take anything that supports __index__
.
Since MyIntTuple
isn't defined yet, you'll have to pass a type to register
explicitly for the slice implementation, so it doesn't have to look at the annotations. Also, you can remove your __new__
implementation - the inherited implementation will work fine:
from functools import singledispatchmethod
from typing import SupportsIndex
class MyIntTuple(tuple[int, ...]):
@singledispatchmethod
def __getitem__(self, arg, /):
raise TypeError("Can't index MyIntTuple with argument of type {!r}".format(type(arg)))
@__getitem__.register(slice)
def _(self, value: slice, /) -> "MyIntTuple":
return MyIntTuple(super().__getitem__(value))
@__getitem__.register
def _(self, value: SupportsIndex, /) -> int:
return super().__getitem__(value)
答案3
得分: 0
有问题与 singledispatchmethod
相关。
来自 __future__ 的导入
从 functools 导入 singledispatchmethod
从 typing 导入 Iterable, Sequence
类 MyIntTuple 继承自 tuple[int, ...]
定义 __new__ 方法,接受 Iterable[int] 参数,返回 MyIntTuple
调用父类的 __new__ 方法创建实例
定义 singledispatchmethod 装饰的 __getitem__ 方法,返回 None
抛出 NotImplementedError 异常
定义 __getitem__ 方法的特例,接受 slice 参数,返回 MyIntTuple
调用父类的 __getitem__ 方法处理参数并返回 MyIntTuple
定义 __getitem__ 方法的特例,接受 int 参数,返回 int
调用父类的 __getitem__ 方法处理参数并返回 int
a 等于 MyIntTuple 的一个实例,传入 range(12)
打印 a # (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
打印 a[3] # 3
打印 a[3:6] # (3, 4, 5)
b 等于 a[5:8]
打印 a 的类型 # <class '__main__.MyIntTuple'>
打印 b 的类型 # <class '__main__.MyIntTuple'>
英文:
There is a problem with singledispatchmethod
.
from __future__ import annotations
from functools import singledispatchmethod
from typing import Iterable, Sequence
class MyIntTuple(tuple[int, ...]):
def __new__(cls, iterable: Iterable[int]) -> MyIntTuple:
return super().__new__(cls, iterable) # type: ignore
@singledispatchmethod
def __getitem__(self) -> None:
raise NotImplementedError
@__getitem__.register
def _(self, value: slice, /) -> MyIntTuple:
return MyIntTuple(super().__getitem__(value))
@__getitem__.register
def _(self, value: int, /) -> int:
return super().__getitem__(value)
a = MyIntTuple(range(12))
print(a) # (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
print(a[3]) # 3
print(a[3:6]) # (3, 4, 5)
b = a[5:8]
print(type(a)) # <class '__main__.MyIntTuple'>
print(type(b)) # <class '__main__.MyIntTuple'>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论