Aviv Abramovich
Aviv Abramovich

Reputation: 135

Wrapping for loop in python

I want to wrap the functionality of for loop so it would be more intuitive to use it. In my case, I have to parse xml file looks like this:

<instance id="line-n.w8_047:15696:">
    <answer instance="line-n.w8_047:15696:" senseid="product" />
    <context> context1 </context>
</instance>

<instance id="line-n.w8_088:12441:">
    <answer instance="line-n.w8_088:12441:" senseid="product" />
    <context> another context</context>
</instance>

I wrote a class for Instance like this:

class Instance:
    def __init__(self, id, answer, context):
        self.id = id
        self.answer = answer
        self.context = context

I wrote the next function to enumerate the instances:

import xml.etree.ElementTree as ET
def enum_instances(file_path, action):
    for instance_xml in ET.parse(file_path).getroot().find('lexelt'):
        action(Instance(
            instance_xml.attrib['id'],
            instance_xml.find('answer').attrib['senseid'],
            instance_xml.find('context').text)
        )

The action parameter is a callback to do some action with the Instance, like this:

enum_instances('/path/to/xml', lambda instance: print(instance.context))

but it looks a bit odd, I would like it be more intuitive, like this:

for instance in enum_instances(file_path):
    print(instance.context)

what is the best way to implement that 'iterable' function? Thanks

Upvotes: 0

Views: 804

Answers (2)

Jon Clements
Jon Clements

Reputation: 142256

Instead of passing a callable to your function, make it yield an Instance, then you can get the behaviour you want, eg:

def enum_instances(file_path, action=None):
    for instance_xml in ET.parse(file_path).getroot().find('lexelt'):
        instance = Instance(
            instance_xml.attrib['id'],
            instance_xml.find('answer').attrib['senseid'],
            instance_xml.find('context').text)
        if action is not None:
            instance = action(instance)
        yield instance

In this case - I've defaulted action to None, but it leaves you room to pass a function to mutate the instance somehow before yielding it if required.

Then:

for instance in enum_instances('some_file_path'):
    print(instance.context)

Upvotes: 1

doctorlove
doctorlove

Reputation: 19317

Generators for the win, in this case. Something like

def enum_instances(file_path):
    for instance_xml in ET.parse(file_path).getroot().find('lexelt'):
        yield Instance(
            instance_xml.attrib['id'],
            instance_xml.find('answer').attrib['senseid'],
            instance_xml.find('context').text)

Then you can say exactly what you required:

for instance in enum_instances(file_path):
    print(instance.context)

Upvotes: 3

Related Questions