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

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

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

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

David Ascher, Alex Martelli, Anna Ravenscroft

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

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

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







Recipe 20.3. Aliasing Attribute Values


Credit: Denis S. Otkidach


Problem


You want to use an attribute name as an alias for another one, either
just as a default value (when the attribute was not explicitly set),
or with full setting and deleting abilities too.


Solution


Custom descriptors are the right tools for this task:

class DefaultAlias(object):
''' unless explicitly assigned, this attribute aliases to another. '''
def _ _init_ _(self, name):
self.name = name
def _ _get_ _(self, inst, cls):
if inst is None:
# attribute accessed on class, return `self' descriptor
return self
return getattr(inst, self.name)
class Alias(DefaultAlias):
''' this attribute unconditionally aliases to another. '''
def _ _set_ _(self, inst, value):
setattr(inst, self.name, value)
def _ _delete_ _(self, inst):
delattr(inst, self.name)


Discussion


Your class instances sometimes have attributes whose default value
must be the same as the current value of other attributes but may be
set and deleted independently. For such requirements, custom
descriptor DefaultAlias, as presented in this
recipe's Solution, is just the ticket. Here is a toy
example:

class Book(object):
def _ _init_ _(self, title, shortTitle=None):
self.title = title
if shortTitle is not None:
self.shortTitle = shortTitle
shortTitle = DefaultAlias('title')
b = Book('The Life and Opinions of Tristram Shandy, Gent.')
print b.shortTitle
# emits: The Life and Opinions of Tristram Shandy, Gent.
b.shortTitle = "Tristram Shandy"
print b.shortTitle
# emits: Tristram Shandy
del b.shortTitle
print b.shortTitle
# emits: The Life and Opinions of Tristram Shandy, Gent.

DefaultAlias is not what is
technically known as a data descriptor class
because it has no _ _set_ _ method. In practice,
this means that, when we assign a value to an instance attribute
whose name is defined in the class as a
DefaultAlias, the instance records the attribute
normally, and the instance attribute shadows the
class attribute. This is exactly what's happening in
this snippet after we explicitly assign to
b.shortTitlewhen we del
b.shortTitle
, we remove the
per-instance attribute, uncovering the per-class
one again.

Custom descriptor class Alias is a simple variant of
class DefaultAlias, easily obtained by inheritance.
Alias aliases one attribute to another, not just
upon accesses to the attribute's value (as
DefaultAlias would do), but also upon all operations
of value setting and deletion. It easily achieves this by being a
"data descriptor" class, which
means that it does have a _ _set_ _ method.
Therefore, any assignment to an instance attribute whose name is
defined in the class as an Alias gets intercepted by
Alias' _ _set_ _
method. (Alias also defines a _ _delete_
_
method, to obtain exactly the same effect upon attribute
deletion.)

Alias can be quite useful when you want to evolve a
class, which you made publicly available in a previous version, to
use more appropriate names for methods and other attributes, while
still keeping the old names available for backwards compatibility.
For this specific use, you may even want a version that emits a
warning when the old name is used:

import warnings
class OldAlias(Alias):
def _warn(self):
warnings.warn('use %r, not %r' % (self.name, self.oldname),
DeprecationWarning, stacklevel=3)
def _ _init_ _(self, name, oldname):
super(OldAlias, self)._ _init_ _(name)
self.oldname = oldname
def _ _get_ _(self, inst, cls):
self._warn( )
return super(OldAlias, self)._ _get_ _(inst, cls)
def _ _set_ _(self, inst, value):
self._warn( )
return super(OldAlias, self)._ _set_ _(inst, value)
def _ _delete_ _(self, inst):
self._warn( )
return super(OldAlias, self)._ _delete_ _(inst)

Here is a toy example of using OldAlias:

class NiceClass(object):
def _ _init_ _(self, name):
self.nice_new_name = name
bad_old_name = OldAlias('nice_new_name', 'bad_old_name')

Old code using this class may still refer to the instance attribute
as bad_old_name, preserving backwards compatibility;
when that happens, though, a warning message is presented about the
deprecation, encouraging the old code's author to
upgrade the code to use nice_new_name instead. The
normal mechanisms of the warnings module of the
Python Standard Library ensure that, by default, such warnings are
output only once per occurrence and per run of a program, not
repeatedly. For example, the snippet:

x = NiceClass(23)
for y in range(4):
print x.bad_old_name
x.bad_old_name += 100

emits:

xxx.py:64: DeprecationWarning: use 'nice_new_name', not 'bad_old_name'
print x.bad_old_name
23
xxx.py:65: DeprecationWarning: use 'nice_new_name', not 'bad_old_name'
x.bad_old_name += 100
123
223
323

The warning is printed once per line using the bad old name, not
repeated again and again as the for loop iterates.


See Also


Custom descriptors are best documented on Raymond
Hettinger's web page: http://users.rcn.com/python/download/Descriptor;
Library Reference and Python in a
Nutshell
docs about the warnings
module.


/ 394