How to instantiate in a lazy way?
nick at craig-wood.com
Wed Dec 3 11:30:50 CET 2008
Slaunger <Slaunger 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.
> 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)
> def unpack_from_ver3_file(cls, f, samples):
> return cls._unpack_from_file(f, samples, ver=3)
> 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:
self.data_attr_names should do instead of cls.data_attr_names unless
you are overriding it in the instance (which you don't appear to be).
> initial_pos = self.f.tell()
> bytes = self.samples * cls.bytes_per_sample
> 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
> raise TermaNotImplemented, \
> "Support for ver. %d not implemented." %
> 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]
I think you want setattr() here - __dict__ is an implemetation detail
- classes with __slots__ for instance don't have a __dict__. I'd
probably do this
for k, v in zip(("I1", "Q1", "I2", "Q2"), bytes_to_data(buf)):
setattr(self, k, v)
return object.__getattr__(self, attr_name)
That then has duplicated a list of "I1", "Q1" etc which needs to be
> # Restore file position
> # 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__!
I would probably factor out the contents of the if statement into a
seperate method, but that is a matter of taste!
> 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.
Nick Craig-Wood <nick at craig-wood.com> -- http://www.craig-wood.com/nick
More information about the Python-list