from asyncio import run, Task, ALL_COMPLETED
a: int = 1
b: str = "foo"
c: float = 3.14
assert all(
name in __annotations__
for name, value in globals().items()
if not (name.startswith("_") or name.isupper() or callable(value))
)
但是你仍然必须在每个模块的底部坚持这一行。 你可以把这个逻辑分解成这样的函数:
from typing import Any
def ensure_everything_annotated(
namespace: dict[str, Any],
annotations: dict[str, Any] | None = None,
) -> None:
if annotations is None:
annotations = namespace["__annotations__"]
assert all(
name in annotations
for name, value in namespace.items()
if not (name.startswith("_") or name.isupper() or callable(value))
)
然后你可以像这样在模块中使用它:
a: int = 1
b: str = "foo"
c = 3.14
...
ensure_everything_annotated(globals()) # error
对于这样的类:(类有自己的__annotations__)
class Foo:
spam: str
eggs: int = -1
beans = 420.69
ensure_everything_annotated(Foo.__dict__) # error
from typing import Any
def ensure_everything_annotated(
namespace: dict[str, Any],
annotations: dict[str, Any] | None = None,
) -> None:
if annotations is None:
annotations = namespace["__annotations__"]
for name, value in namespace.items():
if (
name.startswith("_") # exclude protected/private
or name.isupper() # exclude constants
or callable(value) # exclude functions/classes
or hasattr(value, "__get__") # exclude descriptors
):
continue
if name not in annotations:
raise TypeError(f"{name} is not annotated")
1条答案
按热度按时间yfjy0ee71#
在模块级别,可以将全局名称空间与模块的
__annotations__
字典进行比较。例如,删除
c
的: float
注解,您会得到一个错误。这不包括带下划线的名称,因为有很多立即保留的名称,但当然可以更细粒度。
问题是,一旦从其他模块导入名称,这种方法就会失效,因为它们显然不会出现在导入模块的
__annotations__
中。假设你从不导入“普通的旧变量”,而只导入类、函数或常量,你可以通过额外地从检查中排除可调用类型或全大写名称来解决这个问题。
但是你仍然必须在每个模块的底部坚持这一行。
你可以把这个逻辑分解成这样的函数:
然后你可以像这样在模块中使用它:
对于这样的类:(类有自己的
__annotations__
)编辑:我刚想起来描述符(例如类命名空间中的
property
)也会导致失败,因为描述符本身可能无法调用,所以你也必须考虑到这一点。例如,您还可以检查值上是否存在__get__
方法。下面是一个稍微复杂一点的实现:
但我认为这里的关键要点是,如何使这样的检查**强大 * 并不立即显而易见。