松散后期绑定与严格后期绑定
Loose late binding v. strict late binding
在阅读 Python 的 Execution model 文档时,我意识到 Python 的自由变量似乎没有严格的 后期绑定 属性 其中出现在 any 代码块中的名称绑定可用于名称解析。的确,正在执行:
def f():
return x
def g():
x = 0
return f()
print(g())
加注:
NameError: name 'x' is not defined
他们有相当宽松的后期绑定 属性,其中只有名称绑定出现在 outer 代码块中引入自由变量的代码块可用于名称解析。确实在执行
def f():
return x
x = 0
print(f())
打印:
0
与严格的后期绑定 属性 相比,松散的后期绑定 属性 有哪些优缺点?
静态(早期)和动态(晚期)绑定:
绑定是指将程序文本中的名称与它们所指的存储位置相关联。在静态绑定中,这种关联是在构建时预先确定的。使用动态绑定,此关联直到 run-time.
才确定
动态绑定是发生在 Python 中的绑定。这意味着 Python 解释器仅在代码运行时才进行绑定。例如-
>>> if False:
... x # This line never runs, so no error is raised
... else:
... 1 + 2
...
3
>>>
动态绑定的优点
- 动态类型绑定的主要优点是灵活性。编写通用代码更容易。
- Ex - 使用动态类型绑定的语言处理数据列表的程序可以编写为通用程序。
动态绑定的缺点
- 编译器的错误检测能力减弱。编译器可能捕获的一些错误。
- 运行时的大量开销。
这通常被称为dynamic scoping and static scoping。粗略地说,动态作用域通过调用嵌套确定作用域,静态作用域通过声明嵌套.
确定作用域
一般来说,对于任何具有调用堆栈的语言,动态作用域都非常容易实现——名称查找只是线性搜索当前堆栈。相比之下,静态作用域更为复杂,需要多个具有自己生命周期的不同作用域。
然而,静态作用域通常更容易理解,因为变量的作用域永远不会改变——名称查找必须解析 一次,并且将始终指向相同的作用域.相比之下,动态作用域更脆弱,调用函数时名称在不同的作用域或没有作用域被解析。
Python的作用域规则主要由PEP 227 introducing nested scoping ("closures") and PEP 3104定义,引入可写嵌套作用域(nonlocal
)。这种静态作用域的主要use-case是允许higher-order函数(“function-producing-function”)自动参数化内部函数;这通常用于回调、装饰器或 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 如何处理静态范围的复杂性。具体来说,作用域在编译时解析 一次——此后每个名称都严格是全局的、非本地的或本地的。在 return 中, 静态作用域允许优化变量访问 – 从 a fast array of locals, an indirecting array of closure cells 或慢速全局字典中读取变量。
这种静态作用域名称解析的产物是 UnboundLocalError
:名称可能在本地 作用域 但尚未 分配 本地。即使有 some 值分配给名称 somewhere,静态作用域禁止访问它。
>>> 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
to contextualise a variable to each call stack. Similarly, 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
ed 在有限的时间内。分配的名称在调用的函数中自动可见。
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
在阅读 Python 的 Execution model 文档时,我意识到 Python 的自由变量似乎没有严格的 后期绑定 属性 其中出现在 any 代码块中的名称绑定可用于名称解析。的确,正在执行:
def f():
return x
def g():
x = 0
return f()
print(g())
加注:
NameError: name 'x' is not defined
他们有相当宽松的后期绑定 属性,其中只有名称绑定出现在 outer 代码块中引入自由变量的代码块可用于名称解析。确实在执行
def f():
return x
x = 0
print(f())
打印:
0
与严格的后期绑定 属性 相比,松散的后期绑定 属性 有哪些优缺点?
静态(早期)和动态(晚期)绑定:
绑定是指将程序文本中的名称与它们所指的存储位置相关联。在静态绑定中,这种关联是在构建时预先确定的。使用动态绑定,此关联直到 run-time.
才确定动态绑定是发生在 Python 中的绑定。这意味着 Python 解释器仅在代码运行时才进行绑定。例如-
>>> if False:
... x # This line never runs, so no error is raised
... else:
... 1 + 2
...
3
>>>
动态绑定的优点
- 动态类型绑定的主要优点是灵活性。编写通用代码更容易。
- Ex - 使用动态类型绑定的语言处理数据列表的程序可以编写为通用程序。
动态绑定的缺点
- 编译器的错误检测能力减弱。编译器可能捕获的一些错误。
- 运行时的大量开销。
这通常被称为dynamic scoping and static scoping。粗略地说,动态作用域通过调用嵌套确定作用域,静态作用域通过声明嵌套.
确定作用域一般来说,对于任何具有调用堆栈的语言,动态作用域都非常容易实现——名称查找只是线性搜索当前堆栈。相比之下,静态作用域更为复杂,需要多个具有自己生命周期的不同作用域。
然而,静态作用域通常更容易理解,因为变量的作用域永远不会改变——名称查找必须解析 一次,并且将始终指向相同的作用域.相比之下,动态作用域更脆弱,调用函数时名称在不同的作用域或没有作用域被解析。
Python的作用域规则主要由PEP 227 introducing nested scoping ("closures") and PEP 3104定义,引入可写嵌套作用域(nonlocal
)。这种静态作用域的主要use-case是允许higher-order函数(“function-producing-function”)自动参数化内部函数;这通常用于回调、装饰器或 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 如何处理静态范围的复杂性。具体来说,作用域在编译时解析 一次——此后每个名称都严格是全局的、非本地的或本地的。在 return 中, 静态作用域允许优化变量访问 – 从 a fast array of locals, an indirecting array of closure cells 或慢速全局字典中读取变量。
这种静态作用域名称解析的产物是 UnboundLocalError
:名称可能在本地 作用域 但尚未 分配 本地。即使有 some 值分配给名称 somewhere,静态作用域禁止访问它。
>>> 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
to contextualise a variable to each call stack. Similarly, 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
ed 在有限的时间内。分配的名称在调用的函数中自动可见。
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