How to instantiate in a lazy way?

Nick Craig-Wood nick at craig-wood.com
Wed Dec 3 05:30:50 EST 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.
>      """
> 
>      @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:

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()
>              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]

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
factored out.

>              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__!

:-)

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.

Looks fine!

-- 
Nick Craig-Wood <nick at craig-wood.com> -- http://www.craig-wood.com/nick



More information about the Python-list mailing list