使用类型注释来缩小已声明 Python 变量的类型

Use type comments to narrow typing of already declared Python variable

如何使用 Python 中的类型注释来更改或缩小已声明变量的类型,使 pycharm 或其他类型感知系统能够理解新的类型类型。

例如,我可能有两个 classes:

class A:
   is_b = False
   ...

class B(A):
   is_b = True

   def flummox(self):
       return '?'

和其他地方的另一个功能:

def do_something_to_A(a_in: A):
    ...
    if a_in.is_b:
       assert isinstance(a_in, B)  # THIS IS THE LINE...
       a_in.flummox()

只要我有assert声明,PyCharm就会明白我把a_in缩小为class B,而不是抱怨.flummox()。没有它,就会出现errors/warnings之类的a_in has no method flummox

我的问题是,是否有 PEP 484(或后继者)方式表明 a_in(最初可能是 A 或 B 类型或其他类型)现在是 B 类型 没有 assert 语句。语句 b_in : B = a_in 也给出了类型错误。

在 TypeScript 中我可以这样做:

if a_in.is_b:
   const b_in = <B><any> a_in;
   b_in.flummox()

// or

if a_in.is_b:
   (a_in as B).flummox()

我不想使用 assert 行的主要原因有两个:(1) 速度对这部分代码非常重要,并且每次该行都需要额外的 is_instance 调用运行 太慢了,并且 (2) 项目代码风格禁止裸断言语句。

只要您使用的是 Python 3.6+,您就可以任意 "re-annotate" 变量的类型,使用与 "declare" 变量类型相同的语法未初始化的变量 (PEP 526).

在您提供的示例中,以下代码段具有您期望的行为:

def do_something_to_A(a_in: A):
    ...
    if a_in.is_b:
       a_in: B
       a_in.flummox()

我已经测试 PyCharm 2019.2 正确检测到此技术。

值得注意的是,这不会产生任何运行时成本,因为使用或不使用此添加的注释语句都会生成相同的字节码。给定以下定义,

def do_something_with_annotation(a_in: A): 
     if a_in.is_b: 
        a_in: B 
        a_in.flummox() 


def do_something_without_annotation(a_in: A): 
     if a_in.is_b: 
        a_in.flummox() 

dis 生成以下字节码:

>>> dis.dis(do_something_with_annotation)
  3           0 LOAD_FAST                0 (a_in)
              2 LOAD_ATTR                0 (is_b)
              4 POP_JUMP_IF_FALSE       14

  5           6 LOAD_FAST                0 (a_in)
              8 LOAD_ATTR                1 (flummox)
             10 CALL_FUNCTION            0
             12 POP_TOP
        >>   14 LOAD_CONST               0 (None)
             16 RETURN_VALUE
>>> dis.dis(do_something_without_annotation)
  3           0 LOAD_FAST                0 (a_in)
              2 LOAD_ATTR                0 (is_b)
              4 POP_JUMP_IF_FALSE       14

  4           6 LOAD_FAST                0 (a_in)
              8 LOAD_ATTR                1 (flummox)
             10 CALL_FUNCTION            0
             12 POP_TOP
        >>   14 LOAD_CONST               0 (None)
             16 RETURN_VALUE

作为旁注,您还可以通过使用 -O 标志调用解释器来在生产环境中保留断言语句和 disable assertions。这可能会或可能不会被您的同事认为更具可读性,具体取决于他们对 Python.

中类型提示的熟悉程度