dynamic type changing

Bruno Desthuilliers bdesth.quelquechose at free.quelquepart.fr
Sat May 27 15:40:22 EDT 2006


andychambers2002 at yahoo.co.uk a écrit :
> I'm working on a "TempFile" class that stores the data in memory until
> it gets larger than a specified threshold (as per PEP 42).  Whilst
> trying to implement it, I've come across some strange behaviour.  Can
> anyone explain this?
> 
> The test case at the bottom starts a TempFile at size 50 and prints its
> type.  It then increases the size to the threshold at which point
> "self" is changed to being a TemporaryFile.

Changed how ?-)

>  It seems that the next
> call correctly uses the write() method of TemporaryFile (since we don't
> see "changing type" in the output).  However, type(tmp) still equals
> TempFile.  Not only that, tmp can still access the method dummy() that
> exists only in TempFile.
> 
> #!/usr/bin/env python
> from StringIO import StringIO
> import tempfile
> 
> class TempFile(StringIO, object):
>     """A temporary file implementation that uses memory unless
>        either capacity is breached or fileno is requested, at which
>        point a real temporary file will be created and the relevant
>        details returned
>     """
>     def __init__(self, buffer, capacity):
>         """Creates a TempFile object containing the specified buffer.
>         If capacity is specified, we use a real temporary file once the
> 
>         file gets larger than that size.  Otherwise, the data is stored
> 
>         in memory.
>         """
>         self.capacity = capacity
>         if len(buffer) > capacity:
>             self = tempfile.TemporaryFile()

assigning to 'self' in a method doesn't impact the object itself - it 
only rebinds the *local* name 'self' for the rest of the block.

If you want to change the class of an object, you must assign to 
self.__class__ - but, while perfectly legal (and in fact the simplest 
possible implementation of the state pattern in Python), it may be 
somewhat risky.

(snip)
>      def write(self, str):
>         self.seek(0, 2)  # find end of file
>         if((self.tell() + len(str)) >= self.capacity):
>             print "changing type"
>             flo = tempfile.TemporaryFile()
>             flo.write(self.getvalue())
>             self = flo
>             print type(self)

Same comment here.

(snip)

Now for a practical solution : what you want is the strategy pattern.


from StringIO import StringIO
from tempfile import TemporaryFile
import sys

class TempFile(object):
     """A temporary file implementation that uses memory unless
        either capacity is breached or fileno is requested, at which
        point a real temporary file will be created and the relevant
        details returned
     """

     _strategies = (StringIO, TemporaryFile)

     def __init__(self, buffer, capacity):
         """Creates a TempFile object containing the specified buffer.

         If capacity is specified, we use a real temporary file once the
         file gets larger than that size.  Otherwise, the data is stored
         in memory.
         """
         self.capacity = capacity
         self._delegate = self._strategies[len(buffer) > self.capacity]()
         self.write(buffer)

     def write(self, value):
         print >> sys.stderr, \
                  "about to write %d more characters" % len(value)
         if isinstance(self._delegate, self._strategies[0]):
             len_value = len(value)
             if len_value >= self.capacity:
                 needs_new_strategy = True
             else:
                 self.seek(0, 2)  # find end of file
                 needs_new_strategy = \
                   self.tell() + len_value >= self.capacity

             if needs_new_strategy:
                 print >> sys.stderr, "changing strategy"
                 new_delegate = self._strategies[1]()
                 new_delegate.write(self.getvalue())
                 self._delegate = new_delegate

         self._delegate.write(value)


     def __getattr__(self, name):
         # Takes care of automatic delegation,
         # customize this according to your needs.
         # Hint : this will only be called if normal lookup
         # failed, so to control access to any _delegate's method,
         # just implement a method with same name
         try:
             return getattr(self._delegate, name)
         except AttributeError:
             # hide the delegation
             e = "object '%s' has no attribute '%s'" \
                 % (self.__class__.__name__, name)
             raise AttributeError(e)


if __name__ == "__main__":
     print "testing tempfile:"
     tmp = TempFile("", 100)
     ten_chars = "1234567890"
     tmp.write(ten_chars * 5)
     print "tmp < 100: ", tmp._delegate.__class__.__name__
     tmp.write(ten_chars * 5)
     print "tmp == 100: " , tmp._delegate.__class__.__name__
     tmp.write("the last straw")
     print "tmp > 100: " , tmp._delegate.__class__.__name__



More information about the Python-list mailing list