• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pyta-uoft / pyta / 3671064905

pending completion
3671064905

Pull #864

github

GitHub
<a href="https://github.com/pyta-uoft/pyta/commit/<a class=hub.com/pyta-uoft/pyta/commit/<a class="double-link" href="https://git"><a class=hub.com/pyta-uoft/pyta/commit/<a class="double-link" href="https://git"><a class=hub.com/pyta-uoft/pyta/commit/75c93802b574a2fe7f79211702f80f74eecec446">75c93802b<a href="https://github.com/pyta-uoft/pyta/commit/75c93802b574a2fe7f79211702f80f74eecec446">&quot;&gt;&amp;lt;a href=&amp;quot;https://github.com/pyta-uoft/pyta/commit/&lt;/a&gt;&lt;a class=&quot;double-link&quot; href=&quot;https://github.com/pyta-uoft/pyta/commit/&amp;lt;a class=&amp;quot;double-link&amp;quot; href=&amp;quot;https://git&quot;&gt;&amp;lt;a class=&lt;/a&gt;hub.com/pyta-uoft/pyta/commit/75c93802b574a2fe7f79211702f80f74eecec446&quot;&gt;75c93802b&lt;/a&gt;&lt;a href=&quot;https://github.com/pyta-uoft/pyta/commit/75c93802b574a2fe7f79211702f80f74eecec446&quot;&gt;&amp;lt;a href=&amp;quot;https://github.com/pyta-uoft/pyta/commit/75c93802b574a2fe7f79211702f80f74&lt;/a&gt;eecec446">&quot;&gt;Merge &lt;/a&gt;&lt;a class=&quot;double-link&quot; href=&quot;https://github.com/pyta-uoft/pyta/commit/&lt;a class=&quot;double-link&quot; href=&quot;https://github.com/pyta-uoft/pyta/commit/<a class="double-link" href="https://github.com/pyta-uoft/pyta/commit/a1838db033075c2df70e6a7117d9888b7a857403">a1838db03</a><a href="https://github.com/pyta-uoft/pyta/commit/75c93802b574a2fe7f79211702f80f74eecec446">&amp;quot;&amp;gt;a1838db03&amp;lt;/a&amp;gt;&amp;quot;&amp;gt;a1838db03&amp;lt;/a&amp;gt;&amp;lt;a href=&amp;quot;https://github.com/pyta-uoft/pyta/commit/75c93802b574a2fe7f79211702f80f74eecec&lt;/a&gt;446&quot;&gt; into &lt;/a&gt;&lt;a class=&quot;double-link&quot; href=&quot;https://github.com/pyta-uoft/pyta/commit/<a class="double-link" href="https://github.com/pyta-uoft/pyta/commit/fa8fecd14">fa8fecd14">fa8fecd14</a>
Pull Request #864: Extend accumulation table loop detection

7 of 7 new or added lines in 1 file covered. (100.0%)

1979 of 2274 relevant lines covered (87.03%)

3.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/python_ta/debug/accumulation_table.py
1
"""
2
Table data structure that prints a nicely formatted table
3
for an accumulator loop
4
"""
5
from __future__ import annotations
×
6

7
import copy
×
8
import inspect
×
9
import sys
×
10
import types
×
11
from typing import Any, Union
×
12

13
import astroid
×
14
import tabulate
×
15

16

17
def num_whitespaces(start_of_loop: str) -> int:
×
18
    """Return the number of spaces at the beginning of the accumulation loop"""
19
    blank_chars = 0
×
20
    for char in start_of_loop:
×
21
        if char.isspace():
×
22
            blank_chars += 1
×
23
        else:
24
            break
×
25

26
    return blank_chars
×
27

28

29
def get_with_lines(lines: list[str], num_whitespace: int) -> str:
×
30
    """Return the lines in the with block"""
31
    endpoint = len(lines)
×
32
    for i in range(len(lines)):
×
33
        if lines[i].strip() != "" and not lines[i][num_whitespace].isspace():
×
34
            endpoint = i
×
35
            break
×
36

37
    return "\n".join(lines[:endpoint])
×
38

39

40
def get_loop_node(frame: types.FrameType) -> Union[astroid.For, astroid.While]:
×
41
    """Return the For or While node from the frame containing the accumulator loop"""
42
    func_string = inspect.cleandoc(inspect.getsource(frame))
×
43
    with_stmt_index = inspect.getlineno(frame) - frame.f_code.co_firstlineno
×
44
    lst_str_lines = func_string.splitlines()
×
45
    lst_from_with_stmt = lst_str_lines[with_stmt_index + 1 :]
×
46
    num_whitespace = num_whitespaces(lst_str_lines[with_stmt_index])
×
47
    with_lines = get_with_lines(lst_from_with_stmt, num_whitespace)
×
48

49
    with_module = astroid.parse(with_lines)
×
50
    for statement in with_module.nodes_of_class((astroid.For, astroid.While)):
×
51
        if isinstance(statement, (astroid.For, astroid.While)):
×
52
            return statement
×
53

54

55
class AccumulationTable:
×
56
    """
57
    Class used as a form of print debugging to analyze different loop and
58
    accumulation variables during each iteration in a for or while loop
59

60
    Instance attributes:
61
        loop_accumulators: a mapping between the accumulation variables
62
            and their values during each iteration
63
        loop_variables: a mapping between the loop variables and their
64
            values during each iteration
65
        _loop_lineno: the line number of the loop
66
    """
67

68
    loop_accumulators: dict[str, list]
×
69
    """A dictionary mapping loop accumulator variable name to its values across all loop iterations."""
70
    loop_variables: dict[str, list]
×
71
    """A dictionary mapping loop variable variable name to its values across all loop iterations."""
72
    _loop_lineno: int
×
73

74
    def __init__(self, accumulation_names: list[str]) -> None:
×
75
        """Initialize an AccumulationTable context manager for print-based loop debugging.
76

77
        Args:
78
            accumulation_names: a list of the loop accumulator variable names to display.
79

80
        """
81
        self.loop_accumulators = {accumulator: [] for accumulator in accumulation_names}
×
82
        self.loop_variables = {}
×
83
        self._loop_lineno = 0
×
84

85
    def _record_iteration(self, frame: types.FrameType) -> None:
×
86
        """Record the values of the accumulator variables and loop variables of an iteration"""
87
        if self.loop_variables != {} and len(list(self.loop_variables.values())[0]) > 0:
×
88
            for loop_var in self.loop_variables:
×
89
                self.loop_variables[loop_var].append(copy.copy(frame.f_locals[loop_var]))
×
90
        else:
91
            for loop_var in self.loop_variables:
×
92
                self.loop_variables[loop_var].append("N/A")
×
93

94
        for accumulator in self.loop_accumulators:
×
95
            if accumulator in frame.f_locals:
×
96
                self.loop_accumulators[accumulator].append(copy.copy(frame.f_locals[accumulator]))
×
97
            else:
98
                raise NameError
×
99

100
    def _create_iteration_dict(self) -> dict:
×
101
        """Return a dictionary that maps each accumulator
102
        and loop variable to its respective value during each iteration
103
        """
104

105
        if self.loop_variables != {}:
×
106
            iteration = list(range(len(list(self.loop_variables.values())[0])))
×
107
        elif self.loop_accumulators != {}:
×
108
            iteration = list(range(len(list(self.loop_accumulators.values())[0])))
×
109

110
        return {
×
111
            "iteration": iteration,
112
            **self.loop_variables,
113
            **self.loop_accumulators,
114
        }
115

116
    def _tabulate_data(self) -> None:
×
117
        """Print the values of the accumulator and loop variables into a table"""
118
        iteration_dict = self._create_iteration_dict()
×
119
        print(
×
120
            tabulate.tabulate(
121
                iteration_dict,
122
                headers="keys",
123
                colalign=(*["left"] * len(iteration_dict),),
124
                disable_numparse=True,
125
                missingval="None",
126
            )
127
        )
128

129
    def _trace_loop(self, frame: types.FrameType, event: str, _arg: Any) -> None:
×
130
        """Trace through the loop and store the values of the
131
        accumulators and loop variable during each iteration
132
        """
133
        if event == "line" and frame.f_lineno == self._loop_lineno:
×
134
            self._record_iteration(frame)
×
135

136
    def _setup_table(self) -> None:
×
137
        """
138
        Get the frame of the code containing the with statement, cut down the source code
139
        such that it only contains the with statement and the accumulator loop and set up
140
        the trace function to track the values of the accumulator variables during each iteration
141
        """
142
        func_frame = inspect.getouterframes(
×
143
            inspect.getouterframes(inspect.currentframe())[1].frame
144
        )[1].frame
145

146
        node = get_loop_node(func_frame)
×
147

148
        self._loop_lineno = inspect.getlineno(func_frame) + node.lineno
×
149

150
        if isinstance(node, astroid.For) and isinstance(node.target, astroid.Tuple):
×
151
            self.loop_variables = {loop_var.name: [] for loop_var in node.target.elts}
×
152
        elif isinstance(node, astroid.For):
×
153
            self.loop_variables[node.target.name] = []
×
154

155
        assert (
×
156
            self.loop_accumulators != {} or self.loop_variables != {}
157
        ), "The loop accumulator and loop variables cannot be both empty"
158

159
        func_frame.f_trace = self._trace_loop
×
160
        sys.settrace(lambda *_args: None)
×
161

162
    def __enter__(self) -> AccumulationTable:
×
163
        """Set up and return the accumulation table for the accumulator loop"""
164
        self._setup_table()
×
165
        return self
×
166

167
    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
×
168
        """Exit the accumulator loop, set the frame to none and print the table"""
169
        sys.settrace(None)
×
170
        inspect.getouterframes(inspect.currentframe())[1].frame.f_trace = None
×
171
        self._tabulate_data()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc