How to instantiate in a lazy way?

Slaunger Slaunger at gmail.com
Tue Dec 2 16:01:44 CET 2008


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.

I am still a novice Python programmer, and I may have overlooked
more Pythonic ways to do it.

-- Slaunger



More information about the Python-list mailing list