diff --git a/changes.d/771.misc b/changes.d/771.misc new file mode 100644 index 000000000..168460257 --- /dev/null +++ b/changes.d/771.misc @@ -0,0 +1 @@ +The `repr` implementation of `Loop` will now return an evaluable python expression. The old behaviour moved to the `str` implementation. diff --git a/qupulse/_program/_loop.py b/qupulse/_program/_loop.py index 92d3f7eb2..20ec1204c 100644 --- a/qupulse/_program/_loop.py +++ b/qupulse/_program/_loop.py @@ -3,6 +3,7 @@ from enum import Enum import warnings import bisect +import reprlib import numpy as np import sympy.ntheory @@ -198,23 +199,20 @@ def encapsulate(self) -> None: self._measurements = None self.assert_tree_integrity() - def _get_repr(self, first_prefix, other_prefixes) -> Generator[str, None, None]: + def _get_str(self, first_prefix, other_prefixes) -> Generator[str, None, None]: if self.is_leaf(): yield '%sEXEC %r %d times' % (first_prefix, self._waveform, self.repetition_count) else: yield '%sLOOP %d times:' % (first_prefix, self.repetition_count) for elem in self: - yield from cast(Loop, elem)._get_repr(other_prefixes + ' ->', other_prefixes + ' ') - - def __repr__(self) -> str: - is_circular = is_tree_circular(self) - if is_circular: - return '{}: Circ {}'.format(id(self), is_circular) + yield from cast(Loop, elem)._get_str(other_prefixes + ' ->', other_prefixes + ' ') + @reprlib.recursive_repr() + def __str__(self) -> str: str_len = 0 repr_list = [] - for sub_repr in self._get_repr('', ''): + for sub_repr in self._get_str('', ''): str_len += len(sub_repr) if self.MAX_REPR_SIZE and str_len > self.MAX_REPR_SIZE: @@ -224,6 +222,47 @@ def __repr__(self) -> str: repr_list.append(sub_repr) return '\n'.join(repr_list) + @reprlib.recursive_repr() + def __repr__(self): + children = self.children + waveform = self.waveform + measurements = self._measurements + repetition_count = self.repetition_count + + max_repr_size = self.MAX_REPR_SIZE + + reprs = {} + if children: + if len(children) < max_repr_size // len('Loop(...)'): + reprs['children'] = repr(list(children)) + else: + reprs['children'] = '[...]' + + if waveform: + waveform_repr = repr(waveform) + if len(waveform_repr) >= max_repr_size: + waveform_repr = '...' + reprs['waveform'] = waveform_repr + + if measurements: + meas_repr = repr(measurements) + if len(meas_repr) >= max_repr_size: + meas_repr = '[...]' + reprs['measurements'] = meas_repr + + if repetition_count != 1: + reprs['repetition_count'] = repr(repetition_count) + + kwargs = ', '.join(f'{attr}={val}' for attr, val in reprs.items()) + + type_name = type(self).__name__ + max_kwargs = max(self.MAX_REPR_SIZE - len(type_name) - 2, 0) + + if len(kwargs) > max_kwargs: + kwargs = '...' + + return f'{type(self).__name__}({kwargs})' + def copy_tree_structure(self, new_parent: Union['Loop', bool]=False) -> 'Loop': return type(self)(parent=self.parent if new_parent is False else new_parent, waveform=self._waveform, diff --git a/tests/_program/loop_tests.py b/tests/_program/loop_tests.py index 3bd811cd6..a3ff48aa3 100644 --- a/tests/_program/loop_tests.py +++ b/tests/_program/loop_tests.py @@ -142,7 +142,7 @@ def test_get_measurement_windows(self): # no measurements left self.assertEqual({}, prog.get_measurement_windows()) - def test_repr(self): + def test_str(self): wf_gen = WaveformGenerator(num_channels=1) wfs = [wf_gen() for _ in range(11)] @@ -154,10 +154,19 @@ def test_repr(self): loop.waveform = wfs.pop(0) self.assertEqual(len(wfs), 0) - self.assertEqual(repr(tree), expected) + self.assertEqual(str(tree), expected) with mock.patch.object(Loop, 'MAX_REPR_SIZE', 1): - self.assertEqual(repr(tree), '...') + self.assertEqual(str(tree), '...') + + def test_repr(self): + root_loop = self.get_test_loop() + + root_repr = repr(root_loop) + + root_eval = eval(root_repr) + + self.assertEqual(root_loop, root_eval) def test_is_leaf(self): root_loop = self.get_test_loop(waveform_generator=WaveformGenerator(1)) @@ -195,12 +204,12 @@ def test_flatten_and_balance(self): after = before.copy_tree_structure() after.flatten_and_balance(2) - wf_reprs = dict(zip(ascii_uppercase, - (repr(loop.waveform) + wf_strs = dict(zip(ascii_uppercase, + (str(loop.waveform) for loop in before.get_depth_first_iterator() if loop.is_leaf()))) - before_repr = """\ + before_str = """\ LOOP 1 times: ->EXEC {A} 1 times ->LOOP 10 times: @@ -220,10 +229,10 @@ def test_flatten_and_balance(self): ->EXEC {I} 8 times ->LOOP 9 times: ->EXEC {J} 10 times - ->EXEC {K} 11 times""".format(**wf_reprs) - self.assertEqual(repr(before), before_repr) + ->EXEC {K} 11 times""".format(**wf_strs) + self.assertEqual(str(before), before_str) - expected_after_repr = """\ + expected_after_str = """\ LOOP 1 times: ->LOOP 1 times: ->EXEC {A} 1 times @@ -261,9 +270,9 @@ def test_flatten_and_balance(self): ->EXEC {I} 8 times ->LOOP 9 times: ->EXEC {J} 10 times - ->EXEC {K} 11 times""".format(**wf_reprs) + ->EXEC {K} 11 times""".format(**wf_strs) - self.assertEqual(expected_after_repr, repr(after)) + self.assertEqual(expected_after_str, str(after)) def test_flatten_and_balance_comparison_based(self): wfs = [DummyWaveform(duration=i) for i in range(2)]