4
\$\begingroup\$

My class has a method that does some stuff, and then wants to recursively operate on some data. Specifically, I'm generating an XML report whose output includes a flat list of elements derived from a hierarchical data structure.

Example Input (a Score)

{'wrapper':True, 'sub_scores':[
 {'wrapper':True, 'sub_scores':[
 {'wrapper':False, 'name':'count-objects', 'result':'pass', 'sub_scores':[]},
 {'wrapper':False, 'name':'find-object', 'result':'pass', 'sub_scores':[
 {'wrapper':False, 'name':'measure.x', 'result':'pass', 'sub_scores':[]},
 {'wrapper':False, 'name':'measure.y', 'result':'pass', 'sub_scores':[]},
 ]},
 {'wrapper':False, 'name':'find-object', 'result':'fail', 'sub_scores':[
 {'wrapper':False, 'name':'measure.x', 'result':'skip', 'sub_scores':[]},
 {'wrapper':False, 'name':'measure.y', 'result':'skip', 'sub_scores':[]},
 ]}
 ]} 
]}

Desired Output

<report user="foo" time="1234">
 <summary tests="7" pass="4" fail="1" skip="2"/>
 <results file="foo.bin" />
 <samples>
 <sample name="count-objects" result="pass" />
 <sample name="find-object" result="pass" />
 <sample name="measure.x" result="pass" />
 <sample name="measure.y" result="pass" />
 <sample name="find-object" result="fail" />
 <sample name="measure.x" result="skip" />
 <sample name="measure.y" result="skip" />
 </samples>
</report>

In JavaScript or Lua I would create a helper function within my method that does the recursion. In Ruby I would create a lambda or proc that does the recursion. Is the 'right' way to do this in Python to create a helper static function? Or is there a better way to keep the original method self-contained, perhaps allowing closures over local variables?

class Scorer:
 def generate_report(self, score):
 r = etree.Element('report', {'user': getpass.getuser(), 'time': timestamp()})
 etree.SubElement(r, 'summary', {'tests': score.total(), 'pass': score.passed(),
 'skip': score.passed(), 'fail': score.failed()})
 etree.SubElement(r, 'results', {'file': self.results_path})
 samples = etree.SubElement(r, 'samples')
 Scorer._add_score_to_samples(samples, score)
 return r
 @staticmethod
 def _add_score_to_samples(samples, score):
 # Some scores are wrappers that should not be included in output
 if not score.wrapper:
 etree.SubElement(samples, 'sample', score.report_attr() )
 for s in score.sub_scores:
 Scorer._add_score_to_samples(samples, s)

I dislike 'polluting' my class with a private static method just to help out a specific method. However, if this is considered Pythonic, I'll happily continue down this path.

200_success
145k22 gold badges190 silver badges478 bronze badges
asked Nov 1, 2016 at 15:33
\$\endgroup\$
0

1 Answer 1

2
\$\begingroup\$

I think the code could be made better by keeping all of the XML tree-building code together. The recursive traversal of .sub_scores could be done using a generator function.

Additionally, you might want to reduce the punctuation noise by setting XML attributes using keyword arguments.

class Scorer:
 def generate_report(self, score):
 r = etree.Element('report', user=getpass.getuser(), time=timestamp())
 etree.SubElement(r, 'summary', tests=score.total(), ...)
 etree.SubElement(r, 'results', file=self.results_path)
 samples = etree.SubElement(r, 'samples')
 for sample in self._samples(score):
 etree.SubElement(samples, 'sample', sample.report_attr())
 return r
 def _samples(data):
 # Some scores are wrappers that should not be included in output
 if not data.wrapper:
 yield data
 yield from _samples(data.sub_scores)

Alternatively, you could write _samples as a nested function.

class Scorer:
 def generate_report(self, score):
 def extract_samples(data):
 # Some scores are wrappers that should not be included in output
 if not data.wrapper:
 yield data
 yield from extract_samples(data.sub_scores)
 r = etree.Element('report', user=getpass.getuser(), time=timestamp())
 etree.SubElement(r, 'summary', tests=score.total(), ...)
 etree.SubElement(r, 'results', file=self.results_path)
 samples = etree.SubElement(r, 'samples')
 for sample in extract_samples(score):
 etree.SubElement(samples, 'sample', sample.report_attr())
 return r
answered Nov 1, 2016 at 18:51
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Note that I can't use keyword arguments in one case, because of the attribute name pass. \$\endgroup\$ Commented Nov 1, 2016 at 19:00

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.