Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python docstring for descriptors

I am using descriptors to define the registers of an interface class:

class Register(object):
    def __init__(self, address, docstring="instance docstring"):
        self.address = address    
        self.__doc__ = docstring

    def __get__(self, obj, objtype):
        return obj.read(self.address)

    def __set__(self, obj, val):
        return obj.write(self.address, val)

class Interface(object):
    r = Register(0x00, docstring="the first register") 

I wold like the user of ipython to be able to do one of the following:

i = Interface()
i.r? #should show the docstring "the first register"

or

i = Interface()
i.r(  #should show the docstring "the first register" when parentheses are opened

However, the docstring is always the one from the int object that obj.read returns, not the specified docstring. Is there a way to show the right docstring in this situation?

If I am not using descriptors but manually define them, it works when the parentheses are open:

class Interface(object):
    @property
    def r(self):
        """this docstring will be shown alternatively"""
        return self.read(0x0)

    @r.setter
    def r(self,v):
        """this is the docstring that is shown"""
        self.write(0x0,v)

i = Interface()
i.r( #the right docstring pops up here once i open the bracket

If the setter does not define a docstring, the one of the getter is shown when brackets are opened.

Can I somehow get the same behaviour by using descriptors without unreasonable overhead?

My question is somewhat similar to this one, which however gives no satisfactory answer: Creating dynamic docstrings in Python descriptor

like image 866
Leonhard Neuhaus Avatar asked May 16 '16 13:05

Leonhard Neuhaus


People also ask

What can you use a docstring for?

A Python docstring is a string used to document a Python module, class, function or method, so programmers can understand what it does without having to read the details of the implementation. Also, it is a common practice to generate online (html) documentation automatically from docstrings.

What does __ doc __ mean in Python?

The __doc__ attribute Each Python object (functions, classes, variables,...) provides (if programmer has filled it) a short documentation which describes its features. You can access it with commands like print myobject.

Should __ init __ have a docstring?

The class constructor should be documented in the docstring for its __init__ method. This is quite logical, as this is the usual procedure for functions and methods, and __init__() is not an exception. As a consequence, this puts the code and its documentation in the same place, which helps maintenance.


1 Answers

There are two issues. The first is that the descriptor API also works on classes. So when Ipython tries to get the descriptor from the class, the instance __get__ logic is getting called, which happens to fail with an AttributeError, so the descriptor gets ignored. In your example, if you try to to get the attribute from Interface, it raises an error, since it's trying to run on an instance (which is None in this case):

In [25]: Interface.r
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-25-dd7a6e721f7e> in <module>
----> 1 Interface.r

<ipython-input-24-7d19c1ba4fe6> in __get__(self, obj, objtype)
      5 
      6     def __get__(self, obj, objtype):
----> 7         return obj.read(self.address)
      8 
      9     def __set__(self, obj, val):

AttributeError: 'NoneType' object has no attribute 'read'

The second is that Ipython only uses the descriptor for help if the the descriptor is an instance of property (hardcoded). The logic for this is here.

To fix the first issue, you want to return the descriptor itself if it's passed None for obj:

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        ...

To fix the second issue, you'd either need to submit a patch to ipython, or subclass from property (much easier, though a bit hacky). Putting these together:

class Register(property):
    def __init__(self, address, docstring="instance docstring"):
        self.address = address    
        self.__doc__ = docstring

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return obj.read(self.address)

    def __set__(self, obj, val):
        return obj.write(self.address, val)
        

class Interface(object):
    r = Register(0x00, docstring="the first register")

i = Interface()

Then in ipython, you get:

In [21]: i.r?
Type:        Register
String form: <__main__.Register object at 0x1051203a0>
Docstring:   the first register
like image 79
RecursivelyIronic Avatar answered Sep 20 '22 23:09

RecursivelyIronic