Search code examples
pythonpython-3.xcontextmanager

python3 singleton pattern with "with" statement


I want to write a class with singleton pattern to provide some persistent data storage using pickle/dict:

@singleton
class Pdb:
    def __init__(self):
        self.cache = None
        self.dirty = False
        try:
            with open("data.pck","rb") as fp:
                self.cache = pickle.load(fp)
        except FileNotFoundError:
            pass
        except pickle.PickleError:
            pass

        if self.cache is None:
            self.cache = {}

    def flush(self):
        if self.dirty:
            try:
                with open("data.pck","wb") as fp:
                    pickle.dump(self.cache,fp,protocol=4)
            except pickle.PickleError:
                pass
            else:
                self.dirty = False

    def __del__(self): # PROBLEM HERE
        self.flush()

When I was using python 2, I can do it by overriding __del__. But it does not appear to be correct in python 3. How can I do it?

If I do it by "with" statement, I will need to pass the instance to each function that I call:

def func1(db):
    db.set(...)
    func3(db,x1,x2,...)

with Pdb() as db:
    func1(db)
    func2(db)

It is complicated. Is there a pythonic way to do a global scope "with" statement?


Solution

  • If I do it by "with" statement, I will need to pass the instance to each function that I call:

    No, you don't. Just use your singleton:

    # global
    db = Pdb()
    
    # any other context
    with db:
    

    All that is required is that the expression produces a context manager. Referencing a singleton object with __enter__ and __exit__ methods would satisfy that requirement. You can even ignore the __enter__ return value, as I did above. The global will still be available to all your functions, the only thing that changes is that __enter__ and __exit__ will be called at the appropriate locations.

    Note that even in Python 2, you should not rely on __del__ being called. And in the CPython implementation, outside circular references, the rules for when __del__ are called have not changed between Python 2 and 3.