【问题标题】:Can Python type hints handle a generic class changing its own type in a method?Python 类型提示可以处理泛型类在方法中更改自己的类型吗?
【发布时间】:2022-01-02 03:36:59
【问题描述】:

我有一个现有的类,它会在某个方法调用时更改一个重要的类型。它看起来像这样:

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

理想情况下,remember 的类型将从RememberLast[int] 更改为RememberLast[str],然后更改为RememberLast[float]。有没有办法用类型提示来表示这种情况?

set() 中返回带有不同类型提示的self 并不理想,因为已经存在调用者。对于这些不使用返回值的现有调用者,类型将保持为 RememberLast[int],即使类型已“销毁”并且不再正确。

我指的现有类是twisted.internet.defer.Deferred,它允许链接回调。最后一个返回值的类型成为下一个回调的参数。所以Deferred的类型可以认为是最后添加到它的回调的类型。

【问题讨论】:

  • 如果你知道所有可能的类型,然后把它们放在一起。比如:def get(self) -> Union[int, str, float]:
  • 类型提示用于 static 类型。如果您在运行时更改类型,则根据定义不是静态类型。

标签: python mypy python-typing


【解决方案1】:

我将假设 RememberLast 必须能够处理除 ints、strings 和 floats 之外的事情(或超过有限的类型联合),因为否则你可以只需使用Union[int, str, float]

不幸的是,我对您可以使用比 Any 更具体的注释感到悲观,原因有两个:

  1. mypy's own's documentation 建议将 Any 用于动态类型代码。如果可以使用更具体的东西,他们会这么说的。
  2. This StackOverflow question 提出了类似的问题,而接受的答案基本上是“重构,以便您可以显式定义预期的类型,或使用 Any”。

【讨论】:

    【解决方案2】:

    您可以使用泛型类来跟踪变化的类型。

    定义两个泛型。

    • T = 当前类型
    • 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
    

    请注意,为了使此功能起作用,您需要将 set 的结果重新分配回 remember。我们需要这样做,因为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'>
    

    【讨论】:

      【解决方案3】:

      不幸的是,python 中的类型提示不会阻止类型的改变,例如在你的 sn-p 中,如果你使用下面的类型提示,它会给出相同的输出:

      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
      
      

      有两种方法可以强制方法只采用特定类型:

      1. 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. 在部署代码之前使用带有类型提示的 mypy 以确保类型正确匹配
      mypy my_python_script.py
      

      这应该是输出

      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)
      

      【讨论】:

      • 这不能回答 OP 的问题。代码应该利用动态类型,而您的解决方案引入了阻止代码运行的更改。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-11-27
      • 2021-11-25
      • 2019-05-20
      • 2019-02-14
      • 1970-01-01
      • 2020-10-24
      • 2012-04-25
      相关资源
      最近更新 更多