[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.python

Dynamic Class Creation

Josh English

3/16/2010 6:01:00 AM

I have a large program with lots of data stored in XML. I'm upgrading
my GUI to use ObjectListView, but with my data in XML, I don't have
regular objects to interact with the OLV. I do have an XML validator
that defines the structure of the XML elements, and I'm trying to
dynamically create a class to wrap the XML element.

So, an element like:

<market code="WotF">
<title>Writers of the Future</title>
</market>

I want to create a class like:

class Market(object):
def __init__(self, elem):
self._elem = elem

def get_code(self):
return self._elem.get('code')

def set_code(self, value):
self._elem.set('code', value)

def get_title(self):
return self._elem.find('title').text

def set_title(self, value):
node = self._elem.find('title')
node.text = value

Naturally, I don't want to hand code this for every interface but
would like to create them dynamically. (The laziness of programming, I
guess.)

I have tried several solutions that I found on various forums, but
they are all several years old.

Questions:

What's the best way to create these helper methods?

How can I attach them to the class and have it run?

I have had a few solutions work where I had a class with three methods
(get_code, get_tier, get_mail) but they all return the same value, not
the individual values.

----
Josh English




5 Answers

Chris Rebert

3/16/2010 6:19:00 AM

0

On Mon, Mar 15, 2010 at 11:01 PM, Josh English
<joshua.r.english@gmail.com> wrote:
> I have a large program with lots of data stored in XML. I'm upgrading
> my GUI to use ObjectListView, but with my data in XML, I don't have
> regular objects to interact with the OLV. I do have an XML validator
> that defines the structure of the XML elements, and I'm trying to
> dynamically create a class to wrap the XML element.
>
> So, an element like:
>
> <market code="WotF">
> <title>Writers of the Future</title>
> </market>
>
> I want to create a class like:
>
> class Market(object):
>    def __init__(self, elem):
>        self._elem = elem
>
>    def get_code(self):
>        return self._elem.get('code')
>
>    def set_code(self, value):
>        self._elem.set('code', value)
>
>    def get_title(self):
>        return self._elem.find('title').text
>
>    def set_title(self, value):
>        node = self._elem.find('title')
>        node.text = value
>
> Naturally, I don't want to hand code this for every interface but
> would like to create them dynamically. (The laziness of programming, I
> guess.)
>
> I have tried several solutions that I found on various forums, but
> they are all several years old.
>
> Questions:
>
> What's the best way to create these helper methods?

Nested functions:

def make_attr_getset(name):
def get(self):
return self._elem.get(name)

def set(self, value):
self._elem.set(name, value)
return get, set

get_code, set_code = make_attr_getset('code')

def make_subelement_getset(name):
def get(self):
return self._elem.find(name).text

def set(self, value):
node = self._elem.find(name)
node.text = value
return get, set

get_title, set_title = make_subelement_getset('title')

> How can I attach them to the class and have it run?

Use properties and setattr():

class Market(object):
def __init__(... #same as before

setattr(Market, 'code', property(get_code, set_code))
setattr(Market, 'title', property(get_title, set_title))

m = Market(the_XML)
print m.title #=> Writers of the Future
m.title = "Writers of the Past"
print m.code #=> WotF
m.code = "WofP"

Cheers,
Chris
--
http://blog.re...

Jack Diederich

3/16/2010 4:50:00 PM

0

On Tue, Mar 16, 2010 at 2:18 AM, Chris Rebert <clp2@rebertia.com> wrote:
> On Mon, Mar 15, 2010 at 11:01 PM, Josh English
> <joshua.r.english@gmail.com> wrote:
>> I have a large program with lots of data stored in XML. I'm upgrading
>> my GUI to use ObjectListView, but with my data in XML, I don't have
>> regular objects to interact with the OLV. I do have an XML validator
>> that defines the structure of the XML elements, and I'm trying to
>> dynamically create a class to wrap the XML element.
>>
>> So, an element like:
>>
>> <market code="WotF">
>> <title>Writers of the Future</title>
>> </market>
>>
>> I want to create a class like:
>>
>> class Market(object):
>>    def __init__(self, elem):
>>        self._elem = elem
>>
>>    def get_code(self):
>>        return self._elem.get('code')
>>
>>    def set_code(self, value):
>>        self._elem.set('code', value)
>>
>>    def get_title(self):
>>        return self._elem.find('title').text
>>
>>    def set_title(self, value):
>>        node = self._elem.find('title')
>>        node.text = value
>>
>> Naturally, I don't want to hand code this for every interface but
>> would like to create them dynamically. (The laziness of programming, I
>> guess.)
>>
>> What's the best way to create these helper methods?

You can either define a catch-all __getattr__ method to look them up
dynamically, or as Chris kinda-suggested write descriptors for the
individual elements.

class Market():
def __init__(self, elem):
self._elem = elem
def __getattr__(self, name):
try:
# I'm assuming this raises a KeyError when not found
return self._elem.get(name)
except KeyError:
return self._elem.find(name)
def __setitem__(self, name, value):
# like __getitem__ but for setting

Chris' property maker function is almost like a descriptor class (how
properties are implemented under the hood), here's a refactoring
[untested]

class ElemGetProperty():
def __init__(self, name):
self.name = name
def __get__(self, ob, cls):
return ob._elem.get(self.name)
def __set__(self, ob, val):
ob._elem.set(self.name, val)

You could write one property class for each kind of element (get/find)
and then put them in your class like this

class Market():
code = ElemGetProperty('code')
title = ElemFindProeprty('title')

The getattr/setattr method is easier to understand and will handle
arbitrary elements; for the descriptors version you'll have to define
one for each tag that might be used on the class.

-Jack

Chris Rebert

3/16/2010 4:54:00 PM

0

On Tue, Mar 16, 2010 at 9:49 AM, Jack Diederich <jackdied@gmail.com> wrote:
> On Tue, Mar 16, 2010 at 2:18 AM, Chris Rebert <clp2@rebertia.com> wrote:
>> On Mon, Mar 15, 2010 at 11:01 PM, Josh English
>> <joshua.r.english@gmail.com> wrote:
<snip>
>>> What's the best way to create these helper methods?
>
> You can either define a catch-all __getattr__ method to look them up
> dynamically, or as Chris kinda-suggested write descriptors for the
> individual elements.
>
> class Market():
>  def __init__(self, elem):
>    self._elem = elem
>  def __getattr__(self, name):
>    try:
>      # I'm assuming this raises a KeyError when not found
>      return self._elem.get(name)
>    except KeyError:
>      return self._elem.find(name)
>  def __setitem__(self, name, value):

did you mean __setattr__ here?

<snip>
> The getattr/setattr method is easier to understand and will handle
> arbitrary elements;  for the descriptors version you'll have to define
> one for each tag that might be used on the class.

Cheers,
Chris
--
http://blog.re...

Josh English

3/17/2010 6:14:00 AM

0

Chris,

Thanks. This worked for the attributes, but I think the tactic is
still misleading. There are child elements I can't quite determine how
to deal with:

<market code='anlg' tier='ProMarket' mail='True'>
<title field="pref">Analog Science Fiction and Fact</title>
<nickname>Analog</nickname>
<keyword>Science Fiction</keyword>
<keyword>First Contact</keyword>
<keyword>Hard Science Fiction</keyword>
<address>
<attnline>Stanley Schmidt, Editor</attnline>
<address1>267 Broadway, 4th Floor</address1>
<address2>New York, NY 10007-2352</address2>
</address>
<website>http://www.analogsf.com</w...
</market>

A child element with text and an attribute or two, for example, pose a
problem. I can call Market.title but should I try Market.title.field
or Market.title_field.

Multiple elements, such as keywords, are allowed in xml but harder to
map to the object. I don't know if I want to go create a list and
methods for accessing those keywords as a list, or redefine the rules
of my XML to not allow multiple child elements with the same tag. I
can't decide.

Then the address is a bit of a bear.

In short, I have to figure out the whole object interface to the XML
and how I want that to work.

Thanks for the suggestion. It is undeniably clever.

Josh

Gerard Flanagan

3/17/2010 8:54:00 AM

0

Josh English wrote:
> Chris,
>
> Thanks. This worked for the attributes, but I think the tactic is
> still misleading. There are child elements I can't quite determine how
> to deal with:
>
> <market code='anlg' tier='ProMarket' mail='True'>
> <title field="pref">Analog Science Fiction and Fact</title>
> <nickname>Analog</nickname>
> <keyword>Science Fiction</keyword>
> <keyword>First Contact</keyword>
> <keyword>Hard Science Fiction</keyword>
> <address>
> <attnline>Stanley Schmidt, Editor</attnline>
> <address1>267 Broadway, 4th Floor</address1>
> <address2>New York, NY 10007-2352</address2>
> </address>
> <website>http://www.analogsf.com</w...
> </market>
>
> A child element with text and an attribute or two, for example, pose a
> problem. I can call Market.title but should I try Market.title.field
> or Market.title_field.
>
> Multiple elements, such as keywords, are allowed in xml but harder to
> map to the object. I don't know if I want to go create a list and
> methods for accessing those keywords as a list, or redefine the rules
> of my XML to not allow multiple child elements with the same tag. I
> can't decide.
>
> Then the address is a bit of a bear.
>
> In short, I have to figure out the whole object interface to the XML
> and how I want that to work.
>


Have you heard of or considered PyXB (http://pyxb.source...)? Not
that I've much experience with it, but you can use it to generate python
code from XML Schema files, and, I think, WSDL definitions. It's a bit
of a rabbit hole, but then isn't that half the fun!

The following might give you the idea. You start with a Schema
schema.xsd, generate a python module from it, then read in some XML with
the generated classes to get your custom objects.


---------------- schema.xsd ----------------------

<xs:schema targetNamespace="http://www.josh.com/schema...
xmlns:xs="http://www.w3.org/2001/XMLSc...
xmlns:josh="http://www.josh.com/schema...>


<xs:element name="market" type="josh:MarketType"/>

<xs:complexType name="MarketType">
<xs:sequence>
<xs:element name="title" type="josh:TitleType"/>
<xs:element name="nickname" type="xs:string"/>
<xs:element name="keyword" type="xs:string"
maxOccurs="unbounded"/>
<xs:element name="address" type="josh:USAddress"/>
<xs:element name="website" type="xs:string"/>
</xs:sequence>
<xs:attributeGroup ref="josh:MarketAttributes"/>
</xs:complexType>

<xs:complexType name="TitleType">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="field" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>

<xs:attributeGroup name="MarketAttributes">
<xs:attribute name="code" type="xs:string"/>
<xs:attribute name="tier" type="xs:string"/>
<xs:attribute name="mail" type="xs:string"/>
</xs:attributeGroup>

<xs:complexType name="Address" abstract="true">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="street" type="xs:string"/>
<xs:element name="city" type="xs:string"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="USAddress">
<xs:complexContent>
<xs:extension base="josh:Address">
<xs:sequence>
<xs:element name="state" type="josh:USState"
minOccurs="0"/>
<xs:element name="zip" type="xs:positiveInteger"
minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>

<xs:simpleType name="USState">
<xs:restriction base="xs:string">
<xs:enumeration value="AK"/>
<xs:enumeration value="AL"/>
<xs:enumeration value="AR"/>
<!-- and so on ... -->
</xs:restriction>
</xs:simpleType>

</xs:schema>

---------------- genbindings.sh ---------------------------

#!/bin/sh

pyxbgen -u schema.xsd -m market_binding -r

---------------- demo.py -----------------------------------

s = """<?xml version="1.0"?>
<josh:market
xmlns:josh="http://www.josh.com/schema...
code="anlg"
tier="ProMarket"
mail="True">

<title field="pref">Analog Science Fiction and Fact</title>
<nickname>Analog</nickname>
<keyword>Science Fiction</keyword>
<keyword>First Contact</keyword>
<keyword>Hard Science Fiction</keyword>
<address>
<name>Stanley Schmidt, Editor</name>
<street>267 Broadway, 4th Floor</street>
<city>New York</city>
</address>
<website>http://www.analogsf.com</w...
</josh:market>
"""

import xml.dom.minidom
import market_binding

market =
market_binding.CreateFromDOM(xml.dom.minidom.parseString(s).documentElement)

print market.tier
print market.code
print market.keyword
assert len(market.keyword) == 3
print market.address.name
print market.toxml()