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

Reply via email to