簡體   English   中英

Python 類型提示可以處理通用 class 在方法中更改自己的類型嗎?

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

我有一個現有的 class 可以在某個方法調用時更改重要類型。 它看起來像這樣:

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]即使類型已“銷毀”並且不再正確。

我指的現有 class 是twisted.internet.defer.Deferred ,它允許鏈接回調。 最后一個返回值的類型成為下一個回調的參數。 因此,可以將Deferred的類型視為添加到它的最后一個回調的類型。

我將假設RememberLast必須能夠處理除int s、 string s 和float s 之外的東西(或超過有限的類型聯合),因為否則你可以只使用Union[int, str, float]

不幸的是,我對您可以使用比Any更具體的注釋感到悲觀,原因有兩個:

  1. mypy 自己的文檔建議將Any用於動態類型代碼。 如果可以使用更具體的東西,他們會這么說的。
  2. 這個 StackOverflow 問題提出了類似的問題,並且接受的答案基本上是“重構,以便您可以顯式定義預期的類型,或使用Any ”。

您可以使用通用 class 來跟蹤變化的類型。

定義兩個 generics。

  • 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 back to 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'>

不幸的是,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

有兩種方法可以強制方法僅采用特定類型:

  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

這應該是 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