在 Python 中实现块作用域
Implementing block scoping in Python
为了好玩,我正在尝试在 Python 中实现类似于 block scoping 的东西。这就是我的结论:
import inspect
class Scope:
def __init__(self, *args):
self._start = inspect.currentframe().f_back.f_locals.copy()
print(self._start)
def __enter__(self, *args):
pass
def __exit__(self, *args):
for name in inspect.currentframe().f_back.f_locals.copy():
if name not in self._start:
print(f"deleting {name}")
# print(globals())
# del globals()[name]
del inspect.currentframe().f_back.f_locals[name] # this doesn't do anything
print(inspect.currentframe().f_back.f_locals) # no change
def compute(foo):
with Scope():
bar = foo ** 2
print(bar)
return bar # this should fail
compute(5)
很遗憾,当前帧以外的帧似乎无法修改。 (可能有充分的理由?:P)。我也试过修改 globals
dict 并且它有效,但仅适用于创建的词法范围在模块级别的情况。我希望我的解决方案在函数内部工作,等等。
我试过使用 with
API 来模仿其他语言的作用域。我很高兴听到使用可能有效的不同技术的解决方案。
TLDR:我想要某种构造,在退出时自动销毁绑定在其中的所有局部变量。
帧中的局部变量不能写入 - 但可以读取。
这样做的主要原因是,尽管任何范围内的变量都可以表示为字典,但为了提高效率,某些时候变量开始以不同的方式存储在与框架本身相关联的插槽中,但在 Python 代码中不可见.这样,读取和写入变量的操作就不会通过字典间接进行,并且使用了一个简单的局部变量线性索引。
每当调用 locals()
或框架的 .f_locals
成员时,cPython 会透明地将局部变量中的当前值复制到生成的字典中。
此外,局部变量的另一个存储称为“快速存储”意味着 space 在编译时为代码对象的所有局部变量保留 - 并且函数被编译为单个代码对象.这意味着即使变量仅在块内使用,它也会在函数范围内存在。
这是 Python 很长一段时间以来的行为,但最近在 PEP 558 中有所记录:https://www.python.org/dev/peps/pep-0558/
对于全局变量没有这样的机制,影响globals
或frame.f_globals
返回的字典会改变相应的变量——这就是为什么你的想法适用于全局变量。
语言中最接近的机制是在 except
块的末尾 - 在那个点 Python 将删除异常变量(以避免引用异常中的对象本身,通常不会在 except 块之后重用。
如果你想要一些真正有用的东西,而不仅仅是玩具,我用过 2 或 3 次的一件事是拥有一个可以在 with
块内更改的上下文对象,并且将在退出时恢复其值 - 以便恢复以前的值。这个对象通常是一个“上下文”的东西——例如,带有前景和线宽等参数的绘图 API,或者小数位数和小数的舍入策略。
from collections import ChainMap
class Context:
def __init__(self):
self._stack = [{}]
def __enter__(self):
self._stack.append({})
def __exit__(self, *args, **kw):
self._stack.pop()
def __getattr__(self, name):
if name.startswith("_"):
return super().__getattr__(self, name)
return ChainMap(*reversed(self._stack))[name]
def __setattr__(self, name, value):
if name.startswith("_"):
return super().__setattr__(name, value)
self._stack[-1][name] = value
def __delattr__(self, name):
if name.startswith("_"):
return super().__deltattr__(name)
del self._stack[1][name]
这是在 ipython 控制台中运行的代码:
In [137]: ctx = Context()
In [138]: ctx.color = "red"
In [139]: ctx.color
Out[139]: 'red'
In [140]: with ctx:
...: ctx.color = "blue"
...: print(ctx.color)
...:
blue
In [141]: ctx.color
Out[141]: 'red'
(对于真正好的“生产质量”上下文 class,您必须考虑 thead 安全和 asyncio 任务安全 - 但以上是基本思想)
为了好玩,我正在尝试在 Python 中实现类似于 block scoping 的东西。这就是我的结论:
import inspect
class Scope:
def __init__(self, *args):
self._start = inspect.currentframe().f_back.f_locals.copy()
print(self._start)
def __enter__(self, *args):
pass
def __exit__(self, *args):
for name in inspect.currentframe().f_back.f_locals.copy():
if name not in self._start:
print(f"deleting {name}")
# print(globals())
# del globals()[name]
del inspect.currentframe().f_back.f_locals[name] # this doesn't do anything
print(inspect.currentframe().f_back.f_locals) # no change
def compute(foo):
with Scope():
bar = foo ** 2
print(bar)
return bar # this should fail
compute(5)
很遗憾,当前帧以外的帧似乎无法修改。 (可能有充分的理由?:P)。我也试过修改 globals
dict 并且它有效,但仅适用于创建的词法范围在模块级别的情况。我希望我的解决方案在函数内部工作,等等。
我试过使用 with
API 来模仿其他语言的作用域。我很高兴听到使用可能有效的不同技术的解决方案。
TLDR:我想要某种构造,在退出时自动销毁绑定在其中的所有局部变量。
帧中的局部变量不能写入 - 但可以读取。 这样做的主要原因是,尽管任何范围内的变量都可以表示为字典,但为了提高效率,某些时候变量开始以不同的方式存储在与框架本身相关联的插槽中,但在 Python 代码中不可见.这样,读取和写入变量的操作就不会通过字典间接进行,并且使用了一个简单的局部变量线性索引。
每当调用 locals()
或框架的 .f_locals
成员时,cPython 会透明地将局部变量中的当前值复制到生成的字典中。
此外,局部变量的另一个存储称为“快速存储”意味着 space 在编译时为代码对象的所有局部变量保留 - 并且函数被编译为单个代码对象.这意味着即使变量仅在块内使用,它也会在函数范围内存在。
这是 Python 很长一段时间以来的行为,但最近在 PEP 558 中有所记录:https://www.python.org/dev/peps/pep-0558/
对于全局变量没有这样的机制,影响globals
或frame.f_globals
返回的字典会改变相应的变量——这就是为什么你的想法适用于全局变量。
语言中最接近的机制是在 except
块的末尾 - 在那个点 Python 将删除异常变量(以避免引用异常中的对象本身,通常不会在 except 块之后重用。
如果你想要一些真正有用的东西,而不仅仅是玩具,我用过 2 或 3 次的一件事是拥有一个可以在 with
块内更改的上下文对象,并且将在退出时恢复其值 - 以便恢复以前的值。这个对象通常是一个“上下文”的东西——例如,带有前景和线宽等参数的绘图 API,或者小数位数和小数的舍入策略。
from collections import ChainMap
class Context:
def __init__(self):
self._stack = [{}]
def __enter__(self):
self._stack.append({})
def __exit__(self, *args, **kw):
self._stack.pop()
def __getattr__(self, name):
if name.startswith("_"):
return super().__getattr__(self, name)
return ChainMap(*reversed(self._stack))[name]
def __setattr__(self, name, value):
if name.startswith("_"):
return super().__setattr__(name, value)
self._stack[-1][name] = value
def __delattr__(self, name):
if name.startswith("_"):
return super().__deltattr__(name)
del self._stack[1][name]
这是在 ipython 控制台中运行的代码:
In [137]: ctx = Context()
In [138]: ctx.color = "red"
In [139]: ctx.color
Out[139]: 'red'
In [140]: with ctx:
...: ctx.color = "blue"
...: print(ctx.color)
...:
blue
In [141]: ctx.color
Out[141]: 'red'
(对于真正好的“生产质量”上下文 class,您必须考虑 thead 安全和 asyncio 任务安全 - 但以上是基本思想)