10000 BUG: deallocate recursive closure in arrayprint.py · ahaldane/numpy@92c23cf · GitHub
[go: up one dir, main page]

Skip to content

Commit 92c23cf

Browse files
committed
BUG: deallocate recursive closure in arrayprint.py
Fixes numpy#10620
1 parent eaac472 commit 92c23cf

File tree

2 files changed

+103
-88
lines changed

2 files changed

+103
-88
lines changed

numpy/core/arrayprint.py

Lines changed: 90 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -642,118 +642,121 @@ def _extendLine(s, line, word, line_width, next_line_prefix, legacy):
642642
line += word
643643
return s, line
644644

645+
def _recursive_fmt(param, index, indent, curr_width):
646+
"""
647+
Helper function for _formatArray, to recursively print array elements.
645648
646-
def _formatArray(a, format_function, line_width, next_line_prefix,
647-
separator, edge_items, summary_insert, legacy):
648-
"""formatArray is designed for two modes of operation:
649+
"param" are the values that are invariant under recursion, including
650+
the array to be printed itself. index, indent and curr_width
651+
are updated during recursion.
652+
"""
653+
# unpack parameters
654+
a, format_function, separator, edge_items, summary_insert, legacy = param
649655

650-
1. Full output
656+
axis = len(index)
657+
axes_left = a.ndim - axis
651658

652-
2. Summarized output
659+
if axes_left == 0:
660+
return format_function(a[index])
653661

654-
"""
655-
def recurser(index, hanging_indent, curr_width):
656-
"""
657-
By using this local function, we don't need to recurse with all the
658-
arguments. Since this function is not created recursively, the cost is
659-
not significant
660-
"""
661-
axis = len(index)
662-
axes_left = a.ndim - axis
662+
# when recursing, add a space to align with the [ added, and reduce the
663+
# length of the line by 1
664+
next_indent = indent + ' '
665+
if legacy == '1.13':
666+
next_width = curr_width
667+
else:
668+
next_width = curr_width - len(']')
663669

664-
if axes_left == 0:
665-
return format_function(a[index])
670+
a_len = a.shape[axis]
671+
show_summary = summary_insert and 2*edge_items < a_len
672+
if show_summary:
673+
leading_items = edge_items
674+
trailing_items = edge_items
675+
else:
676+
leading_items = 0
677+
trailing_items = a_len
666678

667-
# when recursing, add a space to align with the [ added, and reduce the
668-
# length of the line by 1
669-
next_hanging_indent = hanging_indent + ' '
670-
if legacy == '1.13':
671-
next_width = curr_width
672-
else:
673-
next_width = curr_width - len(']')
679+
# stringify the array with the hanging indent on the first line too
680+
s = ''
674681

675-
a_len = a.shape[axis]
676-
show_summary = summary_insert and 2*edge_items < a_len
677-
if show_summary:
678-
leading_items = edge_items
679-
trailing_items = edge_items
682+
# last axis (rows) - wrap elements if they would not fit on one line
683+
if axes_left == 1:
684+
# the length up until the beginning of the separator / bracket
685+
if legacy == '1.13':
686+
elem_width = curr_width - len(separator.rstrip())
680687
else:
681-
leading_items = 0
682-
trailing_items = a_len
688+
elem_width = curr_width - max(len(separator.rstrip()), len(']'))
683689

684-
# stringify the array with the hanging indent on the first line too
685-
s = ''
690+
line = indent
691+
for i in range(leading_items):
692+
word = _recursive_fmt(param, index + (i,), next_indent, next_width)
693+
s, line = _extendLine(s, line, word, elem_width, indent, legacy)
694+
line += separator
686695

687-
# last axis (rows) - wrap elements if they would not fit on one line
688-
if axes_left == 1:
689-
# the length up until the beginning of the separator / bracket
696+
if show_summary:
697+
s, line = _extendLine(
698+
s, line, summary_insert, elem_width, indent, legacy)
690699
if legacy == '1.13':
691-
elem_width = curr_width - len(separator.rstrip())
700+
line += ", "
692701
else:
693-
elem_width = curr_width - max(len(separator.rstrip()), len(']'))
694-
695-
line = hanging_indent
696-
for i in range(leading_items):
697-
word = recurser(index + (i,), next_hanging_indent, next_width)
698-
s, line = _extendLine(
699-
s, line, word, elem_width, hanging_indent, legacy)
700702
line += separator
701703

702-
if show_summary:
703-
s, line = _extendLine(
704-
s, line, summary_insert, elem_width, hanging_indent, legacy)
705-
if legacy == '1.13':
706-
line += ", "
707-
else:
708-
line += separator
709-
710-
for i in range(trailing_items, 1, -1):
711-
word = recurser(index + (-i,), next_hanging_indent, next_width)
712-
s, line = _extendLine(
713-
s, line, word, elem_width, hanging_indent, legacy)
714-
line += separator
704+
for i in range(trailing_items, 1, -1):
705+
word = _recursive_fmt(param, index + (-i,), next_indent, next_width)
706+
s, line = _extendLine(s, line, word, elem_width, indent, legacy)
707+
line += separator
708+
709+
if legacy == '1.13':
710+
# width of the separator is not considered on 1.13
711+
elem_width = curr_width
712+
word =_recursive_fmt(param, index + (-1,), next_indent, next_width)
713+
s, line = _extendLine(
714+
s, line, word, elem_width, indent, legacy)
715+
716+
s += line
717+
718+
# other axes - insert newlines between rows
719+
else:
720+
s = ''
721+
line_sep = separator.rstrip() + '\n'*(axes_left - 1)
722+
723+
for i in range(leading_items):
724+
nested =_recursive_fmt(param, index + (i,), next_indent, next_width)
725+
s += indent + nested + line_sep
715726

727+
if show_summary:
716728
if legacy == '1.13':
717-
# width of the seperator is not considered on 1.13
718-
elem_width = curr_width
719-
word = recurser(index + (-1,), next_hanging_indent, next_width)
720-
s, line = _extendLine(
721-
s, line, word, elem_width, hanging_indent, legacy)
729+
# trailing space, fixed nbr of newlines, and fixed separator
730+
s += indent + summary_insert + ", \n"
731+
else:
732+
s += indent + summary_insert + line_sep
722733

723-
s += line
734+
for i in range(trailing_items, 1, -1):
735+
nested = _recursive_fmt(param, index + (-i,), next_indent,
736+
next_width)
737+
s += indent + nested + line_sep
724738

725-
# other axes - insert newlines between rows
726-
else:
727-
s = ''
728-
line_sep = separator.rstrip() + '\n'*(axes_left - 1)
739+
nested =_recursive_fmt(param, index + (-1,), next_indent, next_width)
740+
s += indent + nested
729741

730-
for i in range(leading_items):
731-
nested = recurser(index + (i,), next_hanging_indent, next_width)
732-
s += hanging_indent + nested + line_sep
742+
# remove the hanging indent, and wrap in []
743+
s = '[' + s[len(indent):] + ']'
744+
return s
733745

734-
if show_summary:
735-
if legacy == '1.13':
736-
# trailing space, fixed nbr of newlines, and fixed separator
737-
s += hanging_indent + summary_insert + ", \n"
738-
else:
739-
s += hanging_indent + summary_insert + line_sep
746+
def _formatArray(a, format_function, line_width, next_line_prefix,
747+
separator, edge_items, summary_insert, legacy):
748+
"""_formatArray is designed for two modes of operation:
740749
741-
for i in range(trailing_items, 1, -1):
742-
nested = recurser(index + (-i,), next_hanging_indent, next_width)
743-
s += hanging_indent + nested + line_sep
750+
1. Full output
744751
745-
nested = recurser(index + (-1,), next_hanging_indent, next_width)
746-
s += hanging_indent + nested
752+
2. Summarized output
747753
748-
# remove the hanging indent, and wrap in []
749-
s = '[' + s[len(hanging_indent):] + ']'
750-
return s
754+
"""
751755

752756
# invoke the recursive part with an initial index and prefix
753-
return recurser(
754-
index=(),
755-
hanging_indent=next_line_prefix,
756-
curr_width=line_width)
757+
param = a, format_function, separator, edge_items, summary_insert, legacy
758+
return _recursive_fmt(param, index=(), indent=next_line_prefix,
759+
curr_width=line_width)
757760

758761

759762
def _none_or_positive_arg(x, name):

numpy/core/tests/test_arrayprint.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# -*- coding: utf-8 -*-
22
from __future__ import division, absolute_import, print_function
33

4-
import sys
4+
import sys, gc
55

66
import numpy as np
77
from numpy.testing import (
@@ -355,6 +355,18 @@ def test_wide_element(self):
355355
"[ 'xxxxx']"
356356
)
357357

358+
def test_refcount(self):
359+
# make sure we do not hold references to the array due to a recursive
360+
# closure (gh-10620)
361+
gc.disable()
362+
a = np.arange(2)
363+
r1 = sys.getrefcount(a)
364+
np.array2string(a)
365+
np.array2string(a)
366+
r2 = sys.getrefcount(a)
367+
gc.collect()
368+
gc.enable()
369+
assert_(r1 == r2)
358370

359371
class TestPrintOptions(object):
360372
"""Test getting and setting global print options."""

0 commit comments

Comments
 (0)
0