🔐 localscope

https://img.shields.io/static/v1?label=&message=GitHub&color=gray&logo=github https://github.com/tillahoffmann/localscope/actions/workflows/build.yml/badge.svg https://readthedocs.org/projects/localscope/badge/?version=latest https://img.shields.io/pypi/v/localscope.svg

你是否曾因为在函数中意外使用了全局变量而苦苦追寻 bug? 是否曾因为重启 Python 内核后代码崩溃而百思不得其解? localscope 通过限制函数可以访问的变量来提供帮助。

>>> from localscope import localscope
>>>
>>> a = 'hello world'
>>>
>>> @localscope
... def print_a():
...   print(a)
Traceback (most recent call last):
...
localscope.LocalscopeException: `a` is not a permitted global (file "...",
  line 1, in print_a)

有关选项的详尽列表,请参见 Interface 部分,有关更详细的示例,请参见 Motivation and Example

Installation

$ pip install localscope

Interface

localscope.localscope(_func :function|code|[None](https://localscope.readthedocs.io/en/latest/<https:/docs.python.org/3/library/constants.html#None> "\(in Python v3.13\)")=None_, _*_ , _predicate :[Callable](https://localscope.readthedocs.io/en/latest/<https:/docs.python.org/3/library/typing.html#typing.Callable> "\(in Python v3.13\)")|[None](https://localscope.readthedocs.io/en/latest/<https:/docs.python.org/3/library/constants.html#None> "\(in Python v3.13\)")=None_, _allowed :[Iterable](https://localscope.readthedocs.io/en/latest/<https:/docs.python.org/3/library/typing.html#typing.Iterable> "\(in Python v3.13\)")[[str](https://localscope.readthedocs.io/en/latest/<https:/docs.python.org/3/library/stdtypes.html#str> "\(in Python v3.13\)")]|[str](https://localscope.readthedocs.io/en/latest/<https:/docs.python.org/3/library/stdtypes.html#str> "\(in Python v3.13\)")]|[None](https://localscope.readthedocs.io/en/latest/<https:/docs.python.org/3/library/constants.html#None> "\(in Python v3.13\)")=None_, _allow_closure :[bool](https://localscope.readthedocs.io/en/latest/<https:/docs.python.org/3/library/functions.html#bool> "\(in Python v3.13\)")=False_)

限制 callable 对象的作用域为局部变量,以避免意外的信息流入。

参数:

localscope.mfc

允许 m odules、f unctions 和 c lasses 进入局部作用域的装饰器。

示例

展示 localscope 功能的基本示例。

>>> from localscope import localscope
>>>
>>> a = 'hello world'
>>>
>>> @localscope
... def print_a():
...   print(a)
Traceback (most recent call last):
...
localscope.LocalscopeException: `a` is not a permitted global (file "...",
  line 1, in print_a)

可以通过提供允许的变量名迭代器或空格分隔的允许的变量名字符串来扩展函数的作用域。

>>> a = 'hello world'
>>>
>>> @localscope(allowed=['a'])
... def print_a():
...   print(a)
>>>
>>> print_a()
hello world

可以使用 predicate 关键字参数来控制允许哪些值进入作用域(默认情况下,只有模块可以在函数中使用)。

>>> a = 'hello world'
>>>
>>> @localscope(predicate=lambda x: isinstance(x, str))
... def print_a():
...   print(a)
>>>
>>> print_a()
hello world

Localscope 默认情况下很严格,但可以使用 localscope.mfc 允许模块、函数和类进入函数作用域:这是在 notebook 中的常见用例。

>>> class MyClass:
...   pass
>>>
>>> @localscope.mfc
... def create_instance():
...   return MyClass()
>>>
>>> create_instance()
<MyClass object at 0x...>

注意

localscope 装饰器在声明时分析装饰的函数,因为静态分析对性能的影响最小且更容易实现。 这也确保了 localscope 不会以任何方式影响代码的运行。

>>> def my_func():
...   pass
>>>
>>> my_func is localscope(my_func)
True

Motivation and Example

交互式 Python 会话是分析数据、生成可视化和训练机器学习模型的出色工具。 但是,交互性质允许全局变量意外泄漏到函数的作用域中,从而导致意外的行为。 例如,假设你正在评估两个数字列表之间的均方误差,其中包括比例因子 sigma

>>> sigma = 7
>>> # [other notebook cells and bits of code]
>>> xs = [1, 2, 3]
>>> ys = [4, 5, 6]
>>> mse = sum(((x - y) / sigma) ** 2 for x, y in zip(xs, ys))
>>> mse
0.55102...

一切正常,你可以将代码打包在一个函数中以供以后使用,但忘记了 notebook 中先前引入的比例因子。

>>> def evaluate_mse(xs, ys): # missing argument sigma
...   return sum(((x - y) / sigma) ** 2 for x, y in zip(xs, ys))
>>>
>>> mse = evaluate_mse(xs, ys)
>>> mse
0.55102...

变量 sigma 是从全局作用域获得的,并且代码执行没有任何问题。 但是输出受到更改 sigma 值的影响。

>>> sigma = 13
>>> evaluate_mse(xs, ys)
0.15976...

这个例子可能看起来很牵强。 但是,从全局作用域到局部函数作用域的意外信息泄漏通常会导致无法重现的结果、花费数小时进行调试以及多次重启内核以识别问题的根源。 Localscope 通过限制允许的作用域来解决此问题。

>>> @localscope
... def evaluate_mse(xs, ys): # missing argument sigma
...   return sum(((x - y) / sigma) ** 2 for x, y in zip(xs, ys))
Traceback (most recent call last):
...
localscope.LocalscopeException: `sigma` is not a permitted global (file "...",
  line 3, in <genexpr>)