当使用带有注释字段的类/模块时,如何像函数一样获取注释?
class Test:
def __init__(self):
self.x : int
t = Test()
现在我需要从
getattr(t,'x')
中获得'int'。当使用带有注释字段的类/模块时,如何像函数一样获取注释?
class Test:
def __init__(self):
self.x : int
t = Test()
getattr(t,'x')
中获得'int'。在基本的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
将在您的示例中为Test
和t
都返回{'x': int}
。dataclasses
模块 简化你的代码,最小化输入即可定义注释和基本功能。对于你的情况来说,简单的替换代码如下:import dataclasses
@dataclasses.dataclass
class Test:
x: int
__init__
),但它仍然比肉眼可见的更加强大。除了为您定义了__init__
(它期望接收一个被注释为int
的x
参数),以及适当的__repr__
和__eq__
,您还可以轻松地定义默认值(只需在注释时分配默认值,或者对于更复杂或可变的情况,分配一个dataclasses.field
),并且可以传递参数给dataclass
,使其生成可排序或不可变实例。
在您的情况下,主要优点是消除了冗余;x
被注释并引用了一次,而不是在类级别上注释一次,然后在初始化期间使用(并可选地再次注释)。
我不确定你是否能轻松地获取self.x
的注释。
假设您的代码如下:
class Test:
def __init__(self):
self.x: int = None
t = Test()
我尝试在Test
和t
中查找__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'>}
如果你想要检查mypy
中self.x
的类型,可以参考ruohola提供的答案。
请注意,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
mypy
中没有被检测出来(至少在__init__
中,但在使用“t.x”时会被检测出来),最有可能的原因是它们没有触发任何相应的__annotations__
。也许你应该在Python / mypy中提出一个问题。 - norok2x: int
是一个(未定义的)类属性,而 self.x
则创建了一个同名的实例属性。 - chepnermypy
会因为多个注释而感到困惑,并选择忽略 self.x
的那一个。 - norok2x: int
是一个(未定义的)实例属性注释;即使在类作用域中使用x: int = 0
表示具有默认值的实例属性(尽管它创建了一个类属性,但意图是像实例变量一样使用它)。只有使用typing.ClassVar
进行注释的类属性表达类属性意图。有关详细信息,请参见我的答案。 - ShadowRangermypy
,则可以使用 reveal_type()
来检查任何表达式的类型注释。请注意,此函数仅在运行 mypy
时可用,而在正常运行时不可用。
我还使用 typing.TYPE_CHECKING
,以避免在正常运行文件时出现错误,因为这个特殊的常量只被第三方类型检查器假定为 True
。
test.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
sys.modules
检查,这样我就可以始终拥有相同的代码,并且在正常运行文件时无需删除任何内容。 - ruohola
type()
函数。也许您可以解释一下为什么它不适合您。 - norok2x
是实例属性,在类Test
中并不存在,而是存在于该类的实例中。 - Klaus D.