微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 mypy 确保 Python 类中的两个字段是给定基类的相同子类 第一种方式:使用类型绑定第二种方式:值限制第三种方式:工厂函数

如何解决使用 mypy 确保 Python 类中的两个字段是给定基类的相同子类 第一种方式:使用类型绑定第二种方式:值限制第三种方式:工厂函数

假设我有以下数据类:

@dataclass
class Product:
    color: str

@dataclass
class Wrench(Product):
    pass

@dataclass
class Hammer(Product):
    pass

我正在尝试创建一个名为 Order 的新数据类,其中包含两个字段,两个字段必须具有相同的 Product 子类。我可以像这样创建 Order 类:

@dataclass
class Order:
    primary_product: Product
    secondary_product: Product

但这并不能验证我上面提到的相同的 Product 子类条件:

product1 = Wrench(color="Yellow")
product2 = Hammer(color="Black")

order = Order(primary_product=product1,secondary_product=product2)  # NO ERROR

Ordergenerics 的以下实现让我了解了一些方法

from typing import Generic,TypeVar

T = TypeVar("T")

@dataclass
class Order(Generic[T]):
    primary_product: T
    secondary_product: T

product1 = Wrench(color="Yellow")
product2 = Wrench(color="White")
product3 = Hammer(color="Black")

order1 = Order[Wrench](primary_product=product1,secondary_product=product2)
order2 = Order[Wrench](primary_product=product1,secondary_product=product3)  # error: Argument "secondary_product" to "Order" has incompatible type "Hammer"; expected "Wrench"

在每次对象初始化时将目标 Product 子类(在上述情况下为 Wrench)传递给 Order 很烦人。此外,这并不能确保两个字段都是产品:

order1 = Order[int](primary_product=1,secondary_product=2)  # NO ERROR

无论如何要实现这一点,还是我将 mypy 和 Python 的类型提示的限制推得太远了?

解决方法

在您发布的 mypy 文档中,进一步介绍了两种不同的方式。两者都不理想。

第一种方式:使用类型绑定

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

现在泛型参数只能是 Product

order1 = Order[int](primary_product=1,secondary_product=2)
# error: Value of type variable "T" of "Order" cannot be "int"

order1 = Order(primary_product=1,secondary_product=2)
# error: Value of type variable "T" of "Order" cannot be "int"

不幸的是,现在可以推断出通用参数正好是 Product

product1 = Wrench(color="Yellow")
product2 = Hammer(color="Black")

order = Order(primary_product=product1,secondary_product=product2)  # no error
reveal_type(order)
# Revealed type is 'Order[Product*]'

所以你必须指定泛型类型

第二种方式:值限制

T = TypeVar('T',Hammer,Wrench)

现在即使这也被正确识别为错误

product1 = Wrench(color="Yellow")
product2 = Hammer(color="Black")

order = Order(primary_product=product1,secondary_product=product2)
# error: Value of type variable "T" of "Order" cannot be "Product"

这种方法的问题很明显:您必须将 Product 的所有子类键入到 TypeVar 构造函数中。

第三种方式:工厂函数

经过一些试验,我发现了第三种方式,它具有一些奇怪的语法,但结合了前两种方式的优点。这个想法是强制泛型参数为第一个参数的类型。

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

def make_order(primary: P) -> Callable[[P],Order[P]]:
    def inner(secondary: P) -> Order[P]:
        return Order(primary,secondary)
    return inner

make_order(1)(2)
# error: Value of type variable "T" of "make_order" cannot be "int"

product1 = Wrench(color="Yellow")
product2 = Hammer(color="Black")

make_order(product1)(product2)
# Argument 1 has incompatible type "Hammer"; expected "Wrench"

order = make_order(product1)(product1)
reveal_type(order)
# Revealed type is 'Order[Wrench*]'

缺点是:

  • 奇怪的语法
  • 误导性错误消息(“参数 1”,因为它指的是第二个调用)

为了防止用户直接实例化 Order,您可以将该类重命名为 _Order

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。