Search code examples
pythonclassoopproperties

Dynamic lazy class properties python


I have a class, Record, that is used to contain the results of reading text file. The file contains a simple database with fields and tags. I would like to have each Record instance only have the properties associated with its database. Basically:

R1 = Record("file1")
R2 = Record("file2")
print(R1.TI) #"Record 1's title"
print(R2.TI) #AttributeError: 'Record' object has no attribute 'TI'

unfortunately some of the fields may require a large amount of processing to return something useful, and those values may never be needed. So I would like the value to be determined the first time they are called not when the object is initialized.

Because I know the tags name's only I have tried:

 class tagWrapper(object):
     def __init__(self, tag):
         self.tag = tag
         self.data = None
     def __get__(self, instance, owner):
         if self.data == None:
             try:
                 #tagToFunc is a dictionary that maps tags to their processing function
                 self.data = tagToFunc[self.tag](instance._rawDataDict[self.tag]) 
             except KeyError: #I do not know the full list of tags
                 self.data = instance._rawDataDict[self.tag]
         return self.data

 class Record(object):
     def __init__(self, file):
         #Reading file and making _rawDataDict
         setattr(self, tag, tagWrapper(tag))

This causes R1.TI to produce the wrapper object not the value I want. So I suspect I am screwing something up with the get method.

Note: I am trying to make the attributes part of the individual class instances and not evaluated until needed. I can implement one or the other but have not been able to determine how to do both.


Solution

  • I have a solution that seems to work although it is quite ugly:

    class Record(object):
       def __init__(self, file):
         self._unComputedTags = set() #needs to be initialized first
          #stuff
          self._unComputedTags = set(self._fieldDict.keys())
          for tag in self._fieldDict:
                self.__dict__[tag] = None
    
       def __getattribute__(self, name):
           if name == '_unComputedTags':
               #This may be unnecessary if I play with things a bit
               return object.__getattribute__(self, '_unComputedTags')
           if name in self._unComputedTags:
               try:
                   tagVal = tagToFunc[name](self._fieldDict[name])
               except KeyError:
                   tagVal = self._fieldDict[name]
               setattr(self, name, tagVal)
               self._unComputedTags.remove(name)
           return object.__getattribute__(self, name)
    

    I do not like overwriting __getattribute__ but this seems to work.