Resource Management in Python

by Oliver Schoenborn





Listing One



class A:

    def __init__(self):

        self.b = B(self)

    def __del__(self):

        print "goodbye"

class B:

    def __init__(self, a):

        self.a = a

aa = A()

del aa



Listing Two



while 1:

    aa = A()





Listing Three



# someScript.py

def run_application():

    ...

def handle_exception():

    ...

try:

    run_application()

except: # catch all

    handle_exception()

    # *attempt* to free as many remaining as possible

    import sys

    sys.exc_clear()

    sys.exc_traceback = None

    sys.last_traceback = None





Listing Four



# C++ code:

void func() {

    Lock lock;

    do_stuff();

}

# "equivalent" Python code:

def func():

    lock = Lock()

    do_stuff()





Listing Five



def func():

    lock = Lock()

    try:

        do_stuff()

    finally:

        lock.release()





Listing Six



# extreme danger:

open('somefile.txt','w').write(contents)

# runtime error in exception handler:

try:

    ff = open('somefile.txt','w')

    ff.write(contents)

finally:

    ff.close() #bang!! ff undefined

# multithreaded:

ff = open('somefile.txt','w')

# if exception raised before getting

# into the try-finally clause: bang!

try:

    ff.write(contents)

finally:

    ff.close()





Listing Seven



def func():

    lock = Lock()

    # unfortunately not allowed:

    # try:

    #     do_stuff()

    # except MyExcept:

    #     undo_stuff()

    # finally:

    #     lock.release()



    # instead, nesting is necessary:

    try:

        try:

            do_stuff()

        except MyExcept:

            undo_stuff()

    finally:

        lock.release()





Listing Eight



def func():

    with lock = Lock():

        do_stuff()





Listing Nine



# detscope.py

"""

Example use: a function, funcWCriticalRes(), creates two critical resources,

of type CritRes1 and CritRes2, and you want those resources to be released

regardless of control flow in function:

  import detscope.py

  def funcWCriticalRes():

      critres1 = CritRes1()

      critres2 = CritRes2()

      use_res(res1, res2)

      if something:

          return # early return

      ...

  funcWCriticalRes = ScopeGuarded(funcWCriticalRes)



  class CritRes1(NeedsFinalization):

      def __init__(self, ...):

          ...

      def _finalize(self):

          ...

  class CritRes2(NeedsFinalization):

      def __init__(self, ...):

          ...

      def _finalize(self):

          ...

"""

import sys

def ScopeGuarded(func):

    return lambda *args, **kwargs: ScopeGuardian(func, *args, **kwargs)

_funcStack = []

class NeedsFinalization:

    def __init__(self):

        print '\n%s: being created' % repr(self)

        self.__finalized = False

        try: _funcStack[-1].append(self)

        except IndexError:

            raise RuntimeError, "Forgot to scope-guard function? "

    def finalizeMaster(self):

        """Derived classes MUST define a self._finalize() method,

        where they do their finalization for scope exit."""

        print '%s: Finalize() being called' % repr(self)

        self._finalize()

        self.__finalized = True

    def __del__(self):

        """This just does some error checking, probably want to remove

        in production in case derived objects involved in cycles."""

        try:

            problem = not self.__finalized

        except AttributeError:

            msg = '%s: NeedsFinalization.__init__ not called for %s' \

                  % (repr(self), self.__class__)

            raise RuntimeError, msg

        if not problem:

            print '%s: Finalized properly' % repr(self)

        else:

            print 'Forgot to scope-guard func?'

def ScopeGuardian(func, *args, **kwargs):

    try:

        scopedObjs = []

        _funcStack.append(scopedObjs)

        func(*args, **kwargs)

    finally:

        _funcStack.pop()

        if scopedObjs != []:

            scopedObjs.reverse() # destroy in reverse order from creation

            for obj in scopedObjs:

                obj.finalizeMaster()





Listing Ten



import weakref

class Foo:

    def __str__(self):

        return "I'm a Foo and I'm ok"

    def __del__(self):

        print "obj %s: I was a Foo and now I'm dead" % id(self)

def noticeDeath(wr):

    print "weakref %s: weakly ref'd obj has died" % id(wr)

yourObj = Foo()

wr = weakref.ref(yourObj, noticeDeath)

print 'weakref %s -> obj %s: %s' % (id(wr), id(wr()), wr())



del yourObj

assert wr() is None

# output:

#   weakref 17797504 -> obj 17794632: I'm a Foo and I'm ok

#   weakref 17797504: weakly ref'd obj has died

#   obj 17794632: I was a Foo and now I'm dead





Listing Eleven



import weakref

class A:

    def __init__(self):

        self.b = B(self)

    def __del__(self):

        print "goodbye"

class B:

    def __init__(self, a):

        self.a = weakref.ref(a)

aa = A()

del aa





Listing Twelve



# testCycle.py

from sys import getrefcount

import gc

class CycleContainer:

    def __init__(self, instName):

        self.instName = instName

        self.cycle = Cycle(self)

        print "Constructed a CycleContainer named '%s'" % instName

    def refs(self):

        """Get number of references to self. The 3 was

        determined experimentally, so method returns

        expected number of references."""

        return getrefcount(self)-3

    def __del__(self):

        """Will prevent CycleContainer instance from being destroyed by gc"""

        print "CycleContainer '%s' being finalized" % self.instName

class Cycle:

    def __init__(self, containerOfSelf):

        self.container = containerOfSelf

def checkgc():

    gc.collect()

    return gc.garbage





Listing Thirteen



>>>>>> from testCycle import CycleContainer, Cycle, checkgc

>>>>>> aa= CycleContainer('one')



Constructed a CycleContainer named 'one'



>>>>>> aa.refs()



1



>>>>>> aa.cycle = Cycle(aa)

>>>>>> aa.refs()



2



>>>>>> checkgc()



[]



>>>>>> del aa

>>>>>> checkgc()



[<testCycle.CycleContainer instance at 0x00984CB0>]



>>>>>> checkgc()[0].refs()



2



>>>>>> bad = checkgc()[0]

>>>>>> del bad.cycle

>>>>>> bad.refs()



2



>>>>>> checkgc()



[<testCycle.CycleContainer instance at 0x00984CB0>]



>>>>>> del checkgc()[:]

>>>>>> checkgc()



[]



>>>>>> bad.refs()



1



>>>>>> del bad



CycleContainer 'one' being finalized







5



