PEP 673–Python接受了自我类型

2022-02-17 02:50:45

本PEP介绍了一种简单直观的方法来注释返回类实例的方法。这与PEP 484中指定的TypeVar basedapproach的行为相同,但更简洁,更容易理解。

一个常见的用例是编写一个方法,通常通过返回self来返回sameclass的实例。

类形状:def set_scale(self,scale:float):self。scale=缩放返回selfShape()。设置_刻度(0.5)#=>;应该是形状

表示返回类型的一种方法是将其指定为当前类,例如Shape。使用该方法可使类型检查器按预期推断类型形状。

类别形状:def set_scale(自我,比例:浮动)->;形状:自我。scale=缩放返回selfShape()。设置_刻度(0.5)#=>;形状

然而,当我们在Shape的子类上调用set_scale时,typechecker仍然推断返回类型为Shape。这是有问题的Insituation,如下图所示,类型检查器将返回一个错误,因为我们试图使用基类上不存在的属性或方法。

类圆(形状):def set_半径(self,r:float)->;圆圈:自我。半径=r返回自圆()。设置_scale(0.5)#*Shape*,而不是CircleCircle()。设置_刻度(0.5)。设置_半径(2.7)#=>;错误:形状没有“半径”属性集

此类实例的当前解决方法是定义一个以基类为绑定的TypeVar,并将其用作Self参数和返回类型的注释:

输入import TypeVarTShape=TypeVar(";TShape";,bound=";Shape";)类别形状:def set_比例(自:T形状,比例:浮动)->;形状:自我。缩放=缩放返回自类圆(形状):定义设置_半径(自,半径:浮动)->;圆圈:自我。半径=半径返回自圆()。设置_刻度(0.5)。设置_半径(2.7)#=>;圆圈

不幸的是,这是冗长和不直观的。因为self通常没有明确的注释,所以上面的解决方案没有';我不会马上想到,即使它想到了,也很容易因为忘记TypeVar上的bound而出错(bound=";Shape";)或者自我诠释。

这种困难意味着用户通常会放弃,要么像Any一样使用回退类型,要么完全忽略类型注释,这两种情况都会降低代码的安全性。

我们提出了一种更直观、更简洁的方式来表达上述意图。我们引入一种特殊形式的Self,它代表绑定到封装类的类型变量。对于上述情况,用户只需将返回类型注释为Self:

输入import Selfclass Shape:def set_scale(self,scale:float)->;自我:自我。缩放=缩放返回自类圆(形状):定义设置_半径(自,半径:浮动)->;自我:自我。半径=半径返回自我

通过将返回类型注释为Self,我们不再需要在基类上声明显式绑定的TypeVar。返回类型self反映了函数返回self的事实,因此更容易理解。

如上例所示,类型检查器将正确推断圆()的类型。按预期将_刻度(0.5)设置为圆形。

我们分析了PopularPen源项目,发现上面这样的模式的使用频率大约是dict或Callable等流行类型的40%。例如,在单独类型化中,这种“自我”类型被使用了523次,相比于1286使用DICT和1314使用可调用的2021年10月。这表明Self类型将被经常使用,用户将从上述更简单的方法中受益匪浅。

Python类型的用户也经常在DocProposal和GitHub上请求此功能。

方法签名中使用的Self被视为绑定到类的TypeVar。

输入import Selfclass Shape:def set_scale(self,scale:float)->;自我:自我。刻度=刻度返回自我

输入import-TypeVarSelfShape=TypeVar(";SelfShape";,bound=";Shape";)类别形状:def set_比例(自我:自我形状,比例:浮动)->;自我塑造:自我。刻度=刻度返回自我

SelfCircle=TypeVar(";SelfCircle";,bound=";Circle";)类圆(形状):定义设置_半径(自:自圆,半径:浮动)->;自我循环:自我。半径=半径返回自我

一种实现策略是在预处理步骤中简单地将前一个减粘到后一个。如果方法在其签名中使用Self,则方法中的Self类型将为Self。在其他情况下,自我类型仍将是封闭类。

Self-type注释对于返回其操作的类的实例的classmethods也很有用。例如,下面代码段中的from_config从给定的配置构建一个Shape对象。

类形状:def _uinit(self,scale:float)->;无:@来自_config(cls,config:dict[str,float])的类方法定义->;形状:返回cls(配置[";缩放";])

然而,这意味着这个循环。从_config(…)被推断为返回类型Shape的avalue,而实际上它应该是圆形:

类圆(形状):。。。形状。从_config({";scale";:7.0})#=>;Shapecircle=圆。从_config({";scale";:7.0})#=>*形状*,而不是圆形。周长()#错误:`Shape`没有属性`percentry`

Self=TypeVar(";Self";,bound=";Shape";)类形状:@classmethod def from_config(cls:type[Self],config:dict[str,float])->;Self:return cls(配置[";缩放";])

输入import Selfclass Shape:@classmethod def from_config(cls,config:dict[str,float])->;Self:return cls(配置[";缩放";])

这避免了复杂的cls:type[Self]注释和带有绑定的TypeVardeclaration。同样,后一种代码的行为与前一种代码相当。

Self=TypeVar(";Self";,bound=";Shape";)班级形态:定义差异(自我:自我,其他:自我)->;浮子:。。。def apply(self:self,f:Callable[[self],None])->;无:。。。

输入import Selfclass Shape:def difference(self,other:self)->;浮子:。。。def apply(self,f:可调用[[self],无])->;无:。。。

请注意,指定self:self是无害的,因此一些用户可能会发现将上面的内容写为:

Self=TypeVar(“Self”,bind=“Container[Any]”)类容器(Generic[T]):value:T def set_value(Self:Self,value:T)->;赛尔夫:。。。

其行为是保留调用方法的对象的类型参数。当调用具体类型为Container[int]的对象时,Self绑定到Container[int]。当使用泛型类型Container[T]的对象调用Self时,Self绑定到Container[T]:

def object_with_concrete_type()->;无:int_container:container[int]str_container:container[str]leaver_type(int_container.set_value(42))#=>;容器[int]显示_类型(str_Container.set_value(“hello”))#=>;Container[str]def object_与_generic_type(Container:Container[T],value:T,)->;容器[T]:返回容器。设置_值(值)#=>;容器[T]

政治公众人物没有具体说明自己的类型。方法集合中的值。一些类型检查器可能会选择使用Self=TypeVar(“Self”,bound=Container[T])的类局部类型变量来实现自类型,这将推断出精确的类型T。然而,鉴于类局部类型变量不是标准化的类型系统功能,也可以为Self推断任何类型。价值我们把这件事交给打字员处理。

请注意,我们拒绝将Self与类型参数一起使用,例如Self[int]。这是因为它会对自参数的类型产生歧义,并引入不必要的复杂性:

类容器(泛型[T]):def foo(self,other:self[int],other2:self,)->;Self[str]:#被拒绝。。。

输入导入协议,Selfclass Shape(协议):scale:float def set_scale(self,scale:float)->;自我:自我。刻度=刻度返回自我

输入import TypeVarSelfShape=TypeVar(";SelfShape";,bound=";ShapeProtocol";)类形状(协议):比例:浮动定义集\u比例(自我:自我形状,比例:浮动)->;自我塑造:自我。刻度=刻度返回自我

检查类与协议的兼容性:如果协议在方法或属性注释中使用Self,那么如果相应的方法和属性注释使用Self或Foo或Foo的任何ssubclass,则类Foo被认为与协议兼容。请参见以下示例:

输入import Protocolclass ShapeProtocol(协议):def set_scale(self,scale:float)->;赛尔夫:。。。class ReturnSelf:scale:float=1.0 def set_scale(self,scale:float)->;自我:自我。scale=缩放返回selfclass ReturnConcreteShape:缩放:浮点=1.0 def set_scale(self,scale:float)->;形状:自我。刻度=刻度回位selfclass BadReturnType:刻度:浮点=1.0 def set_刻度(self,刻度:浮点)->;内特:自我。刻度=刻度返回42等级返回差异等级:刻度:浮动=1.0 def set_刻度(自身,刻度:浮动)->;ReturnConcreteShape:ReturnConcreteShape(…)def接受_形状(形状:ShapeProtocol)->;无:y=形状。设置_刻度(0.5)显示_类型(y)def main()->;None:return_self_shape:ReturnSelf return_concrete_shape:ReturnConcreteShape bad_return_type:BadReturnType return_different_class:ReturnDifferentClass accepts_shape(return_self_shape)#OK accepts_shape(bad_return_type)#Not OK#Not OK,因为它返回的是非子类。接受形状(返回不同的类)

自注释仅在类上下文中有效,并且总是引用封装类。在涉及嵌套类的上下文中,self总是指最里面的类。

类返回自我:def foo(self)->;赛尔夫:…#接受@classmethod def bar(cls)->;Self:#接受返回cls()定义u新u(cls,值:int)->;赛尔夫:…#接受定义明确地使用self(self:self)>;赛尔夫:…#已接受#已接受(Self可以嵌套在其他类型中)def返回_list(Self)->;列表[自我]:…#已接受(Self可以嵌套在其他类型中)@classmethod def return_cls(cls)->;键入[Self]:return clsclass Child(ReturnsSelf):#已接受(我们可以重写使用自注释的方法)def foo(Self)->;赛尔夫:。。。课程自选:def foo(self,other:self)>;布尔:…#Acceptedclass Recursive:#Accepted(被视为返回``Self | None``的@property)next:Self | Noneclass CallableAttribute:def foo(Self)->;国际:…#已接受(视为返回可调用类型的@property)条:可调用[[Self],int]=fooclass HasNestedFunction:x:int=42 def foo(Self)->;无:#已接受(Self与HasNestedFunction绑定)。def嵌套(z:int,inner_self:self)>;Self:print(z)print(inner_Self.x)return inner_Self嵌套(42,Self)#ok类外部:类内部:def foo(Self)->;赛尔夫:…#被接受(自我与内在相结合)

def foo(酒吧:自助)>;赛尔夫:…#拒绝(不在类内)栏:Self#拒绝(不在类内)class Foo:#拒绝(Self被视为未知)。def有_现有的_self_注释(self:T)->;赛尔夫:。。。Foo类:def返回混凝土类型(自)->;Self:return Foo()#retired(拒绝)(参见下面的FooChild了解基本原理)类FooChild(Foo):child_值:int=42 def child_方法(Self)->;无:#在运行时,这将是Foo,而不是FooChild。y=自我。返回_concrete_type()y.child_value#运行时错误:Foo没有属性child_valueclass Bar(通用[T]):def Bar(self)>;T:。。。Baz班(Foo[Self]):…#拒绝

我们拒绝包含Self的类型别名。支持Selfoutside类定义可能需要很多特殊的类型检查处理。考虑到它也与类定义之外的其他使用PEP的自我相违背,我们认为别名的附加便利性是不值得的:

TupleSelf=Tuple[Self,Self]#拒绝类别名:def return_Tuple(Self)->;TupleSelf:#拒绝返回(self,self)

注意,我们在静态方法中拒绝Self。Self不会增加多少价值,因为没有Self或cls可以返回。唯一可能的用例是返回一个参数本身或作为参数从容器中传递的某个元素。这些似乎不值得增加复杂性。

类基:@staticmethod def make()->;赛尔夫:#被拒绝了@staticmethod def return_参数(foo:Self)->;赛尔夫:#被拒绝了。。。

同样,我们在元类中拒绝自我。在这个政治公众人物中,自我始终指同一类型(自我)。但在元类中,它必须在不同的方法签名中引用不同的类型。例如,在_mul__;中,返回类型中的Self将引用实现类Foo,而不是封闭类MyMetaclass。但是,在_new__中,返回类型中的Selfin将引用封闭类MyMetaclass。为了避免混淆,我们拒绝这个边缘案例。

类MyMetaclass(类型):def uu new uu(cls,*args:Any)->;Self:#拒绝返回super()__新建(cls,*args)定义(cls,计数:int)>;list[Self]:#拒绝返回[cls()]*countclass Foo(metaclass=MyMetaclass):。。。

一个建议是让Self类型保持隐式,并让type checker从方法体中选择返回类型必须与Self参数的类型相同:

类形状:def set_scale(self,scale:float):self。scale=scale return self#类型检查器推断我们正在返回self

我们拒绝这一点,因为显式优于隐式。除此之外,对于没有方法体可分析的类型存根,上述方法将失败。

关于Python中自我类型的类似讨论始于2016年左右的Mypy:Mypy问题#1212-自我类型或其他拼写方法";自我类型";。然而,最终采用的方法是有界TypeVar方法,如我们的";34岁之前;例子。讨论这一点的其他问题包括Mypy问题#2354——GenericClass中的自我类型。

贾晨、丽贝卡·陈、谢尔盖·勒贝捷夫、凯琳·摩根、托马苏塔里、埃里克·特劳特、亚历克斯·韦古德、朱香农和аааСааааааа

本文件置于公共领域或CC0-1.0-Universal许可证下,以较宽松的为准。

资料来源:https://github.com/python/peps/blob/master/pep-0673.rst