Source

Python 3 Patterns & Idioms / src / InitializationAndCleanup.rst

Full commit

Initialization and Cleanup

Initialization

Constructor Calls

Automatic base-class constructor calls.

Calling the base-class constructor first, how to do it using super(), why you should always call it first even if it's optional when to call it.

__new__() vs. __init__()

Static Fields

An excellent example of the subtleties of initialization is static fields in classes.

::
>>> class Foo(object):
...   x = "a"
...
>>> Foo.x
'a'
>>> f = Foo()
>>> f.x
'a'
>>> f2 = Foo()
>>> f2.x
'a'
>>> f2.x = 'b'
>>> f.x
'a'
>>> Foo.x = 'c'
>>> f.x
'c'
>>> f2.x
'b'
>>> Foo.x = 'd'
>>> f2.x
'b'
>>> f.x
'd'
>>> f3 = Foo()
>>> f3.x
'd'
>>> Foo.x = 'e'
>>> f3.x
'e'
>>> f2.x
'b'

If you assign, you get a new one. If it's modifiable, then unless you assign you are working on a singleton. So a typical pattern is:

class Foo:
    something = None # Static: visible to all classes
    def f(self, x):
        if not self.something:
            self.something = [] # New local version for this object
        self.something.append(x)

This is not a serious example because you would naturally just initialize something in Foo's constructor.

Cleanup

Cleanup happens to globals by setting them to None (what about locals?). Does the act of setting them to None cause __del__ to be called, or is __del__ called by Python before a global is set to None?

Consider the following:

class Counter:
    Count = 0   # This represents the count of objects of this class
    def __init__(self, name):
        self.name = name
        print name, 'created'
        Counter.Count += 1
    def __del__(self):
        print self.name, 'deleted'
        Counter.Count -= 1
        if Counter.Count == 0:
            print 'Last Counter object deleted'
        else:
            print Counter.Count, 'Counter objects remaining'

x = Counter("First")
del x

Without the final del, you get an exception. Shouldn't the normal cleanup process take care of this?

From the Python docs regarding __del__:

Warning: Due to the precarious circumstances under which __del__() methods are invoked, exceptions that occur during their execution are ignored, and a warning is printed to sys.stderr instead. Also, when __del__() is invoked in response to a module being deleted (e.g., when execution of the program is done), other globals referenced by the __del__() method may already have been deleted. For this reason, __del__() methods should do the absolute minimum needed to maintain external invariants.

Without the explicit call to del, __del__ is only called at the end of the program, Counter and/or Count may have already been GC-ed by the time __del__ is called (the order in which objects are collected is not deterministic). The exception means that Counter has already been collectd. You can't do anything particularly fancy with __del__.

There are two possible solutions here.

1. Use an explicit finalizer method, such as close() for file objects.

  1. Use weak references.

Here's an example of weak references, using a WeakValueDictionary and the trick of mapping id(self) to self:

from weakref import WeakValueDictionary

class Counter:
    _instances = WeakValueDictionary()
    @property
    def Count(self):
        return len(self._instances)

    def __init__(self, name):
        self.name = name
        self._instances[id(self)] = self
        print name, 'created'

    def __del__(self):
        print self.name, 'deleted'
        if self.Count == 0:
            print 'Last Counter object deleted'
        else:
            print self.Count, 'Counter objects remaining'

x = Counter("First")

Now cleanup happens properly without the need for an explicit call to del.

Further Reading