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