Cute 'proxy' with __new__

jerf at compy.attbi.com jerf at compy.attbi.com
Mon Dec 30 21:55:54 EST 2002


I have a hierarchy of object types that I have created, and I didn't want
to include in the hierarchy the idea of serialization. The classes are
very diverse and it's just impossible to write any sort of generic method
that does any justice to serializing them properly.

Ideally (IMHO), one writes some sort of proxy class which handles the
loading and saving of the object, and delegates all the methods of the
loaded object such that it acts like the original. However, I was having
trouble writing the notoriously tricky __*attr*__ methods correctly, due
to interactions with the other parts of my program. (I have a lot of
requirements and as usual pushing too much in one direction causes twenty
other things to pop out. ;-) )

I came up with this neat idea for a "proxy" class in Python 2.2 which I
thought might be worth sharing.

--------

import cPickle
import atexit

class FileObjProxy(object):
    """FileObjProxy is a demo of using __new__ to transparently
    'proxy' access to a file-based object. It is not this specific
    implementation that is interesting, it is the idea.

    Other ideas:
      Threadsafety (lock the __new__)
      Be more careful with __del__
      Look for a non-__del__ signal to save
      Add an optional hook to 'serialize' and 'de-serialize' the
        object if it isn't directly picklable."""

    # Since we're referring to a specific file, it is quite likely a
    # good idea to always return the same object instance for the
    # given file, otherwise you could have race conditions amongst the
    # various instances.
    loadedFiles = {}
    def __new__(cls, filename, init = None):
        try: return cls.loadedFiles[filename]
        except: pass
        
        self = object.__new__(cls)

        self.filename = filename
        self.init = init

        try:
            f = file(filename, "r")
            objstr = f.read()
            f.close()
            
            obj = cPickle.loads(objstr)

            cls.loadedFiles[filename] = obj
            self.obj = obj
            atexit.register(self.saveObj)
            return obj
        except:
            if callable(init):
                obj = init()
            else:
                obj = init

            cls.loadedFiles[filename] = obj
            self.obj = obj
            atexit.register(self.saveObj)
            return obj

    def saveObj(self):
        f = file(self.filename, "w")
        f.write(cPickle.dumps(self.obj))
        f.close()

# example
a = []
b = FileObjProxy("listInAFile", a)

# every time you execute this file, the listInAFile will get another 33
b.append(33)

--------

Lightly tested.

Obviously this only really works on mutable, picklable objects, but that
covers a lot of ground.

Up until recently, I had no reason to care about the __new__/__init__
disconnection, but I am rapidly coming to appreciate it a lot. This is
*extremely* generic. Since there is literally no way to tell the
"proxied"object from the real thing, since it *is* the real thing, it is
maximally compatible with a non-proxied object, which was necessary in my
app.

In fact, this is just a specific implementation of a general idea; in my
real program, I don't use atexit, I have hooks to serialize and
deserialize the object before the pickler runs (necessary since my objs
use weakrefs in them), the exception handling is better (no bare
except:'s), and I tie into a more general data structure, not
files, but I thought this was an interesting idea and I wanted to keep it
as simple as possible. I'm not necessarily recommending this specific
implementation; in fact I doubt atexit is the best idea and the file
handling is deliberately pretty stupid to save space.

With this, I completely dissociate serialization from the object hierarchy
I have, so much so that the serialization function is usuable
transparently on anything, not just my prepared objects.

Anybody know what to call it? It's not a proxy, that's just the closest
name I could think for it, since you *use* it just like a proxy. Or other
thoughts? Maybe everybody already had this idea, I dunno. ;-)




More information about the Python-list mailing list