为了账号安全,请及时绑定邮箱和手机立即绑定

松散后期绑定与严格后期绑定

松散后期绑定与严格后期绑定

白猪掌柜的 2023-05-09 14:44:51
在阅读 Python 的执行模型文档时,我意识到 Python 的自由变量似乎没有严格的后期绑定属性,即任何代码块中出现的名称绑定都可以用于名称解析。确实,执行:def f():    return xdef g():    x = 0    return f()print(g())提出:NameError: name 'x' is not defined它们具有松散的后期绑定属性,其中只有出现在引入自由变量的代码块的外部代码块中的名称绑定才能用于名称解析。确实在执行def f():    return xx = 0print(f())印刷:0与严格的后期绑定属性相比,松散的后期绑定属性有哪些优点和缺点?
查看完整描述

2 回答

?
慕标5832272

TA贡献1966条经验 获得超4个赞

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


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


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


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


def adder(base=0):  # factory function returns a new, parameterised function

    def add(x):

        return base + x  # inner function is implicitly parameterised by base

    return add

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


这种静态作用域的名称解析的人工制品是UnboundLocalError :名称可能在本地作用域但尚未在本地分配。即使在某处为名称分配了一些值,静态作用域也禁止访问它。


>>> some_name = 42

>>> def ask():

...     print("the answer is", some_name)

...     some_name = 13

...

>>> ask()

UnboundLocalError: local variable 'some_name' referenced before assignment

存在各种方法来规避这一点,但它们都归结为程序员必须明确定义如何解析名称。


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


Python 本机提供了threading.local将变量上下文化到每个调用堆栈的功能。类似地,contextvars允许明确地将变量置于上下文中——这对于例如async回避常规调用堆栈的代码很有用。线程的原始动态范围可以构建为线程本地的文字范围堆栈:


import contextlib

import threading



class DynamicScope(threading.local):  # instance data is local to each thread

    """Dynamic scope that supports assignment via a context manager"""

    def __init__(self):

        super().__setattr__('_scopes', [])  # keep stack of scopes


    @contextlib.contextmanager  # a context enforces pairs of set/unset operations

    def assign(self, **names):

        self._scopes.append(names)  # push new assignments to stack

        yield self                  # suspend to allow calling other functions

        self._scopes.pop()          # clear new assignments from stack


    def __getattr__(self, item):

        for sub_scope in reversed(self._scopes):  # linearly search through scopes

            try:

                return sub_scope[item]

            except KeyError:

                pass

        raise NameError(f"name {item!r} not dynamically defined")


    def __setattr__(self, key, value):

        raise TypeError(f'{self.__class__.__name__!r} does not support assignment')

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


scope = DynamicScope()


def print_answer():

    print(scope.answer)  # read from scope and hope something is assigned


def guess_answer():

    # assign to scope before calling function that uses the scope

    with scope.assign(answer=42):

        print_answer()


with scope.assign(answer=13):

    print_answer()  # 13

    guess_answer()  # 42

    print_answer()  # 13

print_answer()      # NameError: name 'answer' not dynamically defined


查看完整回答
反对 回复 2023-05-09
?
江户川乱折腾

TA贡献1851条经验 获得超5个赞

静态(早期)和动态(晚期)绑定:

绑定是指将程序文本中的名称与它们所指的存储位置相关联。在静态绑定中,这种关联是在构建时预先确定的。使用动态绑定,此关联直到运行时才确定。

动态绑定是发生在 Python 中的绑定。这意味着 Python 解释器仅在代码运行时才进行绑定。例如 -

>>> if False:
...     x  # This line never runs, so no error is raised
... else:
...     1 + 2
...3>>>

动态绑定的优点

  • 动态类型绑定的主要优点是灵活性。编写通用代码更容易。

    • Ex - 使用动态类型绑定的语言处理数据列表的程序可以编写为通用程序。

动态绑定的缺点

  • 编译器的错误检测能力减弱。编译器可能捕获的一些错误。

  • 运行时的相当大的开销。


查看完整回答
反对 回复 2023-05-09
  • 2 回答
  • 0 关注
  • 114 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信