special attributes (and greetings from a new list user)

cdh cdh at mail.ala.net
Wed Apr 5 22:41:51 EDT 2000


Hi,
My name is Chris Hickman and though I've been using python for a while now,
I'm new to the list. I've been a member on another prog-lang list before and
found it very fun and interesting. 

Below is a module I'd like to submit for comments. It make's attribute handling
a bit easier as it takes care of __getattr__ and __setattr__ for you. The document
string should explain it fairly well.

Happy python programming,
Christoper D. Hickman

###############################################################################

# Author: Christopher D. Hickman
# Date: 4/3/2000

"""
  Module: spec_attr.py
    Simplifies adding special get and set handlers for attributes.

  Exports:
    class spec_attr:
      Defines the methods __getattr__ and __setattr__ implement magic handlers for special attributes.
      The special attribute must be defined with an _ (underscore) prepended.
      The methods are defined as getattr<special attr> and setattr<special attr>.
      The special attributes will then automatically call their handlers whenever accessed without
      the leading underscore.
      You may also implement "virtual attributes" which are handlers only with no special attribute 
      supporting them.
      See example below for usage.
      
    class spec_attr_declarative:
      Same as the spec_attr class except it disallows implicit attribute creation.

    class SpecialAttributeError(AttributeError):
      Error class raised by spec_attr_declarative.__setattr__() when attempting to implicitly 
      create a new attribute by assigning a value to a non-existant attribute. 

  Example:
    >>> from spec_attr import spec_attr
    >>> class foo(spec_attr):
    ...  _x = 0 # special attribute x
    ...  _y = 0
    ...  _z = 0
    ...  count = 5
    ...  def getattr_x(self): # special get method for x
    ...    print "x attribute of %s was read" % repr(self) # verbalize all accesses to x
    ...    return self._x # caveat: you must use the underscore prepended form for all "gets"
    ...                   #         of a special attribute when defining it's get method.
    ...                   #         otherwise, infinite recursion will ensue.
    ...  def setattr_x(self, value):
    ...    print "x attribute of %s was changed from %s to %s" % (self, self._x, value)
    ...    self._x = value # same caveat applies for sets in set methods as for gets in get methods.
    ...  def setattr_y(self, value):
    ...    if value % 2:
    ...      raise "y must be even!"
    ...    self._y = value
    ...  def getattr_xyz(self): # a virtual get method (there's no xyz attribute)
    ...    return self.x, self.y, self.z
    ...  def setattr_xyz(self, value): # a virtual set method
    ...    self.x, self.y, self.z = value
    ...  def getattr_decr_count(self):
    ...    val = self.count
    ...    self.count = self.count - 1
    ...    return val
    ...  
    >>> a = foo()
    >>> a.x
    x attribute of <__main__.foo instance at 10e2bf0> was read
    0
    >>> a.x = 12
    x attribute of <__main__.foo instance at 10e2bf0> was changed from 0 to 12
    >>> a.xyz = (1,2,3)
    x attribute of <__main__.foo instance at 10e2bf0> was changed from 12 to 1
    >>> a.xyz
    x attribute of <__main__.foo instance at 10e2bf0> was read
    (1, 2, 3)
    >>> while a.decr_count:
    ...  print a.count
    ...  
    4
    3
    2
    1
    0
    >>> a.xyz = (6, 7, 8)
    x attribute of <__main__.foo instance at 10e2bf0> was changed from 1 to 6
    Traceback (innermost last):
      File "<interactive input>", line 1, in ?
      .
      .
      .
      File "<interactive input>", line 16, in setattr_y
    y must be even!
    >>> 

"""

class SpecialAttributeError(AttributeError):
  pass

class spec_attr_declarative:

  # important things to remember about __getattr__: 
  #   1. __getattr__ is *only* called when a "real" attr is not present
  #   2. hasattr() can cause recursion to __getattr__,  and traps any exceptions generated
  def __getattr__(self, attr):
    # see if there is a method defined to handle gets for attr
    if attr[:8] == 'getattr_':
      # we've either recursed because getattr_<attr> doesn't exist, (in which case the exception
      # generated here is trapped by the hasattr() call from the caller), or we tried to call
      # the non-existant getattr_<attr> directly.
      raise AttributeError, attr
    if hasattr(self, 'getattr_' + attr):
      return eval ('self.getattr_' + attr + '()') 
    # if no method is present, try to return the value of the special attribute behind the one
    # we're trying to access
    if attr[:2] == '__':
      # we're probably checking for ('_' + _<attr>) due to recursive calls, a private attribute, 
      # or an undefined instance of one of python's "magic" attributes (like __repr__), 
      # so give up.
      raise AttributeError, attr
    if hasattr(self, '_' + attr):
      return eval ('self._' + attr)
    # otherwise, it simply doesn't exist
    raise AttributeError, attr
 
  # unlike __getattr__, __setattr__ is always called
  def __setattr__(self, attr, value):
    # if it's in the __dict__ it's not special, so process it quickly
    if self.__dict__.has_key(attr):
      self.__dict__[attr] = value
      return
    # see if there is a method defined to handle sets for attr
    if hasattr(self, 'setattr_' + attr):
      exec ('self.setattr_' + attr + '(value)')
      return
    # if no method is present, try to set the value of the special attribute behind the one
    # we're trying to set
    if hasattr(self, '_' + attr):
      exec ('self._' + attr + ' = value')
      return
    # otherwise attr could be present in the <instance>.__class__.__dict__ (or somewhere), in which 
    # case hasattr() will return true even though the above tests failed. Create a real attribute 
    # in the instance's __dict__. (this mirrors the default behavior in this case when no __setattr__ 
    # method is defined)
    if hasattr(self, attr):
      self.__dict__[attr] = value
      return
    # for the declarative version, don't allow de-facto creation of a new attribute. (explicitly use 
    # __dict__ to circumvent this)
    raise SpecialAttributeError, "Attempted to create a new attribute %s for the declarative class instance %s" % (attr, repr(self))

class spec_attr(spec_attr_declarative):
  def __setattr__(self, attr, value):
    try:
      spec_attr_declarative.__setattr__(self, attr, value)
    except SpecialAttributeError:
      # allow creation of new normal attributes (without explicitly using __dict__) in the 
      # non-declaritive version.
      self.__dict__[attr] = value 


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-list/attachments/20000405/d8abbf92/attachment.html>


More information about the Python-list mailing list