Recursively generating flat XML elements from hierarchical data structure 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

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


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.
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