Skip to content

globals_dict

GlobalsDict

Bases: Dict[str, object]

A custom dict that is meant to be accessed in a particular way, in order to record getitems. It is used for setting as the globals when executing some code, so we can try to understand which globals were accessed.

It is meant to be used like:

  1. Instantiate it empty like GlobalsDict()
  2. Call setup_globals(d) to update it with the input globals
  3. Execute some code that uses it as globals, which will call __setitem__ as well as our custom __getitem__.
  4. Call teardown_globals() which will return the Result, containing the a record of all the original globals that were accessed and any new globals that were updated or added.

We cannot overload the __setitem__ method, since Python will not respect it for custom globals, but we can overload the getitem method.

See https://stackoverflow.com/a/12185315/907060 which refers to https://bugs.python.org/issue14385

Source code in lineapy/execution/globals_dict.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class GlobalsDict(Dict[str, object]):
    """
    A custom dict that is meant to be accessed in a particular way, in order
    to record getitems. It is used for setting as the globals when executing
    some code, so we can try to understand which globals were accessed.

    It is meant to be used like:

    1. Instantiate it empty like `GlobalsDict()`
    2. Call `setup_globals(d)` to update it with the input globals
    3. Execute some code that uses it as globals, which will call `__setitem__`
       as well as our custom `__getitem__`.
    4. Call `teardown_globals()` which will return the `Result`, containing the
       a record of all the original globals that were accessed and any new
       globals that were updated or added.

    We cannot overload the `__setitem__` method, since Python will not respect
    it for custom globals, but we can overload the __getitem__ method.

    See https://stackoverflow.com/a/12185315/907060
    which refers to https://bugs.python.org/issue14385
    """

    def __init__(self):
        self._state: Optional[State] = None
        super().__init__()

    def __getitem__(self, k):
        v = super().__getitem__(k)
        if not self._state:
            raise RuntimeError("GlobalsDict not setup")
        self._state.process_getitem(k, v)
        return v

    def setup_globals(self, inputs: Dict[str, object]) -> None:
        self._state = State(inputs)
        self.update(inputs)
        self["__builtins__"] = builtins

    def teardown_globals(self) -> GlobalsDictResult:
        if not self._state:
            raise RuntimeError("GlobalsDict not setup")
        state = self._state
        # Calculate what globals have changed or have been added. Compare by pointer,
        # not by value, since we want to see if the global variable has been re-assigned
        # not if the value has been mutated
        changed_globals = {
            k: v
            for k, v, in self.items()
            if k != "__builtins__"
            and (
                # The global was changed if it is new, i.e. was not in the our variables
                k not in state.inputs
                # Or if it is different
                or state.inputs[k] is not v
            )
        }

        self._state = None
        self.clear()

        return GlobalsDictResult(state.accessed_inputs, changed_globals)

State dataclass

Source code in lineapy/execution/globals_dict.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@dataclass
class State:
    # The mapping of input globals
    inputs: Dict[str, object]

    # A subset of the input globals, containing only the keys that were accessed
    # from it
    accessed_inputs: List[str] = field(default_factory=list)

    def process_getitem(self, k: str, v: object) -> None:
        """
        If we haven't recorded this key and its value is the same as the value
        in the input globals (meaning we haven't overwritten it), then record
        it as a getitem.
        """
        if (
            k != "__builtins__"
            and k not in self.accessed_inputs
            and k in self.inputs
            and self.inputs[k] is v
        ):
            self.accessed_inputs.append(k)

process_getitem(k, v)

If we haven't recorded this key and its value is the same as the value in the input globals (meaning we haven't overwritten it), then record it as a getitem.

Source code in lineapy/execution/globals_dict.py
81
82
83
84
85
86
87
88
89
90
91
92
93
def process_getitem(self, k: str, v: object) -> None:
    """
    If we haven't recorded this key and its value is the same as the value
    in the input globals (meaning we haven't overwritten it), then record
    it as a getitem.
    """
    if (
        k != "__builtins__"
        and k not in self.accessed_inputs
        and k in self.inputs
        and self.inputs[k] is v
    ):
        self.accessed_inputs.append(k)

Was this helpful?

Help us improve docs with your feedback!