宽松的后期绑定与严格的后期绑定

Mag*_*ero 0 python language-design late-binding free-variable

在阅读 Python\xe2\x80\x99s执行模型文档时,我意识到 Python\xe2\x80\x99s 自由变量似乎没有严格的后期绑定属性,其中任何代码块中发生的名称绑定都可以用于名称解析。事实上,执行:

\n
def f():\n    return x\n\ndef g():\n    x = 0\n    return f()\n\nprint(g())\n
Run Code Online (Sandbox Code Playgroud)\n

提高:

\n
NameError: name \'x\' is not defined\n
Run Code Online (Sandbox Code Playgroud)\n

它们具有相当松散的后期绑定属性,其中只有在引入自由变量的代码块的外部代码块中发生的名称绑定才能用于名称解析。确实执行

\n
NameError: name \'x\' is not defined\n
Run Code Online (Sandbox Code Playgroud)\n

印刷:

\n
0\n
Run Code Online (Sandbox Code Playgroud)\n

与严格的后期绑定属性相比,松散的后期绑定属性有哪些优点和缺点?

\n

Mis*_*agi 5

这通常称为动态范围静态范围。粗略地说,动态作用域通过调用嵌套确定作用域,静态作用域通过声明嵌套确定作用域。

\n

一般来说,对于任何具有调用堆栈 \xe2\x80\x93 的语言来说,动态作用域都很容易实现,名称查找只需线性搜索当前堆栈。相比之下,静态作用域更为复杂,需要多个具有自己生命周期的不同作用域。

\n

然而,静态作用域通常更容易理解,因为变量的作用域永远不会改变 \xe2\x80\x93 名称查找必须解析一次,并且始终指向相同的作用域。相比之下,动态作用域更脆弱,调用函数时名称会在不同的作用域中解析或没有作用域。

\n
\n

Python 的作用域规则主要由引入嵌套作用域(“闭包”)的PEP 227和引入可写嵌套作用域(“闭包”)的PEP 3104nonlocal定义。这种静态作用域的主要用例是允许高阶函数(“函数生成函数”)自动参数化内部函数;这通常用于回调、装饰器或工厂函数。

\n
def adder(base=0):  # factory function returns a new, parameterised function\n    def add(x):\n        return base + x  # inner function is implicitly parameterised by base\n    return add\n
Run Code Online (Sandbox Code Playgroud)\n

两个 PEP 都规定了 Python 如何处理静态作用域的复杂性。具体来说,范围在编译时解析一次\xe2\x80\x93 此后每个名称都严格是全局的、非本地的或本地的。作为回报,静态作用域允许优化变量访问\xe2\x80\x93 变量可以从快速的本地数组闭包单元的间接数组或慢速的全局字典中读取。

\n

这种静态范围名称解析的一个产物是UnboundLocalError :名称可以在本地范围内,但尚未在本地分配。即使在某处为名称分配了一些值,静态作用域也禁止访问它。

\n
>>> some_name = 42\n>>> def ask():\n...     print("the answer is", some_name)\n...     some_name = 13\n...\n>>> ask()\nUnboundLocalError: local variable \'some_name\' referenced before assignment\n
Run Code Online (Sandbox Code Playgroud)\n

存在多种方法可以避免这种情况,但它们都归结为程序员必须显式定义如何解析名称。

\n
\n

虽然 Python 本身并不实现动态作用域,但可以轻松模拟它。由于动态作用域与每个调用堆栈的作用域堆栈相同,因此可以显式实现。

\n

Python 本身提供了threading.local将变量关联到每个调用堆栈的上下文。类似地,contextvars允许显式地将变量 \xe2\x80\x93 置于上下文中,这对于async避开常规调用堆栈的代码很有用。线程的原生动态作用域可以构建为线程本地的文字作用域堆栈:

\n
import contextlib\nimport threading\n\n\nclass DynamicScope(threading.local):  # instance data is local to each thread\n    """Dynamic scope that supports assignment via a context manager"""\n    def __init__(self):\n        super().__setattr__(\'_scopes\', [])  # keep stack of scopes\n\n    @contextlib.contextmanager  # a context enforces pairs of set/unset operations\n    def assign(self, **names):\n        self._scopes.append(names)  # push new assignments to stack\n        yield self                  # suspend to allow calling other functions\n        self._scopes.pop()          # clear new assignments from stack\n\n    def __getattr__(self, item):\n        for sub_scope in reversed(self._scopes):  # linearly search through scopes\n            try:\n                return sub_scope[item]\n            except KeyError:\n                pass\n        raise NameError(f"name {item!r} not dynamically defined")\n\n    def __setattr__(self, key, value):\n        raise TypeError(f\'{self.__class__.__name__!r} does not support assignment\')\n
Run Code Online (Sandbox Code Playgroud)\n

assign这允许全局定义一个动态范围,可以在有限的时间内编辑名称。分配的名称在被调用的函数中自动可见。

\n
scope = DynamicScope()\n\ndef print_answer():\n    print(scope.answer)  # read from scope and hope something is assigned\n\ndef guess_answer():\n    # assign to scope before calling function that uses the scope\n    with scope.assign(answer=42):\n        print_answer()\n\nwith scope.assign(answer=13):\n    print_answer()  # 13\n    guess_answer()  # 42\n    print_answer()  # 13\nprint_answer()      # NameError: name \'answer\' not dynamically defined\n
Run Code Online (Sandbox Code Playgroud)\n

  • @SkyWalker它是一堆命名空间,所以是一个“字典列表”——我不明白“平面字典”或“字典字典”如何表达这一点。 (2认同)