Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating dynamic docstrings in Python descriptor

I am trying to generate some class definitions dynamically (for wrapping a C++ extension). The following descriptor works fine except when I try to access the docstring for a field using help(), it gives default documentation for the descriptor rather than the field it self. However when I do help(classname), it retrieves the docstring passed to the descriptor:

class FieldDescriptor(object):
    def __init__(self, name, doc='No documentation available.'):
        self.name = name
        self.__doc__ = doc

    def __get__(self, obj, dtype=None):
        if obj is None and dtype is not None:
            print 'Doc is:', self.__doc__
            return self
        return obj.get_field(self.name)

    def __set__(self, obj, value):
        obj.set_field(self.name, value)

class TestClass(object):
    def __init__(self):
        self.fdict = {'a': None, 'b': None}

    def get_field(self, name):
        return self.fdict[name]

    def set_field(self, name, value):
        self.fdict[name] = value

fields = ['a', 'b']
def define_class(class_name, baseclass):
    class_obj = type(class_name, (baseclass,), {})
    for field in fields:
        setattr(class_obj, field, FieldDescriptor(field, doc='field %s in class %s' % (field, class_name)))
    globals()[class_name] = class_obj


if __name__ == '__main__':
    define_class('DerivedClass', TestClass)
    help(DerivedClass.a)
    help(DerivedClass)
    v = DerivedClass()
    help(v.a)

"python test.py" prints:

Doc is: field a in class DerivedClass
Help on FieldDescriptor in module __main__ object:

class FieldDescriptor(__builtin__.object)
 |  Methods defined here:
 |  
 |  __get__(self, obj, dtype=None)
 |  
 |  __init__(self, name, doc='No documentation available.')
 |  
 |  __set__(self, obj, value)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Doc is: field a in class DerivedClass
Doc is: field b in class DerivedClass
Help on class DerivedClass in module __main__:

class DerivedClass(TestClass)
 |  Method resolution order:
 |      DerivedClass
 |      TestClass
 |      __builtin__.object
 |  
 |  Data descriptors defined here:
 |  
 |  a
 |      field a in class DerivedClass
 |  
 |  b
 |      field b in class DerivedClass
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from TestClass:
 |  
 |  __init__(self)
 |  
 |  get_field(self, name)
 |  
 |  set_field(self, name, value)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from TestClass:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Help on NoneType object:

class NoneType(object)
 |  Methods defined here:
 |  
 |  __hash__(...)
 |      x.__hash__()  hash(x)
 |  
 |  __repr__(...)
 |      x.__repr__()  repr(x)

Any idea how one can get the descriptor.__doc__ for help(class.field) ? And is there a way to bypass this and have something like a getter function for doc in stead of having to store the doc string in the descriptor?

like:

class FieldDescriptor(object):
    def __init__(self, name, doc='No documentation available.'):
        self.name = name
        self.__doc__ = doc

    def __get__(self, obj, dtype=None):
        if obj is None and dtype is not None:
            print 'Doc is:', self.__doc__
            return self
        return obj.get_field(self.name)

    def __set__(self, obj, value):
        obj.set_field(self.name, value)

    # This is what I'd like to have
    def __doc__(self, obj, dtype):
       return dtype.generate_docstring(self.name)

UPDATE: Actually I started with this definition of __get__:

def __get__(self, obj, dtype=None):
    return obj.get_field(self.name)

The problem with this was that when I said:

help(DerivedClass.a)

Python threw an Exception indicating that I was trying to call None.get_field. Thus help() is calling the __get__ method with obj=None and dtype=DerivedClass. That is why I decided to return the FieldDescriptor instance when obj=None and dtype!=None. My impression was help(xyz) tries to display xyz.__doc__. By that logic, if __get__ returns descriptor_instance, then descriptor_instance.__doc__ should be printed by help(), which is the case for the whole class [help(DerivedClass)], but not for the single field [help(DerivedClass.a)].

like image 274
subhacom Avatar asked Apr 06 '12 14:04

subhacom


1 Answers

What goes on is that when you request help(DerivedClass.a) - python calculates the expression inside the parentheses - which is the object returned by the descriptor's __get__ method - and them searchs for the help (including docstring) on that object.

A way to have this working, including the dynamic docstring generation, is to have your __get__ method to retudn a dynamically generated object that features the desired doc string. But this object would itself need to be a proper proxy object to the original one, and would create some overhead on your code - and a lot of special cases.

Anyway, the only way to get it working ike you want is to modify the objects returned by __get__ itself, so that they behave like you'd like them to.

Id suggest that if all you want in the help is a bit of information like you are doing, maybe you want the objects returned from your __get__ to be of a class that define a __repr__ method (rather than just a __doc__ string) .

like image 180
jsbueno Avatar answered Nov 12 '22 18:11

jsbueno