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
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With