告诉基类的方法在派生类中使用更多限制类型提示?

2024-10-03 00:21:33 发布

您现在位置:Python中文网/ 问答频道 /正文

情景

假设我有一个通用的Store类,它实现了各种方法来检索StoreObjects。为了填充存储,它定义了一个抽象方法load_object。你知道吗

然后我创建一个CarStore。我从Store派生并覆盖load_object方法以返回Car对象。你知道吗

现在的问题是如何为此添加类型提示。首先是代码:

from typing import Dict
import weakref
import abc


class StoreObject:
    pass


class Car(StoreObject):
    def __init__(self, color: str):
        self.color = color  # type: str


class Store(abc.ABC):
    def __init__(self):
        self._cache = weakref.WeakValueDictionary()  # type: weakref.WeakValueDictionary[int, StoreObject]

    def get(self, index: int) -> StoreObject:
        try:
            return self._cache[index]
        except KeyError:
            obj = self.load_object(index)
            self._cache[index] = obj
            return obj

    @abc.abstractmethod
    def load_object(self, index: int) -> StoreObject:
        raise NotImplementedError


class CarStore(Store):
    def load_object(self, index: int) -> Car:
        if index < 100:
            return Car("red")
        else:
            return Car("blue")


store = CarStore()
car = store.get(10)
print("Your car color is", car.color)

类型检查错误

问题出现在以下行中:

print("Your car color is", car.color)

这里PyCharm给出了以下警告:

Unresolved attribute reference 'color' for class 'StoreObject'

Mypy给出以下错误:

development/storetyping.py:39: error: "StoreObject" has no attribute "color"

而且PyCharm代码完成显然不包括namestore.get(10).?方法。你知道吗

问题

如何键入基类以便PyCharmmypy可以成功地检查此代码?你知道吗

有没有办法参数化Store中的类型,以便在创建CarStore时,我可以告诉它在注释中使用Car而不是StoreObject?你知道吗


Tags: 方法storeselfindexreturnobjectdefload
2条回答

您的类型检查工作正常;getCarStore中没有被重写,因此它的注释继续指定它返回StoreObject。如果要更改注释,必须在CarStore中重新定义get,例如添加:

def get(self, index: int) -> Car:
    return typing.cast(Car, super().get(index))

确保import typing可以访问cast(或者使用不合格的cast并将其添加到from typing import Dict导入中)。你知道吗

为了避免运行时性能开销,您只能基于if typing.TYPE_CHECKING:测试有条件地定义get(当静态检查器分析代码时返回True,运行代码时返回False),因此get重载实际上在运行时没有定义。你知道吗

在更静态的语言中,您可以创建Store作为泛型类,并在从Store继承时使用Car作为类型参数。你知道吗

实际上,我们可以使用python中的typing模块来实现这一点。你知道吗

下面是一个简单的例子:

from typing import Generic, TypeVar


T = TypeVar('T')  # this is the generic placeholder for a type

# Store is a generic class with type parameter T
class Store(Generic[T]):
    def get(self) -> T:  # this returns a T
        return self.load_object()

    def load_object(self) -> T:  # this also returns a T
        raise NotImplementedError


class Car:
    def __init__(self, color):
        self.color = color

# Now we inherit from the Store and use Car as the type parameter
class CarStore(Store[Car]):
    def load_object(self):
        return Car('red')


s = CarStore()
c = s.get()
print(c.color)  # Code completion works and no warnings are shown

编辑:

为了解决ShadowRanger的问题:如果您希望Car和所有产品都有一个公共基类,那么可以使用boundTypeVar参数。谢谢您胡安帕.阿里维拉加为了提示。你知道吗

所以我们创建一个产品类并将TypeVar绑定到它。你知道吗

class Product:
    def get_id(self):
        raise NotImplementedError

T = TypeVar('T', bound=Product)

Mypy现在会抱怨:

class CarStore(Store[Car]):
    def load_object(self):
        return Car('red')

因为Car不是Product。所以让我们也改变一下:

class Car(Product):
    def get_id(self):
        return ...

    def __init__(self, color):
        self.color = color

现在,玛比很高兴。你知道吗

编辑2:

下面是带有更多注释的完整代码,这些注释甚至使mypy strict感到高兴。你知道吗

from typing import Generic, TypeVar


class Product:
    def get_id(self) -> int:
        raise NotImplementedError


T = TypeVar('T', bound=Product)


class Store(Generic[T]):
    def get(self) -> T:
        return self.load_object()

    def load_object(self) -> T:
        raise NotImplementedError


class Car(Product):
    def get_id(self) -> int:
        return hash(self.color)

    def __init__(self, color: str):
        self.color = color


class CarStore(Store[Car]):
    def load_object(self) -> Car:
        return Car('red')


if __name__ == '__main__':
    s = CarStore()
    c = s.get()
    print(c.color)

相关问题 更多 >