Custom Object Model
October 9th, 2006 by Bernard Lebel - Viewed 7415 times - Popularity: 11%3. BEYOND THE BASICS
Iteration
We’ve just been scratching the surface. Now we want to implement iteration. Specifically, we want to do this:
1 2 | for oProperty in X3DObject.Properties: print oProperty.name |
One way to do it is the __getitem__ operator overload. If you put this method into a class, it will support iteration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class PropertyCollection: def __init__(self): self.name = 'Properties' def __call__(self, sPropertyName): """ Let's intercept the calls made to this instance. If the name used matches a set of predefined properties, return the corresponding class instance. """ if sPropertyName == 'visibility': return PropertyObject( sPropertyName ) else: return None def __getitem__(self, i): """ Let's do some loop over this instance! For each iteration, return a new instance of PropertyObject. """ return PropertyObject(aPROPERTIES[i]) |
So at each iteration during the loop, a new instance of PropertyObject is returned. You have probably noticed aPROPERTIES[i]. This is one of the data structures I talked about earlier in the article. aPROPERTIES is basically a list of strings, each string being one of the properties available for the X3DObject class. This list could look like this:
1 | aPROPERTIES = ['visibility', 'display', 'geomapprox'] |
Also, there is another implicit argument for this method, that is, the index number.
If you have created ParameterCollection and ParameterObject classes already, you could implement the iteration operator overload in the ParameterCollection class and run this code:
1 2 3 | oObject = X3DObject() for oParameter in oObject.Properties('visibility').Parameters: print oParameter.value |
Get the sample code with iteration implemented here.
Membership
As a Python programmer, you are probably used to this nice little membership testing operator:
1 2 3 4 5 6 | if 'myString' in 'anotherString': pass if something in sequence: pass if something in dictionary: pass |
I don’t know about you, but I can’t count how many times I would have liked to do this to check if a parameter scriptname is found among the parameters of an object, especially custom properties. The good news is that you can do that in your own object model, using this operator overload: __contains__. Implemented in our PropertyCollection class, we would get this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | class PropertyCollection: def __init__(self): self.name = 'Properties' def __call__(self, sPropertyName): """ Let's intercept the calls made to this instance. If the name used matches a set of predefined properties, return the corresponding class instance. """ if sPropertyName == 'visibility': return PropertyObject(sPropertyName) else: return None def __getitem__(self, i): """ Let's do some loop over this instance! For each iteration, return a new instance of PropertyObject. """ return PropertyObject(aPROPERTIES[i]) def __contains__(self, sName): """ Let's test if the provided name is available as an element of the class. """ if sName in aPROPERTIES: return True else: return False |
Get the code augmented with membership testing here.
CONCLUSION
So we’ve been working on our own version of the XSI Object Model, using Python, classic classes and operator overloading. We’ve achieved some level of functionality to read data from data structures using a nice interface. If I had to mention only one drawback, is that the design I have presented in this article needs strong documentation. If not, you may spend time lurking in those files to remember what are the attributes you defined in classes and what are the kind of information they fetch from data structures. Not everyone has time for writing good documentation, unfortunately.
Also, I have covered only a fraction of the possibilities. Two examples come to mind: first, we could have added the ability to use an index number in place of a string to get a property or parameter instance. So you could use this syntax: oProperty = X3DObject.Properties(0). Second, we could have implemented string representations. As with membership testing, I cannot count the number of times I would have liked to use something Application.LogMessage(str(myObject.Parameters)) to print all the collection item name of a collection object. The __str__ method is one way of doing this. You could also implement a method that does just that, for example it could print out the FullName, Name and ScriptName grouped in a tuple.
This this file, I have implemented the ideas I have presented in this article. You may want to experiment with this file. Import it as a module in a Python command line interface (either XSI or the standalone command line application on Windows). You may run these lines of code to see:
1 2 3 4 5 6 7 8 9 10 11 | import bb_granulesconstants gc = bb_granulesconstants.bbGranuleConstants() for oGranule in gc.granules: print oGranule.scriptname if oGranule.implemented: print oGranule.metagroups('stage').enabled print str(gc.granules) print gc.granules('vertexcolor').dbtables('element').table print gc.projectdatabases('TS').database |
In this article I have talked about reading data through a custom object model interface, but I have not talked about writing data through such an interface. To be fair I have not spent enough time experimenting with this subject to discuss it in a really constructive way. With this in mind, I don’t see why it would not be possible. For example, using methods __setattr__ to intercept attribute assignments as well as methods that behave like container methods (append(), del, etc) I imagine that populating and modifying data structures should not pose a problem. I think that it would need more robust error handling for user errors than when simply reading data. Also, a strong documentation of both the data structures and the interface would be necessary.




