Properties in Python
Michal Wallace
sabren at manifestation.com
Wed Jun 20 00:14:14 EDT 2001
On Wed, 20 Jun 2001, Peter Caven wrote:
> So, instead of allowing direct access to the instance attributes, C#
> actually executes 'get' and 'set' methods (if defined) for the attributes.
> This lets a programmer change the behavior of the attributes defined in the
> class (to have side-effects for example) without changing any code written
> by users of the class.
>
> Does anyone have any ideas on how to do this (elegantly) in Python?
> __getattr__ and __setattr__ don't seem to quite do it (ie. inheritance).
>
> Or, do the Python experts here think that this is not really such a good
> idea?
I use a class that overrides __getattr__ and __setattr__ .. It looks
first for a get_XXX or set_XXX method...
The only trick is that you can't store the actual data in __dict__
this way, because __dict__ gets accessed before __getattr__ (at least
it did back in 1.52).. You have to maintain your own dictionary
instead.
The code below is the base class I use. Probably could use a cleanup,
but there you go..
Cheers,
- Michal
------------------------------------------------------------------------
www.manifestation.com www.sabren.net www.linkwatcher.com www.zike.net
------------------------------------------------------------------------
"""
zdc.Object - a base class for building database objects
"""
__ver__ = "$Id: Object.py,v 1.15 2001/02/11 06:20:51 sabren Exp $"
import Record
class Object:
"""
A base class for building business objects.
zdc.Object is a generic base class for business
objects. It has the ability to reject adding
attributes that don't apply to it.
see RecordObject and ModelObject (?) for examples..
"""
__key__="ID" # field that uniquely identifies this object
_links = {}
_locks = []
def __init__(self, key=None, **where):
"""
Don't override this! override _init(), _new() or _fetch() instead.
"""
self.__dict__['_data']={}
self._link()
self._init()
if key is None:
if where:
apply(self._fetch, (), where)
else:
self._new()
else:
self._fetch(key)
self._lock()
### Abstract Protected Methods ##########################
def _link(self):
"""
Set up the links (relationships) between the objects.
This is pretty generic, so you probably don't need to
override it. Just populate class._links.
Structure is:
_links = {
collectionName : (linkClass, params, to, linkClass's, constructor)
}
linkclasses (eg, zdc.LinkSet) Should take the left-hand object
(self, from our perspective) as the first parameter. You don't
have to include self in the param list.
eg:
_links = {
'details': (zdc.LinkSet, SomeDetailClass, 'ID', 'summaryID')
}
"""
for item in self._links.keys():
setattr(self, item, apply(self._links[item][0],
(self,) + tuple(self._links[item][1:])))
def _init(self):
"""
Override this to initialize an Object before
the data is filled in by _new or _fetch.
"""
pass
def _new(self):
"""
Override this to initialize a new instance of an
object.. This is NOT called for objects that already
exist in storage.
"""
pass
def _fetch(self, key=None, **kw):
"""
Override this to initialize fetched instances of an
object. This is ONLY called for objects that already
exist in storage.
"""
pass
def _lock(self):
"""
This just locks the object. You probably don't want to
override it.
"""
self.__dict__["_isLocked"] = 1
### Abstract Public Methods ############################
def save(self):
raise NotImplementedError, "Object.save()"
def delete(self):
raise NotImplementedError, "Object.delete()"
def getEditableAttrs(self):
raise NotImplementedError, "Object.getEditableAttrs()"
def getEditableTuples(self):
raise NotImplementedError, "Object.getEditableTuples()"
### private Methods ####################################
def get__isLocked(self):
"""
Makes sure we're unlocked by default.
we can't put this in __init__ because child classes
might want to do stuff before calling _new() or _save()
and therefore, before __init__..
"""
if not self.__dict__.has_key("_isLocked"):
self.__dict__["_isLocked"] = 0
return self.__dict__["_isLocked"]
def _findmember(self, member):
"""
self._findmember(member) : does self define or inherit member?
with subclasses, It's hard to tell if we have get_XXX,
because we have to iterate through all the base classes.
This ought to be built in to python, but it isn't.. :/
"""
# __bases__ is only the IMMEDIATE parent, so we have
# to climb the tree...
#@TODO: handle multiple inheritence
ancestors = [self.__class__]
while ancestors[-1].__bases__ != ():
ancestors.append(ancestors[-1].__bases__[0])
for ancestor in ancestors:
if member in dir(ancestor):
# grab the first one we find:
return ancestor.__dict__[member]
return None
def __setattr__(self, name, value):
## case A: there's a set_XXX method.
meth = self._findmember('set_' + name)
if meth is not None:
meth(self, value)
## case B: object is locked, so be careful
elif self._isLocked:
## B1: locked (read only) attribute
if name in self._locks:
raise AttributeError, name + " is read-only."
## B2: editable object attribute
elif self._data.has_key(name):
self._data[name] = value
## B3: normal python attribute:
elif self.__dict__.has_key(name):
self.__dict__[name] = value
## B4: Attribute isn't part of the object
else:
raise AttributeError, \
"can't add new attribute '%s' to locked object." \
% name
## case C: unlocked, so do whatever you want
else:
self.__dict__['_data'][name] = value
def __getattr__(self, name):
## case A: the name is already in __dict__
## python (1.52 anyway) won't call __getattr__,
## and there's not a damn thing we can do about it.
## case B: there's a get_XXX method:
meth = self._findmember('get_' + name)
if meth is not None:
return meth(self)
## case C: the attribute is in _data
elif self._data.has_key(name):
return self._data[name]
## case D: it does not have the attribute
else:
raise AttributeError, "no such attribute [" + name + "]"
More information about the Python-list
mailing list