【问题标题】:Python type-checking Protocols and DescriptorsPython 类型检查协议和描述符
【发布时间】:2021-10-11 03:09:55
【问题描述】:

当涉及描述符时,我观察到关于 typing.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 协议? According to mypy, it isn't:

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 的描述符中只是一个实现细节。这里的 Duck-typing 告诉我,我们使用 first_name(界面)的方式不会改变。

换句话说,Person 实现了Named

作为旁注,PyCharm 的类型检查器在这种特殊情况下不会抱怨(尽管我不确定这是设计使然还是偶然)。

根据typing.Protocol的预期用途,我的理解有误吗?

【问题讨论】:

    标签: python type-hinting mypy python-typing python-descriptors


    【解决方案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)
    

    这个passes MyPy

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-13
      • 2018-04-24
      相关资源
      最近更新 更多