En Mon, 28 Apr 2008 14:35:40 -0300, cyril giraudon
<[EMAIL PROTECTED]> escribió:
Hello,
I try to use python descriptors to define attributes with default
value (the code is reported below).
But apparently, it breaks the docstring mechanism.
help(Basis) shows the right help but help(Rectangle) shows only two
lines :
"
Help on class Rectangle in module basis2:
Rectangle = <class 'basis2.Rectangle'>
"
If the Rectangle.length attribute is removed, the help is OK.
Secondly, the __doc__ attribute of a PhysicalValue instance doesn't
seem to be read.
I don't understand.
Any idea ?
This looks like a soup of descriptors, metaclasses and properties...
I'll write a two step example. I assume that you want to define an
attribute with a default value: when not explicitely set, it returns the
default value. This can be implemented with an existing descriptor:
property. The only special thing is to handle the default value.
Step 1: Our first attempt will let us write something like this:
class X(object:
length = property_default("length", 12., "This is the length property")
We have to write property_default so it returns a property object with the
right fget and fset methods. Let's use the same convention as your code,
property "foo" will store its value at attribute "_foo".
def property_default(prop_name, default_value=None, doc=None):
attr_name = '_'+prop_name
def fget(self, attr_name=attr_name,
default_value=default_value):
return getattr(self, attr_name, default_value)
def fset(self, value,
attr_name=attr_name,
default_value=default_value):
if value == default_value:
delattr(self, attr_name)
else:
setattr(self, attr_name, value)
return property(fget=fget, fset=fset, doc=doc)
When setting the same value as the default, the instance attribute is
removed (so the default will be used when retrieving the value later). I
think this is what you intended to do.
That's all. The classes look like this:
# just to use a more meaningful name, if you wish
PhysicalValue = property_default
# A basis class
class Basis(object):
"""
Tempest basis class
"""
# A concrete class
class Rectangle(Basis):
"""
A beautiful Rectangle
"""
length = PhysicalValue("length", 12., "This is the length property")
py> r = Rectangle()
py> print r.length
12.0
py> r.length = 13.5
py> print r.length
13.5
py> dir(r)
['__class__', ... '_length', 'length']
py> r.length = 12
py> dir(r)
['__class__', ... 'length']
Help works too:
py> help(Rectangle)
Help on class Rectangle in module __main__:
class Rectangle(Basis)
| A beautiful Rectangle
|
| Method resolution order:
| Rectangle
| Basis
| __builtin__.object
| [...]
py> help(Rectangle.length)
Help on property:
This is the length property
Step 2: The problem with the property_default declaration above is that it
repeats the name "length". If we want to comply with the DRY principle, we
can use a metaclass (note that the previous solution doesn't require a
custom metaclass). In the class declaration, we only have to store the
parameters needed to define the property; later, when the class is created
(the metaclass __new__ method), we replace those parameters with an actual
property object. The fget/gset implementation is the same as above.
class property_default(object):
"""Store info for defining a property with a default value.
Replaced with a real property instance at class creation time.
"""
def __init__(self, default_value, doc=None):
self.default_value = default_value
self.doc = doc
# just to use a more meaningful name, if you wish
class PhysicalValue(property_default): pass
class PropDefaultMetaClass(type):
def __new__(cls, name, bases, dct):
# replace all property_default declarations
# with an actual property object
# (we can't modify dct at the same time
# we iterate over it, so collect the new items
# into another dictionary)
newprops = {}
for prop_name, prop in dct.iteritems():
if isinstance(prop, property_default):
attr_name = '_'+prop_name
def fget(self, attr_name=attr_name,
default_value=prop.default_value):
return getattr(self, attr_name, default_value)
def fset(self, value,
attr_name=attr_name,
default_value=prop.default_value):
if value == default_value:
delattr(self, attr_name)
else:
setattr(self, attr_name, value)
newprops[prop_name] = property(
fget=fget, fset=fset,
doc=prop.doc)
dct.update(newprops)
return super(MyMetaClass, cls).__new__(cls, name, bases, dct)
# A basis class
class Basis(object):
"""
Tempest basis class
"""
__metaclass__ = PropDefaultMetaClass
# A concrete class
class Rectangle(Basis):
"""
A beautiful Rectangle
"""
length = PhysicalValue(12., "This is the length property")
The usage and behavior is the same as in step 1, only that we can omit the
"length" parameter to PhysicalValue.
--
Gabriel Genellina
--
http://mail.python.org/mailman/listinfo/python-list