O'Reilly logo

Python Cookbook by David Ascher, Alex Martelli

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Implementing the Null Object Design Pattern

Credit: Dinu C. Gherman

Problem

You want to reduce the need for conditional statements in your code, particularly the need to keep checking for special cases.

Solution

The usual marker for “there’s nothing here” is None, but we may be able to do better than that:

class Null:
    """ Null objects always and reliably "do nothing." """

    def _ _init_ _(self, *args, **kwargs): pass
    def _ _call_ _(self, *args, **kwargs): return self
    def _ _repr_ _(self): return "Null(  )"
    def _ _nonzero_ _(self): return 0

    def _ _getattr_ _(self, name): return self
    def _ _setattr_ _(self, name, value): return self
    def _ _delattr_ _(self, name): return self

Discussion

An instance of the Null class can replace the primitive value None. Using this class, you can avoid many conditional statements in your code and can often express algorithms with little or no checking for special values. This recipe is a sample implementation of the Null Object design pattern (see “The Null Object Pattern”, B. Woolf, Pattern Languages of Programming, PLoP 96, September 1996).

This recipe’s Null class ignores all parameters passed when constructing or calling instances and any attempt to set or delete attributes. Any call or attempt to access an attribute (or a method, since Python does not distinguish between the two and calls _ _getattr_ _ either way) returns the same Null instance (i.e., self, since there’s no reason to create a new one). For example, if you have a computation such as:

def compute(x, y):
    try: "lots of computation here to return some appropriate object"
    except SomeError: return None

and you use it like this:

for x in xs:
    for y in ys:
        obj = compute(x, y)
        if obj is not None:
            obj.somemethod(y, x)

you can usefully change the computation to:

def compute(x, y):
    try: "lots of computation here to return some appropriate object"
    except SomeError: return Null(  )

and thus simplify it as:

for x in xs:
    for y in ys:
        compute(x, y).somemethod(y, x)

Thus, you don’t need to check whether compute has returned a real result or an instance of Null. Even in the latter case, you can safely and innocuously call on it whatever method you want.

Python calls _ _getattr_ _ for special methods as well. This means that you may have to take some care and customize Null to your application’s needs, either directly in the class’s sources or by subclassing it appropriately. For example, with this recipe’s Null, any comparison between Null instances, even a==a, returns a Null instance and evaluates as false. (Note that we’ve had to explicitly define _ _nonzero_ _ for this purpose, since _ _nonzero_ _ must return an int.) If this is a problem for your purposes, you must define _ _eq_ _ (in Null itself or in an appropriate subclass) and implement it appropriately. Similar delicate considerations apply to several other special methods.

The goal of Null objects is to provide an intelligent replacement for the often-used primitive value None in Python (Null or null pointers in other languages). These “nobody lives here” markers are used for many purposes, including the important case in which one member of a group of otherwise similar elements is special. Usually, this usage results in conditional statements to distinguish between ordinary elements and the primitive null (e.g., None) value, but Null objects help you avoid that.

Among the advantages of using Null objects are the following:

  • Superfluous conditional statements can be avoided by providing a first-class object alternative for the primitive value None, thereby improving code readability.

  • They can act as a placeholder for objects whose behavior is not yet implemented.

  • They can be used polymorphically with instances of any other class.

  • They are very predictable.

To cope with the disadvantage of creating large numbers of passive objects that do nothing but occupy memory space, the Null Object pattern is often combined with the Singleton pattern (see Recipe 5.22), but this recipe does not explore that combination.

See Also

“The Null Object Pattern”, B. Woolf, Pattern Languages of Programming, PLoP 96, September 1996, http://www.cs.wustl.edu/~schmidt/PLoP-96/woolf1.ps.gz.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required