Python 将键入和Mypy与描述符一起使用
我已经看过了一些关于使用描述符打字的SO帖子和github问题,但是我还没有解决我的问题 我有包装器类,我想将属性定义为描述符,可以获取和“转换”内部数据结构的属性Python 将键入和Mypy与描述符一起使用,python,mypy,python-typing,Python,Mypy,Python Typing,我已经看过了一些关于使用描述符打字的SO帖子和github问题,但是我还没有解决我的问题 我有包装器类,我想将属性定义为描述符,可以获取和“转换”内部数据结构的属性 class DataDescriptor(object): def __init__(self, name: str, type_): self.name = name self.type_ = type_ def __get__(self, instance, cls):
class DataDescriptor(object):
def __init__(self, name: str, type_):
self.name = name
self.type_ = type_
def __get__(self, instance, cls):
if not instance:
raise AttributeError("this descriptor is for instances only")
value = getattr(instance._data, self.name)
return self.type_(value)
class City(object):
zip_code: str = DataDescriptor("zip_code", str)
# mypy: Incompatible types in assignment
population: float = DataDescriptor("population", float)
# mypy: Incompatible types in assignment
def __init__(self, data):
self._data = data
class InternalData:
# Will be consumed through city wrapper
def __init__(self):
self.zip_code = "12345-1234"
self.population = "12345"
self.population = "12345"
data = InternalData()
city = City(data)
assert city.zip_code == "12345-1234"
assert city.population == 12345.0
我想我可能可以使用TypeVar,但我还没能完全理解它
这就是我尝试过的——我想我可以动态地描述描述符将采用“类型”,并且该类型也是\uuuuu get\uuuu
将返回的类型。我走对了吗
from typing import TypeVar, Type
T = TypeVar("T")
class DataDescriptor(object):
def __init__(self, name: str, type_: Type[T]):
self.name = name
self.type_ = type_
def __get__(self, instance, cls) -> T:
if not instance:
raise AttributeError("this descriptor is for instances only")
value = getattr(instance._data, self.name)
return self.type_(value)
# Too many arguments for "object"mypy(error)
你的解决方案很接近。为了让它完全工作,您只需再做三个更改:
\uuuuu init\uuuu
的T的值是什么,它实际上都将完全独立于T\uuu get\uuuu
返回的值
这与您想要的完全相反:您希望不同方法之间的T
值完全相同
要修复此问题,请让DataDescriptor继承自Generic[T]
。(在运行时,这与从对象
继承基本相同)DataDescriptor[str]
和DataDescriptor[float]
类型
基本上,这里发生的事情是,字段本身实际上是DataDescriptor对象,需要进行注释。稍后,当您实际尝试使用city.zip\u code
和city.population
字段时,mypy将意识到这些字段是描述符,并使它们的类型与\u get\uuu
方法的返回类型不同
此行为对应于运行时发生的情况:您的属性实际上是描述符,只有在尝试访问这些属性时,您才能返回float或str数据描述符的签名中,将类型[T]
更改为可调用[[str],T]
,可调用[[Any],T]
,或可调用[…],T]
基本上,做Type[T]
不起作用的原因是mypy不知道您可能会给描述符指定哪种类型的Type[…]
对象。例如,如果您尝试执行foo=DataDescriptor('foo',object)
,会发生什么?这将使\uuu get\uuu
最终调用对象(“某个值”)
,这将在运行时崩溃
因此,让您的DataDescriptor接受任何类型的转换器函数。根据需要,您可以让转换器函数只接受一个字符串(Callable[[str],T]
),任意类型的任何单个参数(Callable[[any],T]
),或者任意类型的任意数量的参数(Callable[…,T]
)
输入import Generic、TypeVar、Any、Callable
T=TypeVar('T')
类数据描述符(泛型[T]):
#注意:我将'type_uu`重命名为'converter',因为我觉得这样更好
#反映了这个论点现在可以做什么。
def uu init uuu(self,name:str,converter:Callable[[str],T])->无:
self.name=名称
self.converter=转换器
def uu get uu(self,instance:Any,cls:Any)->T:
如果没有,请举例说明:
raise AttributeError(“此描述符仅用于实例”)
value=getattr(实例。\u数据,self.name)
返回自转换器(值)
类别城市(对象):
#注意,“str”和“float”仍然是有效的转换器——它们的
#构造函数都可以接受单个str参数。
#
#我个人也喜欢省略字段上的类型提示,除非
#必要:我觉得那样看起来更干净。
邮政编码=数据描述符(“邮政编码”,str)
填充=数据描述符(“填充”,浮点)
定义初始化(自身,数据):
self.\u data=数据
类内部数据:
定义初始化(自):
self.zip_code=“12345-1234”
self.population=“12345”
self.population=“12345”
数据=内部数据()
城市=城市(数据)
#reveal_type是mypy理解的一个特殊伪函数:
#它会让mypy打印出你给它的任何表达式的类型。
显示类型(city.zip_code)#显示类型为'str'
显示类型(城市人口)#显示类型为“浮动”
感谢您的全面和周到的回复-正如所述。