如何获取Python变量注释?

17

当使用带有注释字段的类/模块时,如何像函数一样获取注释?

class Test:
  def __init__(self):
    self.x : int
t = Test()

现在我需要从getattr(t,'x')中获得'int'。

3
我假设您知道type()函数。也许您可以解释一下为什么它不适合您。 - norok2
3
x是实例属性,在类Test中并不存在,而是存在于该类的实例中。 - Klaus D.
3个回答

10

在基本的Python中,如果不改变Test的定义,就没有办法实现你想要的选项。最简单的更改是在类级别上注释属性:

class Test:
    x: int
    def __init__(self):
        # define self.x or not, but it needn't be annotated again

这其实非常好;默认情况下,类作用域的注释被认为是实例属性而不是类属性(在类作用域分配值会创建类属性,但注释它不会);你必须明确地使用typing.ClassVar来指示注释的类型仅用于类属性。PEP 526关于类和实例变量注释的章节定义了这些行为;它们是可靠的,不仅仅是实现的偶然事件。
完成后,typing.get_type_hints将在您的示例中为Testt都返回{'x': int}
虽然这已经足够了,但我想指出,在许多这样的情况下,只要你已经在注释了,你可以使用 dataclasses 模块 简化你的代码,最小化输入即可定义注释和基本功能。对于你的情况来说,简单的替换代码如下:
import dataclasses

@dataclasses.dataclass
class Test:
    x: int

虽然您的示例未展示全部功能集(基本上只是用装饰器替换了__init__),但它仍然比肉眼可见的更加强大。除了为您定义了__init__(它期望接收一个被注释为intx参数),以及适当的__repr____eq__,您还可以轻松地定义默认值(只需在注释时分配默认值,或者对于更复杂或可变的情况,分配一个dataclasses.field),并且可以传递参数给dataclass,使其生成可排序或不可变实例。

在您的情况下,主要优点是消除了冗余;x被注释并引用了一次,而不是在类级别上注释一次,然后在初始化期间使用(并可选地再次注释)。


1
感谢您详细的回答!我正在使用dataclass,我即将开源一个项目在github上。该项目严重依赖于Python类型提示。我目前支持dataclasses,但我想添加Python新风格类。 - Kamyar

3

我不确定你是否能轻松地获取self.x的注释。

假设您的代码如下:

class Test:
    def __init__(self):
        self.x: int = None

t = Test()

我尝试在Testt中查找__annotations__,但没有找到。

然而,你可以使用以下解决方法:

class Test:
    x: int
    def __init__(self):
        # annotation from here seems to be unreachable from `__annotations__`
        self.x: str

t = Test()

print(Test.__annotations__)
# {'x': <class 'int'>}
print(t.__annotations__)
# {'x': <class 'int'>}

编辑

如果你想要检查mypyself.x的类型,可以参考ruohola提供的答案


编辑2

请注意,mypy(至少版本0.560)会被类和__init__中对x进行的注释所迷惑,即self.x的注释被忽略:

import sys

class Test:
    x: str = "0"
    def __init__(self):
        self.x: int = 1

t = Test()

print(Test.x, t.x)
# 0 1
print(Test.x is t.x)
# False

if "mypy" in sys.modules:
    reveal_type(t.x)
    # from mypyp: annotated_self.py:14: error: Revealed type is 'builtins.str'
    reveal_type(Test.x)
    # from mypy: annotated_self.py:15: error: Revealed type is 'builtins.str'

Test.x = 2
# from mypy: annotated_self.py:17: error: Incompatible types in assignment (expression has type "int", variable has type "str")

t.x = "3"
# no complaining from `mypy`
t.x = 4
# from mypy: annotated_self.py:19: error: Incompatible types in assignment (expression has type "int", variable has type "str")

print(Test.x, t.x)
# 2 4

谢谢!但这不是我需要的!我只想从类构造函数中定义的字段中获取注释! - Kamyar
@Kamyar,“self.”变量很可能是矩阵中的一个小故障(即bug):它们在语法上被允许,但在mypy中没有被检测出来(至少在__init__中,但在使用“t.x”时会被检测出来),最有可能的原因是它们没有触发任何相应的__annotations__。也许你应该在Python / mypy中提出一个问题。 - norok2
没有冲突;x: int 是一个(未定义的)类属性,而 self.x 则创建了一个同名的实例属性。 - chepner
@chepner 完全正确,但是 mypy 会因为多个注释而感到困惑,并选择忽略 self.x 的那一个。 - norok2
1
@chepner:实际上,根据PEP 526,类作用域中的x: int是一个(未定义的)实例属性注释;即使在类作用域中使用x: int = 0表示具有默认值的实例属性(尽管它创建了一个类属性,但意图是像实例变量一样使用它)。只有使用typing.ClassVar进行注释的类属性表达类属性意图。有关详细信息,请参见我的答案 - ShadowRanger
@ShadowRanger 哎呀,错过了。 - chepner

2
如果您正在使用 mypy,则可以使用 reveal_type() 来检查任何表达式的类型注释。请注意,此函数仅在运行 mypy 时可用,而在正常运行时不可用。 我还使用 typing.TYPE_CHECKING,以避免在正常运行文件时出现错误,因为这个特殊的常量只被第三方类型检查器假定为 Truetest.py:
from typing import Dict, Optional, TYPE_CHECKING


class Test:
    def __init__(self) -> None:
        self.x: Optional[Dict[str, int]]


test = Test()
if TYPE_CHECKING:
    reveal_type(test.x)
else:
    print("not running with mypy")

在运行mypy时的示例:
$ mypy test.py
test.py:10: error: Revealed type is 'Union[builtins.dict[builtins.str, builtins.int], None]'


"最初的回答":当正常运行它时:
需要翻译的内容已经完成翻译。
$ python3 test.py
not running with mypy

有没有办法在从常规解释器运行脚本时使其工作? - norok2
很不幸,根据mypy的文档:“reveal_type和reveal_locals仅被mypy理解,在Python中不存在。如果你尝试运行你的程序,在运行代码之前必须删除任何reveal_type和reveal_locals的调用。两者始终可用,无需导入。” 我使用了sys.modules检查,这样我就可以始终拥有相同的代码,并且在正常运行文件时无需删除任何内容。 - ruohola
是的,我看到了,我想可能有其他方法可以解决这个问题。 - norok2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接