#python #type-hinting #mypy #python-typing #python-descriptors
Вопрос:
Я наблюдаю поведение, 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
протокол? По словам майпи, это не так:
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 быстро приходит к такому выводу str
и MyDescriptor[str]
просто состоит из 2 разных типов. Справедливо.
Тем не менее, использование простого str
для first_name
или обертывание его в дескриптор, который получает и задает a str
, — это всего лишь деталь реализации. Ввод утки здесь говорит мне, что способ, которым мы будем использовать first_name
(интерфейс), не изменится.
Другими словами, Person
орудия Named
.
В качестве примечания, проверка типов PyCharm в данном конкретном случае не жалуется (хотя я не уверен, сделано ли это специально или случайно).
В соответствии с предполагаемым использованием typing.Protocol
, мое понимание неверно?
Ответ №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)