继承/子类化自 `tuple`,具有正确的索引和切片功能。

huangapple go评论65阅读模式
英文:

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]) -&gt; &quot;MyIntTuple&quot;:
        return super().__new__(cls, iterable)  # type: ignore

    @singledispatchmethod
    def __getitem__(self, value: slice, /) -&gt; &quot;MyIntTuple&quot;:
        return MyIntTuple(super().__getitem__(value))

    @__getitem__.register
    def __getitem__(self, value: int, /) -&gt; 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))  # &lt;class &#39;__main__.MyIntTuple&#39;&gt;
print(type(b))  # &lt;class &#39;tuple&#39;&gt;

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 &quot;__getitem__&quot; already defined on line 488  [no-redef]

the subs in tuple are

Superclass:
    @overload
    def __getitem__(self, SupportsIndex, /) -&gt; int
    @overload
    def __getitem__(self, slice, /) -&gt; 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

有了overloadSupportsIndex,您几乎已经准备好了。唯一需要的更改是交换重载的顺序:mypy强制执行相同的重载顺序,而SequenceSupportsIndex的签名放在第一位(尝试交换它们并观察错误)。在这种情况下,这并不是关键,但通常情况下,不同的重载顺序可能会更改类型解析。重载按照定义顺序尝试,最上面的胜出。如果签名重叠,通常会看到额外的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, /) -&gt; int: ...
    @overload
    def __getitem__(self, value: slice, /) -&gt; &quot;MyIntTuple&quot;: ...
    
    def __getitem__(self, value: SupportsIndex | slice, /) -&gt; &quot;MyIntTuple | int&quot;:
        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 &quot;__main__.MyIntTuple&quot;
reveal_type(t[0])  # N: Revealed type is &quot;builtins.int&quot;
reveal_type(t[1:])  # N: Revealed type is &quot;__main__.MyIntTuple&quot;

# And here&#39;s why &quot;SupportsIndex&quot;
class MySomething:
    def __index__(self) -&gt; int:
        return 1

reveal_type(t[MySomething()])  # N: Revealed type is &quot;builtins.int&quot;

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 &quot;__getitem__&quot; incompatible with supertype &quot;tuple&quot;  [override]
main.py:6: note: Overload variants must be defined in the same order as they are in &quot;tuple&quot;

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, /) -&gt; 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(&quot;Can&#39;t index MyIntTuple with argument of type {!r}&quot;.format(type(arg)))

    @__getitem__.register(slice)
    def _(self, value: slice, /) -&gt; &quot;MyIntTuple&quot;:
        return MyIntTuple(super().__getitem__(value))

    @__getitem__.register
    def _(self, value: SupportsIndex, /) -&gt; 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]) -&gt; MyIntTuple:
        return super().__new__(cls, iterable)  # type: ignore

    @singledispatchmethod
    def __getitem__(self) -&gt; None:
        raise NotImplementedError

    @__getitem__.register
    def _(self, value: slice, /) -&gt; MyIntTuple:
        return MyIntTuple(super().__getitem__(value))

    @__getitem__.register
    def _(self, value: int, /) -&gt; 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))  # &lt;class &#39;__main__.MyIntTuple&#39;&gt;
print(type(b))  # &lt;class &#39;__main__.MyIntTuple&#39;&gt;

huangapple
  • 本文由 发表于 2023年5月25日 22:39:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/76333507.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定