descriptor & docstring
Gabriel Genellina
gagsl-py2 at yahoo.com.ar
Mon Apr 28 22:59:59 EDT 2008
En Mon, 28 Apr 2008 14:35:40 -0300, cyril giraudon
<cyril.giraudon at gmail.com> 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
More information about the Python-list
mailing list