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

pyta-uoft / pyta / 3615634947

pending completion
3615634947

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/<a class="double-link" href="https://git"><a class=hub.com/pyta-uoft/pyta/commit/bde882df1e015d642780e8e0f6cd95fb50ebbb2e">bde882df1">&lt;a href=&quot;https://github.com/pyta-uoft/pyta/commit/</a><a class="double-link" href="https://github.com/pyta-uoft/pyta/commit/&lt;a class=&quot;double-link&quot; href=&quot;https://git">&lt;a class=</a>hub.com/pyta-uoft/pyta/commit/&lt;a class=&quot;double-link&quot; href=&quot;https://git">&lt;a class=</a>hub.com/pyta-uoft/pyta/commit/&lt;a class=&quot;double-link&quot; href=&quot;https://git">&lt;a class=</a>hub.com/pyta-uoft/pyta/commit/&lt;a class=&quot;double-link&quot; href=&quot;https://git">&lt;a class=</a>hub.com/pyta-uoft/pyta/commit/&lt;a class=&quot;double-link&quot; href=&quot;https://git">&lt;a class=</a>hub.com/pyta-uoft/pyta/commit/bde882df1e015d642780e8e0f6cd95fb50ebbb2e">bde882df1</a><a href="https://github.com/pyta-uoft/pyta/commit/bde882df1e015d642780e8e0f6cd95fb50ebbb2e">&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/&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/&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/&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/&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/bde882df1e015d642780e8e0f6cd95fb50ebbb2e&quot;&gt;bde882df1&lt;/a&gt;&lt;a href=&quot;https://github.com/pyta-uoft/pyta/commit/bde882df1e015d642780e8e0f6cd95fb50ebbb2e&quot;&gt;&amp;quot;&amp;gt;&amp;amp;lt;a h... (continued)
Pull Request #864: Extend accumulation table loop detection

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

1977 of 2269 relevant lines covered (87.13%)

2.61 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 from the start to the end
31
    of the accumulator loop
32
    """
33
    endpoint = len(lines)
×
34
    for i in range(len(lines)):
×
35
        if lines[i].strip() != "" and not lines[i][num_whitespace].isspace():
×
36
            endpoint = i
×
37
            break
×
38

39
    return "\n".join(lines[:endpoint])
×
40

41

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

51
    for node in astroid.parse(with_lines).body:
×
52
        if isinstance(node, (astroid.For, astroid.While)):
×
53
            return node
×
54

55

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

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

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

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

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

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

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

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

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

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

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

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

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

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

147
        node = get_loop_node(func_frame)
×
148

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

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

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

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

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

168
    def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
×
169
        """Exit the accumulator loop, set the frame to none and print the table"""
170
        sys.settrace(None)
×
171
        inspect.getouterframes(inspect.currentframe())[1].frame.f_trace = None
×
172
        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