python 要求变量在第一次使用时或使用前声明

r1zhe5dt  于 2023-05-05  发布在  Python
关注(0)|答案(1)|浏览(139)

Python总是允许你用你用来赋值给现有变量的相同语法来赋值给一个新变量。因此,如果你拼错了一个变量名(或者忘记说globalnonlocal,当你想引用一个预先存在的但非局部的变量时),你会默默地创建一个新变量。
我知道Python在过去几年中为类型注解增加了更多的语法支持,并且有各种工具使用这些来对Python代码进行静态检查。但我对可能性的细节一无所知。你能自动检查所有的变量都是用注解声明的吗,这样错误地创建的变量就变成了类型检查器的错误?

yfjy0ee7

yfjy0ee71#

在模块级别,可以将全局名称空间与模块的__annotations__字典进行比较。

a: int = 1
b: str = "foo"
c: float = 3.14

assert all(name in __annotations__ for name in globals() if not name.startswith("_"))

例如,删除c: float注解,您会得到一个错误。
这不包括带下划线的名称,因为有很多立即保留的名称,但当然可以更细粒度。
问题是,一旦从其他模块导入名称,这种方法就会失效,因为它们显然不会出现在导入模块的__annotations__中。
假设你从不导入“普通的旧变量”,而只导入类、函数或常量,你可以通过额外地从检查中排除可调用类型或全大写名称来解决这个问题。

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

编辑:我刚想起来描述符(例如类命名空间中的property)也会导致失败,因为描述符本身可能无法调用,所以你也必须考虑到这一点。例如,您还可以检查值上是否存在__get__方法。

下面是一个稍微复杂一点的实现:

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")

但我认为这里的关键要点是,如何使这样的检查**强大 * 并不立即显而易见。

相关问题