我想要一个具有由描述符类定义的自定义属性的类和一个隐藏实现细节的接口。
我的代码目前看起来像这样。它有效,类型检查器不会抱怨,但由于从
cast
到 Ten
,它看起来很难看。如果我希望描述符为 int
,中间函数 ten
也会使事情变得复杂。Generic
是否有可能在不使用像这样的魔术技巧的情况下实现某种程度的类型检查并且类型检查器不会出现错误?
它将返回的东西。如果您输入 from typing import cast
class Ten:
def __get__(self, obj, objtype=None) -> int:
return 10
def __set__(self, obj, value: int) -> None:
pass # just to make Ten "writeable"
def ten() -> int:
return cast(int, Ten()) # <-- this is ugly
class ABase:
x: int
class A(ABase):
x: int = ten()
def fn(value: ABase) -> int:
return value.x
a = A()
print(fn(a))
作为通用描述符,那么 mypy(至少)将理解对属性的访问将导致描述符返回的结果。它甚至会理解描述符如何根据是在类上还是在实例上访问描述符来返回不同的结果。例如。
ABase.x
如果您对此文件调用
from typing import overload, Callable, Generic, TypeVar
T = TypeVar('T')
Value = TypeVar('Value')
ClassValue = TypeVar('ClassValue')
class GenericNonDataDescriptor(Generic[T, Value, ClassValue]):
def __init__(
self,
inst_func: Callable[[T], Value],
class_func: Callable[[type[T]], ClassValue]
) -> None:
self.inst_func = inst_func
self.class_func = class_func
@overload
def __get__(self, instance: T, owner: type[T]) -> Value: ...
@overload
def __get__(self, instance: None, owner: type[T]) -> ClassValue: ...
def __get__(self, instance: T | None, owner: type[T]) -> Value | ClassValue:
print(f'{instance=} and {owner=}')
if instance is None:
return self.class_func(owner)
else:
return self.inst_func(instance)
def ten(instance: 'Foo') -> int:
return 10
def ten_for_class(cls: type['Foo']) -> str:
return 'called on class'
class MyClass:
x = GenericNonDataDescriptor(ten, ten_for_class)
reveal_type(MyClass().x) # line 33
reveal_type(MyClass.x) # line 34
,您将得到:
mypy
替代方法
file.py:33: note: Revealed type is "builtins.int"
file.py:34: note: Revealed type is "builtins.str"
Success: no issues found in 1 source file
属性的类型更改为“
x
”的并集之外,您实际上还能做什么?一个答案是使用属性。 Mypy 和其他类型检查器对属性有更好的理解,即使它们是作为(数据)描述符实现的。使用属性(和 int | Descriptor[int]
模块),您可以获得几乎相同的接口、易于阅读的类型以及未定义所需接口的类的运行时保护。最后一点很重要,如果一个类忘记“覆盖”
abc
,那么类型检查器不会抱怨,并且该属性在运行时不会出现。x
关键的区别在于,通过类访问属性不会调用该属性,而是返回属性本身。 mypy 显示的是:
from abc import ABC, abstractmethod
class Base(ABC):
@abstractmethod
def _x(self) -> int: ...
@property
def x(self) -> int:
return self._x()
class Foo(Base):
def _x(self) -> int:
return 10
reveal_type(Foo().x) # line 15
reveal_type(Foo.x) # line 16