宽松的后期绑定v.严格的后期绑定 [英] Loose late binding v. strict late binding

查看:54
本文介绍了宽松的后期绑定v.严格的后期绑定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在阅读Python的执行模型文档时,我意识到Python的自由变量似乎没有严格的 late绑定属性,在 any 代码块中发生的名称绑定可以用于名称解析.确实,执行:

While reading Python’s Execution model documentation, I realized that Python’s free variables do not seem to have a strict late binding property where a name binding occurring in any code block can be used for name resolution. Indeed, executing:

def f():
    return x

def g():
    x = 0
    return f()

print(g())

提高:

NameError: name 'x' is not defined

它们具有相当宽松的 late绑定属性,在该属性中,仅在引入了free变量的代码块的外部代码块中发生名称绑定,才能用于名称解析.确实执行

They have rather a loose late binding property where only a name binding occurring in an outer code block of the code block introducing the free variable can be used for name resolution. Indeed executing

def f():
    return x

x = 0
print(f())

打印:

0

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

What are the benefits and drawbacks of the loose late binding property compared to the strict late binding property?

推荐答案

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

This is generally known as dynamic scoping and static scoping. Roughly speaking, dynamic scoping determines scope by call nesting and static scoping determines scope by declaration nesting.

通常,对于任何具有调用堆栈的语言,动态范围都很容易实现–名称查找只是线性地搜索当前堆栈.相比之下,静态范围界定更为复杂,需要具有各自生命周期的多个不同范围.

In general, dynamic scoping is very easy to implement for any language with a call stack – a name lookup simply searches the current stack linearly. In contrast, static scoping is more complex, requiring several distinct scopes with their own lifetime.

但是,静态作用域通常更容易理解,因为变量的范围永远不会改变-名称查找必须一次解析,并且始终指向相同的范围.相比之下,动态作用域更加脆弱,调用函数时,名称在不同范围内解析或没有作用域.

However, static scoping is generally easier to understand, since the scope of a variable never changes – a name lookup has to be resolved once and will always point to the same scope. In contrast, dynamic scoping is more brittle, with names being resolved in different or no scope when calling a function.

Python的作用域规则主要由 PEP 227 定义,该方法引入了嵌套作用域("closures")和 PEP 3104 引入了可写的嵌套作用域(代码>非本地).这种静态作用域的主要用例是允许高阶函数(函数产生函数")自动参数化内部函数.通常用于回调,装饰器或工厂函数.

Python's scoping rules are mainly defined by PEP 227 introducing nested scoping ("closures") and PEP 3104 introducing writable nested scoping (nonlocal). The primary use-case of such static scoping is to allow higher-order functions ("function-producing-function") to automatically parameterise inner functions; this is commonly used for callbacks, decorators or factory-functions.

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如何处理静态作用域的复杂性.具体来说,范围是在编译时一次解析的–之后,每个名称都严格地是全局,非本地或本地.作为回报,静态作用域可以优化变量访问 –可以从快速的本地变量数组闭包单元格的间接数组或慢速全局字典.

Both PEPs codify how Python handles the complications of static scoping. In specific, scope is resolved once at compile time – every name is thereafter strictly either global, nonlocal or local. In return, static scoping allows to optimise variable access – variables are read either from a fast array of locals, an indirecting array of closure cells, or a slow global dictionary.

此静态作用域名称解析的伪像是 UnboundLocalError :名称可能在本地作用域,但尚未在本地分配.即使为 somewhere 名称分配了 some 值,静态作用域也禁止访问它.

An artefact of this statically scoped name resolution is UnboundLocalError : a name may be scoped locally but not yet assigned locally. Even though there is some value assigned to the name somewhere, static scoping forbids accessing it.

>>> some_name = 42
>>> def ask():
...     print("the answer is", some_name)
...     some_name = 13
...
>>> ask()
UnboundLocalError: local variable 'some_name' referenced before assignment

存在各种规避此问题的方法,但是所有这些都归因于程序员必须明确定义如何解析名称.

Various means exist to circumvent this, but they all come down to the programmer having to explicitly define how to resolve a name.

虽然Python本身没有实现动态作用域,但可以轻松地对其进行仿真.由于动态作用域与每个调用堆栈的作用域堆栈相同,因此可以明确实现.

While Python does not natively implement dynamic scoping, it can in be easily emulated. Since dynamic scoping is identical to a stack of scopes for each stack of calls, this can be implemented explicitly.

Python原生提供 threading.local 将变量关联到每个调用堆栈.同样, contextvars 允许将变量明确地上下文化–这对于例如有用 async 代码避开了常规调用堆栈.可以将线程的幼稚动态作用域构建为线程本地的文字作用域堆栈:

Python natively provides threading.local to contextualise a variable to each call stack. Similarly, contextvars allows to explicitly contextualise a variable – this is useful for e.g. async code which sidesteps the regular call stack. A naive dynamic scope for threads can be built as a literal scope stack that is thread local:

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

这允许全局定义动态范围,可以在限定的时间内对名称进行分配.分配的名称会在调用的函数中自动显示.

This allows to globally define a dynamic scope, to which a name can be assigned for a restricted duration. Assigned names are automatically visible in called functions.

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

这篇关于宽松的后期绑定v.严格的后期绑定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆