def after_expr(self, node, frame, value, exc_value, exc_tb): # type: (ast.expr, FrameType, Any, Optional[BaseException], Optional[TracebackType]) -> Optional[ChangeValue] if _tracing_recursively(frame): return None if node._is_interesting_expression: # Find the frame corresponding to the function call if we're inside a comprehension original_frame = frame while frame.f_code.co_name in ('<listcomp>', '<dictcomp>', '<setcomp>'): frame = frame.f_back if frame.f_code not in self._code_infos: return None if is_obvious_builtin( node, self.stack[original_frame].expression_values[node]): return None frame_info = self.stack[frame] if exc_value: node_value = self._exception_value(node, frame, exc_value) else: node_value = NodeValue.expression(value, level=max( 1, 3 - len(node._loops))) self._set_node_value(node, frame, node_value) self._check_inner_call(frame_info, node, node_value) # i.e. is `node` the `y` in `[f(x) for x in y]`, making `node.parent` the `for x in y` is_special_comprehension_iter = ( isinstance(node.parent, ast.comprehension) and node is node.parent.iter and # Generators execute in their own time and aren't directly attached to the parent frame not isinstance(node.parent.parent, ast.GeneratorExp)) if not is_special_comprehension_iter: return None # Mark `for x in y` as a bit that executed, so it doesn't show as grey self._set_node_value(node.parent, frame, NodeValue.covered()) if exc_value: return None # Track each iteration over `y` so that the 'loop' can be stepped through loops = node._loops + (node.parent, ) # type: Tuple[Loop, ...] def comprehension_iter_proxy(): for item in value: self._add_iteration(loops, frame) yield item # This effectively changes to code to `for x in comprehension_iter_proxy()` return ChangeValue(comprehension_iter_proxy())
def after_expr(self, node, frame, value, exc_value, exc_tb): # type: (ast.expr, FrameType, Any, Optional[BaseException], Optional[TracebackType]) -> Optional[ChangeValue] if _tracing_recursively(frame): return None if frame.f_code not in self._code_infos: return None if node._is_interesting_expression: # If this is an expression statement and the last statement # in the body, the value is returned from the cell magic # to be displayed as usual if (self._code_infos[frame.f_code].traced_file.is_ipython_cell and isinstance(node.parent, ast.Expr) and node.parent is node.parent.parent.body[-1]): self._ipython_cell_value = value if is_obvious_builtin(node, self.stack[frame].expression_values[node]): return None frame_info = self.stack[frame] if exc_value: node_value = self._exception_value(node, frame, exc_value) else: node_value = NodeValue.expression( self.num_samples, value, level=max( 1, 3 - len(node._loops) * (not self._is_first_loop_iteration(node, frame))), ) self._set_node_value(node, frame, node_value) self._check_inner_call(frame_info, node, node_value) # i.e. is `node` the `y` in `[f(x) for x in y]`, making `node.parent` the `for x in y` is_special_comprehension_iter = ( isinstance(node.parent, ast.comprehension) and node is node.parent.iter and # Generators execute in their own time and aren't directly attached to the parent frame not isinstance(node.parent.parent, ast.GeneratorExp)) if not is_special_comprehension_iter: return None # Mark `for x in y` as a bit that executed, so it doesn't show as grey self._set_node_value(node.parent, frame, NodeValue.covered()) if exc_value: return None # Track each iteration over `y` so that the 'loop' can be stepped through loops = node._loops + (node.parent, ) # type: Tuple[Loop, ...] def comprehension_iter_proxy(): for item in value: self._add_iteration(loops, frame) yield item # This effectively changes to code to `for x in comprehension_iter_proxy()` return ChangeValue(comprehension_iter_proxy())