鸭子类型注解在Python3中

huangapple go评论85阅读模式
英文:

Duck Typing Annotations in Python3

问题

我试图为一个具有与另一个dataclass重叠属性的函数输入参数添加类型注释,实际上将其作为输入参数传递。

考虑以下代码:

from dataclasses import dataclass
from typing import TypeVar

@dataclass
class Foo:
    a: str
    zar: str

@dataclass
class Car(Foo):
    b: str

@dataclass
class CarInterface:
    a: str
    b: str

mar = TypeVar("mar", bound=CarInterface)

def blah(x: mar):
    print(x.a)

car_instance = Car(a="blah blah", zar="11", b="bb")
blah(car_instance)

在这个示例中,我尝试创建自己的类型注释mar,它受CarInterface的限制。我希望检查传递给blah()的任何类至少具有ab属性(不关心类是否具有其他属性,如zar)。我之所以这样做,是因为类Car(实际传递的类)将来将是许多将被编写并传递给这个函数的类之一。

我还希望很容易定义一个新的Car,因此我希望避免抽象类,因为我认为增加复杂性不值得让mypy满意。

因此,我尝试创建mar,它使用鸭子类型来表示Car符合CarInterface的接口。

然而,我遇到了两个mypy错误。

第一个错误出现在def blah中的mar注释处

TypeVar "mar" appears only once in generic function signature

另一个错误是我将car_instance传递给blah()的地方

Argument of type "Car" cannot be assigned to parameter "x" of type "bar@blah" in function "blah"
  Type "Car" cannot be assigned to type "CarInterface"
    "Car" is incompatible with "CarInterface"
英文:

I am trying to add a type annotation to a function input argument that is a dataclass with attributes that overlap with another dataclass, which actually gets passed in as an input argument.

Consider the following code:

from dataclasses import dataclass
from typing import TypeVar


@dataclass
class Foo:
    a: str
    zar: str

@dataclass
class Car(Foo):
    b: str


@dataclass
class CarInterface:
    a: str
    b: str


mar = TypeVar("mar", bound=CarInterface)


def blah(x: mar):
    print(x.a)

car_instance = Car(a="blah blah", zar="11", b="bb")
blah(car_instance)

In this example, I'm trying to create my own type annotation mar which is bound by CarInterface. I want to check that whatever class is passed into blah() at least has a and b attributes (don't care if the class has other attributes such as zar). I want to do it this way because class Car (which actually gets passed in) is one of many classes that will be written in the future and passed into this function.

I also want it to be very easy to define a new Car, so I would like to avoid abstract classes as I don't think the added complexity is worth mypy being happy.

So I'm trying to create mar which uses duck typing to say that Car satisfies the interface of CarInterface.

However, I get two mypy errors.

The first is on the mar annotation in def blah

TypeVar "mar" appears only once in generic function signaturePylancereportInvalidTypeVarUse

And the other is where I pass car_instance into blah()

Argument of type "Car" cannot be assigned to parameter "x" of type "bar@blah" in function "blah"
  Type "Car" cannot be assigned to type "CarInterface"
    "Car" is incompatible with "CarInterface"PylancereportGeneralTypeIssues

答案1

得分: 4

使用Protocol来定义CarInterface而不是dataclass

from dataclasses import dataclass
from typing import Protocol

@dataclass
class Foo:
    a: str
    zar: str

@dataclass
class Car(Foo):
    b: str

class CarInterface(Protocol):
    a: str
    b: str

def blah(x: CarInterface):
    print(x.a)

car_instance = Car(a="blah blah", zar="11", b="bb")
blah(car_instance)

上述代码将进行类型检查,但是如果你尝试传递给blah一个Foo而不是Car,你将得到一个mypy错误,类似于:

test.py:22: error: Argument 1 to "blah" has incompatible type "Foo"; expected "CarInterface"
test.py:22: note: "Foo" is missing following "CarInterface" protocol member:
test.py:22: note:     b
Found 1 error in 1 file (checked 1 source file)

Protocol可以被用作TypeVar的约束,但只有在你想要表明两个变量不仅实现了协议,而且还是相同特定类型时才需要使用TypeVar(例如,表明函数接受任何实现了CarInterface的对象,并返回完全相同类型的对象,而不是其他任意的CarInterface实现)。

英文:

Use a Protocol to define CarInterface rather than a dataclass:

from dataclasses import dataclass
from typing import Protocol

@dataclass
class Foo:
    a: str
    zar: str

@dataclass
class Car(Foo):
    b: str

class CarInterface(Protocol):
    a: str
    b: str

def blah(x: CarInterface):
    print(x.a)

car_instance = Car(a="blah blah", zar="11", b="bb")
blah(car_instance)

The above code will typecheck fine, but if you try to pass blah a Foo instead of a Car you'll get a mypy error like this:

test.py:22: error: Argument 1 to "blah" has incompatible type "Foo"; expected "CarInterface"
test.py:22: note: "Foo" is missing following "CarInterface" protocol member:
test.py:22: note:     b
Found 1 error in 1 file (checked 1 source file)

A Protocol can be used as the bound for a TypeVar, but it's only necessary to use a TypeVar if you want to indicate that two variables not only implement the protocol but are also the same specific type (e.g. to indicate that a function takes any object implementing CarInterface and returns the same exact type of object rather than some other arbitrary CarInterface implementation).

huangapple
  • 本文由 发表于 2023年2月6日 09:48:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/75356710.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定