Recipe20.3.Aliasing Attribute Values


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.htm; Library Reference and Python in a Nutshell docs about the warnings module.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net