简体   繁体   English

Python 类型提示可以处理通用 class 在方法中更改自己的类型吗?

[英]Can Python type hints handle a generic class changing its own type in a method?

I have an existing class that changes an important type upon a certain method call.我有一个现有的 class 可以在某个方法调用时更改重要类型。 It looks something like this:它看起来像这样:

class RememberLast:
    def __init__(self, last):
        self._last = last

    def set(self, new_last):
        self._last = new_last

    def get(self):
        return self._last

remember = RememberLast(5)
type(remember.get())  # int
remember.set('wow')
type(remember.get())  # str
remember.set(4.5)
type(remember.get())  # float

Ideally the type of remember would change from RememberLast[int] to RememberLast[str] and then to RememberLast[float] .理想情况下, remember的类型将从RememberLast[int]更改为RememberLast[str]然后更改为RememberLast[float] Is there a way to represent this situation with type hints?有没有办法用类型提示来表示这种情况?

Returning self with a different type hint in set() isn't ideal because there are existing callers.set()中以不同的类型提示返回self并不理想,因为存在调用者。 For these existing callers that don't use the return value, the type would stay as RememberLast[int] even though the type was "destroyed" and isn't correct anymore.对于这些不使用返回值的现有调用者,类型将保持为RememberLast[int]即使类型已“销毁”并且不再正确。

The existing class I'm referring to is twisted.internet.defer.Deferred , which allows chaining callbacks.我指的现有 class 是twisted.internet.defer.Deferred ,它允许链接回调。 The type of the last return value becomes the parameter for the next callback.最后一个返回值的类型成为下一个回调的参数。 So the type of a Deferred can be thought of as the type of the last callback added to it.因此,可以将Deferred的类型视为添加到它的最后一个回调的类型。

I'm going to assume that RememberLast must be able to handle things besides int s, string s, and float s (or more than a finite union of types), because otherwise you could just use Union[int, str, float] .我将假设RememberLast必须能够处理除int s、 string s 和float s 之外的东西(或超过有限的类型联合),因为否则你可以只使用Union[int, str, float]

I'm unfortunately pessimistic that you can use an annotation more specific than Any for two reasons:不幸的是,我对您可以使用比Any更具体的注释感到悲观,原因有两个:

  1. mypy's own's documentation suggests using Any for dynamically typed code. mypy 自己的文档建议将Any用于动态类型代码。 If it was possible to use something more specific, they would have said so.如果可以使用更具体的东西,他们会这么说的。
  2. This StackOverflow question asks something similar, and the accepted answer is basically "refactor so you can explicitly define the expected types, or use Any ".这个 StackOverflow 问题提出了类似的问题,并且接受的答案基本上是“重构,以便您可以显式定义预期的类型,或使用Any ”。

You can use a generic class to track the changing type.您可以使用通用 class 来跟踪变化的类型。

Define two generics.定义两个 generics。

  • T = The current type T = 当前类型
  • R = The type after we call set R = 我们调用 set 后的类型
from typing import TypeVar, Generic, Type

T = TypeVar("T")
R = TypeVar("R")


class RememberLast(Generic[T]):
    def __init__(self, last: T):
        self._last = last

    def set(self, new_last: R) -> "RememberLast[R]":
        return RememberLast(new_last)

    def get(self) -> T:
        return self._last

Note in order for this to work you will need to reassign the results of set back to remember .请注意,为了使其工作,您需要重新分配set back to remember的结果。 We need to do this because the results of set hold the new type hinting information.我们需要这样做,因为set的结果包含新的类型提示信息。

# int
remember = RememberLast(5)  # RememberLast[int]
print(type(remember.get()))  # <class 'int'>

# str
remember = remember.set("abc")  # RememberLast[str]
print(type(remember.get()))  # <class 'str'>

# float
remember = remember.set(4.5)  # RememberLast[float]
print(type(remember.get()))  # <class 'float'>

Unfortunately, type hints in python doesn't prevent the type from changing for example in your snippet if you used the type hints as bellow it will give the same output:不幸的是,python 中的类型提示不会阻止类型更改,例如,如果您使用下面的类型提示,它将给出相同的 output:

class RememberLast:
    def __init__(self, last: int):
        self._last = last

    def set(self, new_last: int):
        self._last = new_last

    def get(self):
        return self._last

remember = RememberLast(5)
type(remember.get())  # int
remember.set('wow')
type(remember.get())  # str
remember.set(4.5)
type(remember.get())  # float

There are two ways to force the methods to take only specific type:有两种方法可以强制方法仅采用特定类型:

  1. Assert with isinstance :isinstance断言:
class RememberLast:
    def __init__(self, last: int):
        assert isinstance(last, int)
        self._last = last

    def set(self, new_last: int):
        assert isinstance(last, int)
        self._last = new_last

    def get(self):
        assert isinstance(last, int)
        return self._last

remember = RememberLast(5)
type(remember.get())  # int
remember.set('wow')
type(remember.get())  # raises AssertionError
remember.set(4.5)
type(remember.get())  # raises AssertionError
  1. use mypy with type hinting before you deploy your code to make sure that types are matching correctly在部署代码之前使用带有类型提示的mypy以确保类型正确匹配
mypy my_python_script.py

this should be the output这应该是 output

my_python_script.py:26: error: Argument 1 to "set" of "RememberLast" has incompatible type "str"; expected "int"
my_python_script.py:28: error: Argument 1 to "set" of "RememberLast" has incompatible type "float"; expected "int"
Found 2 errors in 1 file (checked 1 source file)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM