Source code for varscope
"""
A simple Python library for creating local scopes.
This module consists of a single function
:func:`scope` that creates :class:`Scope` context manager, to be used
with Python's :code:`with` statement:
.. code:: python
# Scope A
with scope():
# Scope B (includes A)
# Scope A
Scroll below for usage examples.
"""
import ctypes
import inspect
from typing import Set
[docs]class Scope:
"""
Context manager for encapsulating variables in a scope.
See :func:`scope` for a detailed description.
References
----------
https://pydev.blogspot.com/2014/02/changing-locals-of-frame-frameflocals.html
"""
def __init__(self, *names: str) -> None:
self._delete: Set[str] = set(*names)
self._keep: Set[str] = set()
def __enter__(self) -> "Scope":
self.__initial_scope = inspect.currentframe().f_back.f_locals.copy() # type: ignore[union-attr]
return self
def __exit__(self, *exc) -> None:
current_frame = inspect.currentframe().f_back # type: ignore[union-attr]
current_scope = current_frame.f_locals.copy() # type: ignore[union-attr]
initial_scope = self.__initial_scope
f_locals = current_frame.f_locals # type: ignore[union-attr]
for key in current_scope.keys():
if key in self._keep:
continue
elif key in initial_scope:
f_locals[key] = initial_scope[key] # type: ignore[union-attr]
else:
f_locals.pop(key) # type: ignore[union-attr]
for key in self._delete:
if key not in self._keep:
f_locals.pop(key) # type: ignore[union-attr]
ctypes.pythonapi.PyFrame_LocalsToFast(
ctypes.py_object(current_frame), ctypes.c_int(1)
)
del current_frame
[docs] def keep(self, *names: str) -> None:
"""
Tells the context manager to keep specific variable names
after the scope was exited.
:param names: Variable names to be moved outside the scope.
Examples
--------
>>> from varscope import scope
>>> with scope() as s:
... a = 1
... b = 2
... s.keep("a")
...
>>> a
1
>>> b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined
"""
self._keep.update(names)
[docs]def scope(*names: str) -> Scope:
"""
Returns a context manager to encapsulate all the
variables, making them inaccessible afterward.
Variables that are created in a given scope will not
be accessible outside of this scope.
Optionally, you can specify variable names from outside the scope
that must be moved inside. Upon exiting the scope, they will be deleted.
:param names: Variable names to be moved inside the scope,
as they were defined there.
Examples
--------
>>> from varscope import scope
>>> a = 1
>>> with scope():
... b = 2
...
>>> a
1
>>> b
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'b' is not defined
Variables that are redefined will get their original
value back, as of when the scope was entered.
>>> from varscope import scope
>>> a = 1
>>> with scope():
... a = 2
... print(a)
2
>>> a
1
Mutating an object will still keep its effects outside
the scope.
>>> from varscope import scope
>>> d = {}
>>> with scope():
... d["a"] = 1
...
>>> d
{'a': 1}
You can also specify variables (by name) that must be moved
inside the scope, and deleted afterward.
>>> from varscope import scope
>>> a = 1
>>> with scope("a"):
... print(a)
...
1
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
"""
return Scope(*names)