Python类型检查协议和描述符

3

我注意到涉及 Descriptorstyping.Protocol 行为,但我并不完全理解。考虑以下代码:

import typing as t

T = t.TypeVar('T')


class MyDescriptor(t.Generic[T]):

    def __set_name__(self, owner, name):
        self.name = name

    def __set__(self, instance, value: T):
        instance.__dict__[self.name] = value

    def __get__(self, instance, owner) -> T:
        return instance.__dict__[self.name]


class Named(t.Protocol):

    first_name: str


class Person:

    first_name = MyDescriptor[str]()
    age: int

    def __init__(self):
        self.first_name = 'John'


def greet(obj: Named):
    print(f'Hello {obj.first_name}')


person = Person()
greet(person)

Person类是否隐式实现了Named协议?根据 mypy的结果,它并没有实现

error: Argument 1 to "greet" has incompatible type "Person"; expected "Named"
note: Following member(s) of "Person" have conflicts:
note:     first_name: expected "str", got "MyDescriptor[str]"

我猜这是因为mypy很快就得出结论,即strMyDescriptor[str]是两种不同的类型。这是公平的。
然而,对于first_name使用普通的str或将其包装在获取和设置str的描述符中只是一些实现细节。在此进行鸭子类型判断时,first_name(接口)的使用方式不会改变。
换句话说,Person实现了Named
顺便说一下,PyCharm的类型检查器在这种特定情况下不会抱怨(虽然我不确定这是出于设计还是偶然)。
根据typing.Protocol的预期用途,我的理解错了吗?
1个回答

1

我很难找到相关的参考资料,但我认为MyPy在一些描述符的细节上有些困难(你可以理解为什么,因为那里有相当多的魔法)。我认为这里的一个解决方法就是使用typing.cast

import typing as t

T = t.TypeVar('T')


class MyDescriptor(t.Generic[T]):
    def __set_name__(self, owner, name: str) -> None:
        self.name = name

    def __set__(self, instance, value: T) -> None:
        instance.__dict__[self.name] = value

    def __get__(self, instance, owner) -> T:
        name = instance.__dict__[self.name]
        return t.cast(T, name)


class Named(t.Protocol):
    first_name: str


class Person:
    first_name = t.cast(str, MyDescriptor[str]())
    age: int

    def __init__(self) -> None:
        self.first_name = 'John'


def greet(obj: Named) -> None:
    print(f'Hello {obj.first_name}')


person = Person()
greet(person)

这个句子的翻译是:“这个 通过 MyPy。”,其中保留了HTML格式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接