5

假设我这样定义了一个新类型:

import typing

Index = typing.NewType('Index', int)

然后假设我有一个Index这样的变量:

index = Index(0)

什么是惯用的增量方式index

如果我做

index += 1

这相当于

index = index + 1

那么从静态类型检查的角度来看,类型index变为int而不是:Index

$ mypy example.py
example.py:4: error: Incompatible types in assignment (expression has type "int", variable has type "Index")

有什么比

index = Index(index + 1)

保持Index类型?

4

2 回答 2

2

您必须int“真正地”创建一个子类(双关语不是故意的,但留在那里已经够糟糕了)。

那里有两个问题:

  • typing.NewType不会创建新类。它只是将对象的“谱系”分开,以便它们“看起来像”静态类型检查工具的新类 - 但是用这样的类创建的对象在运行时仍然属于指定的类。

在交互式提示中查看“typing.Newtype”,而不是依赖静态检查报告:

In [31]: import typing                                                                                                                                                              

In [32]: Index = typing.NewType("Index", int)                                                                                                                                       

In [33]: a = Index(5)                                                                                                                                                               

In [34]: type(a)                                                                                                                                                                    
Out[34]: int
  • 第二个问题是,即使您int以正确的方式进行子类化,应用任何运算符所产生的操作仍会将结果类型转换回int,并且不会属于创建的子类:
In [35]: class Index(int): pass                                                                                                                                                     

In [36]: a = Index(5)                                                                                                                                                               

In [37]: type(a)                                                                                                                                                                    
Out[37]: __main__.Index

In [38]: type(a + 1)                                                                                                                                                                
Out[38]: int

In [39]: type(a + a)                                                                                                                                                                
Out[39]: int

In [40]: a += 1                                                                                                                                                                     

In [41]: type(a)                                                                                                                                                                    
Out[41]: int

因此,唯一的出路是实际上将所有执行数字运算的魔术方法包装在将结果“转换”回子类的函数中。for通过创建一个装饰器来执行此转换,并将其应用于类主体本身的循环中的所有数字方法,可以避免在类主体中多次重复相同的模式。

In [68]: num_meths = ['__abs__', '__add__', '__and__',  '__ceil__', 
'__divmod__',  '__floor__', '__floordiv__', '__invert__', '__lshift__',
'__mod__', '__mul__', '__neg__', '__pos__', '__pow__', '__radd__', 
'__rand__', '__rdivmod__',  '__rfloordiv__', '__rlshift__', '__rmod__',
'__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', 
'__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__sub__', 
'__truediv__', '__trunc__', '__xor__',  'from_bytes'] 
# to get these, I did `print(dir(int))` copy-pasted the result, and 
# deleted all non-relevant methods to this case, leaving only the ones 
# that perform operations which should preserve the subclass


In [70]: class Index(int): 
             for meth in num_meths: 
                locals()[meth] = (lambda meth:
                    lambda self, *args:
                         __class__(getattr(int, meth)(self, *args))
                    )(meth) 
# creating another "lambda" layer to pass the value of meth in _each_
# iteration of the for loop is needed so that these values are "frozen" 
# for each created method

In [71]: a = Index(5)                                                                                                                                                               

In [72]: type(a)                                                                                                                                                                    
Out[72]: __main__.Index

In [73]: type(a + 1)                                                                                                                                                                
Out[73]: __main__.Index

In [74]: a += 1                                                                                                                                                                     

In [75]: type(a)                                                                                                                                                                    
Out[75]: __main__.Index


这实际上会起作用。

但是,如果意图是静态类型检查“看到”这种包装正在发生,那么您将再次偏离轨道。静态类型检查器无法通过在类主体内的循环中应用装饰器来理解方法创建。

换句话说,我认为没有办法解决这个问题,只能通过复制并粘贴上面示例中所有相关数字方法中自动应用的转换,然后在其中创建注释:

from __future__ import annotations
from typing import Union

class Index(int):
    def __add__(self: Index, other: Union[Index, int]) -> Index:
        return __class__(super().__add__(other))
    def __radd__(self: Index, other: Union[Index, int]) -> Index:
        return __class__(super().__radd__(other))
    # Rinse and repeat for all numeric methods you intend to use
于 2020-08-09T03:36:37.083 回答
1

我认为由于对变量的操作,您将无法隐式(间接)保留类型NewType,因为正如文档所述:

您仍然可以对类型的变量执行所有 int 操作UserId,但结果将始终是类型int

因此,对变量的任何操作都会产生基本类型的结果。如果您希望结果为 a NewType,则需要明确指定它,例如 as index = Index(index + 1)。这里Index用作演员功能。

这与以下目的一致NewType

静态类型检查器会将新类型视为原始类型的子类。这对于帮助捕获逻辑错误很有用

NewType 的预期目的是帮助您检测意外将旧的基本类型和新的派生类型混合在一起的情况。

于 2020-08-08T13:01:07.080 回答