How to instantiate in a lazy way?

George Sakkis george.sakkis at gmail.com
Tue Dec 2 11:50:32 EST 2008


On Dec 2, 10:01 am, Slaunger <Slaun... at gmail.com> wrote:
> Just wanted to show the end result in its actual implementation!
>
> I ended up *not* making a decorator, as I already had a good idea
> about how to do it
> using __getattr__
>
> class PayloadDualFrqIQOnDemand(PayloadDualFrqIQ):
>     """
>     This class has the same interface as its parent,
>     but unlike its parent, it is instantiated without
>     its payload parsed up in its instance attributes
>     Q1, I1, Q2 and I2. Instead it stores a reference to
>     the file object in which the Payload data can be
>     read, the file position and
>     the version of the payload data.
>
>     On accessing one of the data attributes, the actual
>     payload data are read from the file, and the reference to
>     the file object is unbound.
>     The constructor signature is therefore different from its
>     parent as it takes the file object, position and version
>     as arguments instead of the actual data.
>     """
>
>     @classmethod
>     def _unpack_from_file(cls, f, samples, ver):
>         bytes = samples * cls.bytes_per_sample
>         initial_pos = f.tell()
>         f.seek(initial_pos + bytes) #Skip over the payload
>         return cls(f, initial_pos, samples, ver)
>
>     @classmethod
>     def unpack_from_ver3_file(cls, f, samples):
>         return cls._unpack_from_file(f, samples, ver=3)
>
>     @classmethod
>     def unpack_from_ver4_file(cls, f, samples):
>         return cls._unpack_from_file(f, samples, ver=4)
>
>     data_attr_names = frozenset(["Q1", "I1", "Q2", "I2"])
>
>     def __init__(self, a_file, a_file_position, samples, a_version):
>         """
>         Returns an instance where the object knows where to
>         look for the payload but it will only be loaded on the
>         first attempt to read one of the data attributes
>         in a "normal" PayloadDualFrqIQ object.
>         """
>         self.f = a_file
>         self.file_position = a_file_position
>         self.samples = samples
>         self.ver = a_version
>
>     def __getattr__(self, attr_name):
>         """
>         Checks if a request to read a non-existing data attribute
>         has an attribute corresponding to one of the data attributes
>         in a normal PayloadDualFrqIQ object.
>
>         If true, the data attributes are created and bound to the
>         object using the file object instance, the file position
>         and the version.
>
>         It is a prerequisite that the file object is still open.
>         The function leaves the file object at the file position
>         when it entered the method
>
>         """
>         cls = self.__class__
>         if attr_name in cls.data_attr_names:
>             initial_pos = self.f.tell()
>             try:
>                 bytes = self.samples * cls.bytes_per_sample
>                 self.f.seek(self.file_position)
>                 buf = self.f.read(bytes)
>                 if self.ver == 3:
>                     bytes_to_data = cls._v3_byte_str_to_data
>                 elif self.ver == 4:
>                     bytes_to_data = cls._v4_byte_str_to_data
>                 else:
>                     raise TermaNotImplemented, \
>                         "Support for ver. %d not implemented." %
> self.ver
>                 I1, Q1, I2, Q2 = bytes_to_data(buf)
>                 self.__dict__["I1"] = I1
>                 self.__dict__["Q1"] = Q1
>                 self.__dict__["I2"] = I2
>                 self.__dict__["Q2"] = Q2
>                 return self.__dict__[attr_name]
>             finally:
>                 # Restore file position
>                 self.f.seek(initial_pos)
>                 # Unbind lazy attributes
>                 del self.f
>                 del self.ver
>                 del self.file_position
>                 del self.samples
>
> This seems to work out well. No infinite loops in __getattr__!
>
> At least it passes the unit test cases I have come up with so far.
>
> No guarantees though, as I may simply not have been smart enough to
> bring forth unit test cases which make it crash.
>
> Comments on the code is still appreciated though.

A trivial improvement: replace

>                 I1, Q1, I2, Q2 = bytes_to_data(buf)
>                 self.__dict__["I1"] = I1
>                 self.__dict__["Q1"] = Q1
>                 self.__dict__["I2"] = I2
>                 self.__dict__["Q2"] = Q2

with:

    self.__dict__.update(zip(self.data_attr_names, bytes_to_data
(buf)))

where data_attr_names = ("I1", "Q1", "I2", "Q2") instead of a
frozenset. A linear search in a size-4 tuple is unlikely to be the
bottleneck with much I/O anyway.

George



More information about the Python-list mailing list