Python Cookbook 2Nd Edition Jun 1002005 [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Python Cookbook 2Nd Edition Jun 1002005 [Electronic resources] - نسخه متنی

David Ascher, Alex Martelli, Anna Ravenscroft

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید


Recipe 6.17. Implementing the Null Object Design Pattern


Credit: Dinu C. Gherman, Holger Krekel


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 placeholder object for
"there's nothing
here" is None, but we may be able
to do better than that by defining a class meant exactly to act as
such a placeholder:

class Null(object):
"" Null objects always and reliably "do nothing." ""
# optional optimization: ensure only one instance per subclass
# (essentially just to save memory, no functional difference)
def _ _new_ _(cls, *args, **kwargs):
if '_inst' not in vars(cls):
cls._inst = type._ _new_ _(cls, *args, **kwargs)
return cls._inst
def _ _init_ _(self, *args, **kwargs): pass
def _ _call_ _(self, *args, **kwargs): return self
def _ _repr_ _(self): return "Null( )"
def _ _nonzero_ _(self): return False
def _ _getattr_ _(self, name): return self
def _ _setattr_ _(self, name, value): return self
def _ _delattr_ _(self, name): return self


Discussion


You can use an instance of the Null class instead of
the primitive value None. By using such an
instance as a placeholder, instead of None, 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 B. Woolf, "The Null Object
Pattern" in Pattern Languages of
Programming
[PLoP 96, September 1996].)

This recipe's Null class ignores
all parameters passed when constructing or calling instances, as well
as 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, calling _ _getattr_ _ either way)
returns the same Null instance (i.e.,
selfno reason to create a new instance).
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 its use down to:

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

The point is that 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. Here is another,
more specific use case:

log = err = Null( )
if verbose:
log = open('/tmp/log', 'w')
err = open('/tmp/err', 'w')
log.write('blabla')
err.write('blabla error')

This obviously avoids the usual kind of
"pollution" of your code from
guards such as if verbose: strewn all over the
place. You can now call log.write('bla'), instead
of having to express each such call as if log is not None:
log.write('bla')
.

In the new object model, Python does not call _ _getattr_
_
on an instance for any special methods needed to perform
an operation on the instance (rather, it looks up such methods in the
instance class' slots). You may have to take care
and customize Null to your
application's needs regarding operations on null
objects, and therefore special methods of the null
objects' class, either directly in the
class' sources or by subclassing it appropriately.
For example, with this recipe's
Null, you cannot index Null
instances, nor take their length, nor iterate on them. If this is a
problem for your purposes, you can add all the special methods you
need (in Null itself or in an appropriate subclass)
and implement them appropriatelyfor example:

class SeqNull(Null):
def _ _len_ _(self): return 0
def _ _iter_ _(self): return iter(( ))
def _ _getitem_ _(self, i): return self
def _ _delitem_ _(self, i): return self
def _ _setitem_ _(self, i, v): return self

Similar considerations apply to several other operations.

The key goal of Null objects is to provide an
intelligent replacement for the often-used primitive value
None in Python. (Other languages represent the
lack of a value using either null or a null pointer.) These
nobody-lives-here markers/placeholders are used for many purposes,
including the important case in which one member of a group of
otherwise similar elements is special. This usage usually results in
conditional statements all over the place 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.

  • Null objects can act as placeholders for objects
    whose behavior is not yet implemented.

  • Null objects can be used polymorphically with
    instances of just about any other class (perhaps needing suitable
    subclassing for special methods, as previously mentioned).

  • Null objects are very predictable.


The one serious disadvantage of Null is that it can
hide bugs. If a function returns None, and the
caller did not expect that return value, the caller most likely will
soon thereafter try to call a method or perform an operation that
None doesn't support, leading to
a reasonably prompt exception and traceback. If the return value that
the caller didn't expect is a Null,
the problem might stay hidden for a longer time, and the exception
and traceback, when they eventually happen, may therefore be harder
to reconnect to the location of the defect in the code. Is this
problem serious enough to make using Null
inadvisable? The answer is a matter of opinion. If your code has
halfway decent unit tests, this problem will not arise; while, if
your code lacks decent unit tests, then using
Null is the least of your
problems. But, as I said, it boils down to a matter of opinions. I
use Null very widely, and I'm
extremely happy with the effect it has had on my productivity.

The Null class as presented in this recipe uses a
simple variant of the "Singleton"
pattern (shown earlier in Recipe 6.15), strictly for optimization
purposesnamely, to avoid the creation of numerous passive
objects that do nothing but take up memory. Given all the previous
remarks about customization by subclassing, it is, of course, crucial
that the specific implementation of
"Singleton" ensures a
separate instance exists for each subclass of
Null that gets instantiated. The number of
subclasses will no doubt never be so high as to eat up substantial
amounts of memory, and anyway this per-subclass distinction can be
semantically crucial.


See Also


B. Woolf, "The Null Object Pattern"
in Pattern Languages of Programming (PLoP 96,
September 1996), http://www.cs.wustl.edu/~schmidt/PLoP-96/woolf1.ps.gz;
Recipe 6.15.

/ 394