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.5. Delegating Automatically as an Alternative to Inheritance


Credit: Alex Martelli, Raymond Hettinger


Problem


You'd like to
inherit from a class or type, but you need some tweak that
inheritance does not provide. For example, you want to selectively
hide some of the base class' methods, which
inheritance doesn't
allow.


Solution


Inheritance
is quite handy, but it's not all-powerful. For
example, it doesn't let you hide methods or other
attributes supplied by a base class. Containment with automatic
delegation is often a good alternative. Say, for example, you need to
wrap some objects to make them read-only; thus preventing accidental
alterations. Therefore, besides stopping attribute-setting, you also
need to hide mutating methods. Here's a way:

# support 2.3 as well as 2.4
try: set
except NameError: from sets import Set as set
class ROError(AttributeError): pass
class Readonly:
# there IS a reason to NOT subclass object, see Discussion
mutators = {
list: set('''_ _delitem_ _ _ _delslice_ _ _ _iadd_ _ _ _imul_ _
_ _setitem_ _ _ _setslice_ _ append extend insert
pop remove sort'''.split( )),
dict: set('''_ _delitem_ _ _ _setitem_ _ clear pop popitem
setdefault update'''.split( )),
}
def _ _init_ _(self, o):
object._ _setattr_ _(self, '_o', o)
object._ _setattr_ _(self, '_no', self.mutators.get(type(o), ( )))
def _ _setattr_ _(self, n, v):
raise ROError, "Can't set attr %r on RO object" % n
def _ _delattr_ _(self, n):
raise ROError, "Can't del attr %r from RO object" % n
def _ _getattr_ _(self, n):
if n in self._no:
raise ROError, "Can't get attr %r from RO object" % n
return getattr(self._o, n)

Code using this class Readonly can easily add other
wrappable types with Readonly.mutators[sometype] =
the_mutators
.


Discussion



Automatic delegation, which the special
methods _ _getattr_ _, _ _setattr_
_
, and _ _delattr_ _ enable us to
perform so smoothly, is a powerful, general technique. In this
recipe, we show how to use it to get an effect that is almost
indistinguishable from subclassing while hiding some names. In
particular, we apply this quasi-subclassing to the task of wrapping
objects to make them read-only. Performance isn't
quite as good as it might be with real inheritance, but we get better
flexibility and finer-grained control as compensation.

The fundamental idea is that each instance of our class holds an
instance of the type we are wrapping (i.e., extending and/or
tweaking). Whenever client code tries to get an attribute from an
instance of our class, unless the attribute is specifically defined
there (e.g., the mutators dictionary in class
Readonly), _ _getattr_ _
TRansparently shunts the request to the wrapped instance after
appropriate checks. In Python, methods are also attributes, accessed
in just the same way, so we don't need to do
anything different to access methods. The _ _getattr_
_
approach used to access data attributes works for methods
just as well.

This is where the comment in the recipe about there being a specific
reason to avoid subclassing object comes in. Our _ _getattr_
_
based approach does work on special
methods too, but only for instances of old-style classes. In
today's object model, Python operations access
special methods on the class, not on the instance. Solutions to this
issue are presented next in Recipe 6.6 and in Recipe 20.8. The approach adopted in
this recipemaking class Readonly old style,
so that the issue can be locally avoided and delegated to other
recipesis definitely not recommended for
production code. I use it here only to keep this recipe shorter and
to avoid duplicating coverage that is already amply given elsewhere
in this cookbook.

_ _setattr_ _ plays a role similar to _
_getattr_ _
, but it gets called when client code sets an
instance attribute; in this case, since we want to make a read-only
wrapper, we simply forbid the operation. Remember, to avoid
triggering _ _setattr_ _ from inside the methods
you code, you must never code normal self.n = v
statements within the methods of classes that have _
_setattr_ _
. The simplest workaround is to delegate the
setting to class object, just like our class
Readonly does twice in its _ _init_
_
method. Method _ _delattr_ _ completes
the picture, dealing with any attempts to delete attributes from an
instance.

Wrapping by automatic delegation does not
work well with client or framework code that, one way or another,
does type-testing. In such cases, the client or framework code is
breaking polymorphism and should be rewritten. Remember not to use
type-tests in your own client code, as you probably do not need them
anyway. See Recipe 6.13 for
better alternatives.


In old versions of Python,
automatic delegation was even more prevalent, since you could not
subclass built-in types. In modern Python, you can inherit from
built-in types, so you'll use automatic delegation
less often. However, delegation still has its placeit is just
a bit farther from the spotlight. Delegation is more flexible than
inheritance, and sometimes such flexibility is invaluable. In
addition to the ability to delegate selectively (thus effectively
"hiding" some of the attributes),
an object can delegate to different subobjects over time, or to
multiple subobjects at one time, and inheritance
doesn't offer anything comparable.

Here is an example of delegating to multiple specific subobjects. Say
that you have classes that are chock full of
"forwarding methods", such as:

class Pricing(object):
def _ _init_ _(self, location, event):
self.location = location
self.event = event
def setlocation(self, location):
self.location = location
def getprice(self):
return self.location.getprice( )
def getquantity(self):
return self.location.getquantity( )
def getdiscount(self):
return self.event.getdiscount( )
and many more such methods

Inheritance is clearly not applicable because an instance of
Pricing must delegate to
specific location and
event instances, which get passed at initialization
time and may even be changed. Automatic delegation to the rescue:

class AutoDelegator(object):
delegates = ( )
do_not_delegate = ( )
def _ _getattr_ _(self, key):
if key not in do_not_delegate:
for d in self.delegates:
try:
return getattr(d, key)
except AttributeError:
pass
raise AttributeError, key
class Pricing(AutoDelegator):
def _ _init_ _(self, location, event):
self.delegates = [location, event]
def setlocation(self, location):
self.delegates[0] = location

In this case, we do not delegate the setting and deletion of
attributes, only the getting of attributes (and nonspecial methods).
Of course, this approach is fully applicable only when the methods
(and other attributes) of the various objects to which we want to
delegate do not interfere with each other; for example,
location must not have a
getdiscount method; otherwise, it would preempt the
delegation of that method, which is intended to go to
event.


If a class that does lots of
delegation has a few such issues to solve, it can do so by explicitly
defining the few corresponding methods, since _ _getattr_
_
enters the picture only for attributes and methods that
cannot be found otherwise. The ability to hide
some attributes and methods that are supplied by a delegate, but the
delegator does not want to expose, is supported through attribute
do_not_delegate, which any subclass may override.
For example, if class Pricing wanted to hide a
method setdiscount that is supplied by, say,
event, only a tiny change would be required:

class Pricing(AutoDelegator):
do_not_delegate = ('set_discount',)

while all the rest remains as in the previous snippet.


See Also


Recipe 6.13; Recipe 6.6; Recipe 20.8; Python in a
Nutshell
chapter on OOP; PEP 253 (http://www.python.org/peps/pep-0253l) for
more details about Python's current (new-style)
object model.

/ 394