descriptor & docstring

Discussion in 'Python' started by cyril giraudon, Apr 28, 2008.

  1. 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 ?

    Thanks a lot

    Cyril.



    # A descriptor class with default value handling
    class PhysicalValue(object):
    """
    A physical value descriptor
    """
    def __init__(self, default_value):
    self.default_value = default_value
    self.__doc__ = "Hello from Physical Value"

    def __get__(self, obj, type=None):
    if obj.__dict__.has_key(self.key):
    return getattr(obj, self.key)
    else:
    return self.default_value

    def __set__(self, obj, value):
    if value is DefaultValue:
    setattr(obj, self.key, self.default_value)
    else:
    setattr(obj, self.key, value)

    # A meta class which adds instance attributes
    # If hasattr(cls, "attr") then add "_attr" attribute.
    class MyMetaClass(type):
    def __init__(cls, name, bases, dct):
    super(MyMetaClass, cls).__init__(name, bases, dct)
    print "Add property to ", name
    def init(self):
    pvl = [item for item in cls.__dict__.items()
    if isinstance(item[1], PhysicalValue)]
    for pv in pvl:
    print "Add _%s property to %s" % (pv[0], name)
    cls.__dict__[pv[0]].key = "_" + pv[0]
    setattr(self, "_" + pv[0], getattr(self, pv[0]))
    cls.__init__ = init

    # A basis class
    class Basis(object):
    """
    Tempest basis class
    """
    __metaclass__ = MyMetaClass

    # A concrete class
    class Rectangle(Basis):
    """
    A beautiful Rectangle
    """
    length = PhysicalValue(12.)
     
    cyril giraudon, Apr 28, 2008
    #1
    1. Advertising

  2. A precision, I use python 2.5.2 under linux mandiva 2007.0

    Cyril.
     
    cyril giraudon, Apr 28, 2008
    #2
    1. Advertising

  3. En Mon, 28 Apr 2008 14:35:40 -0300, cyril giraudon
    <> 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
     
    Gabriel Genellina, Apr 29, 2008
    #3
  4. On Apr 28, 10:59 pm, "Gabriel Genellina"

    > 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.


    Note that this will fail if the value is already equal to the default
    and you try to reset it to the default, so it needs an extra
    hasattr(self, attr_name) before the delattr. Regardless, I would be
    surprised with the following behaviour:

    >>> r = Rectangle()
    >>> r.length = 4
    >>> type(r.length)

    <type 'int'>
    >>> r.length = 12
    >>> type(r.length)

    <type 'float'>

    Another simpler alternative would be to (ab)use a decorator:

    def defaultproperty(func):
    attr = '_' + func.__name__
    default = func.func_defaults[0]
    return property(
    fget = lambda self: getattr(self, attr, default),
    fset = lambda self,value: setattr(self, attr, value),
    doc = func.__doc__)


    class Rectangle(object):
    '''A beautiful Rectangle'''

    @defaultproperty
    def length(default=12.0):
    '''This is the length property'''

    George
     
    George Sakkis, Apr 29, 2008
    #4
  5. En Tue, 29 Apr 2008 01:29:40 -0300, George Sakkis
    <> escribió:
    > On Apr 28, 10:59 pm, "Gabriel Genellina"
    >
    >> 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.

    >
    > Note that this will fail if the value is already equal to the default
    > and you try to reset it to the default, so it needs an extra
    > hasattr(self, attr_name) before the delattr. Regardless, I would be
    > surprised with the following behaviour:
    >
    >>>> r = Rectangle()
    >>>> r.length = 4
    >>>> type(r.length)

    > <type 'int'>
    >>>> r.length = 12
    >>>> type(r.length)

    > <type 'float'>


    Yep, probably the best thing to do is to always call setattr and avoid
    special cases.

    > Another simpler alternative would be to (ab)use a decorator:
    >
    > def defaultproperty(func):
    > attr = '_' + func.__name__
    > default = func.func_defaults[0]
    > return property(
    > fget = lambda self: getattr(self, attr, default),
    > fset = lambda self,value: setattr(self, attr, value),
    > doc = func.__doc__)
    >
    >
    > class Rectangle(object):
    > '''A beautiful Rectangle'''
    >
    > @defaultproperty
    > def length(default=12.0):
    > '''This is the length property'''



    Nice, although that empty function looks somewhat strange...

    --
    Gabriel Genellina
     
    Gabriel Genellina, Apr 29, 2008
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. John J. Lee
    Replies:
    6
    Views:
    558
    John J. Lee
    Jul 29, 2003
  2. Thomas Guettler

    DocumentationTools (docstring --> html)

    Thomas Guettler, Jun 21, 2004, in forum: Python
    Replies:
    1
    Views:
    391
    Paul McGuire
    Jun 21, 2004
  3. Andrew Durdin

    Integers have docstring for int()

    Andrew Durdin, Aug 11, 2004, in forum: Python
    Replies:
    0
    Views:
    289
    Andrew Durdin
    Aug 11, 2004
  4. Andrew Durdin

    Getting the docstring of a property

    Andrew Durdin, Aug 11, 2004, in forum: Python
    Replies:
    1
    Views:
    309
    John Roth
    Aug 11, 2004
  5. bdb112
    Replies:
    3
    Views:
    269
    Chris Rebert
    Mar 9, 2009
Loading...

Share This Page