2

Suppose I have two classes (A & B) of which the second is derived from the first. Further, I hide some of the properties implemented in A by writing new implementations in B. However, the docstring I wrote for A is still valid for B and I'm lazy -- I don't want to copy paste everything.

Please note that a key issue here is my targets are properties, for class methods in general there are solutions posted.

In code, minimal example of classes could look like this:

In [1]: class A(object):
   ...:     @property
   ...:     def a(self):
   ...:         "The a:ness of it"
   ...:         return True
   ...:     

In [2]: class B(A):
   ...:     @property
   ...:     def a(self):
   ...:         return False
   ...:     

What I basically would want is to have a way to make the following happen:

In [8]: B.a.__doc__
Out[8]: 'The a:ness of it'

In reality the docstring of B.a is empty and writing to B.a.__doc__ is impossible as it raises a TypeError.

As far as I have gotten is the following hack of a solution:

from inspect import getmro

def inheritDocFromA(f):
    """This decorator will copy the docstring from ``A`` for the
    matching function ``f`` if such exists and no docstring has been added
    manually.
    """
    if f.__doc__ is None:

        fname = f.__name__

        for c in getmro(A):
            if hasattr(c, fname):
                d = getattr(c, fname).__doc__
                if d is not None:
                    f.__doc__ = d
                    break

    return f

Which does indeed work, but is dead ugly as A is hard-coded into the decorator-function as I have found no way to know what class f is attached to when passed to the decorator:

In [15]: class C(A):
   ....:     @property
   ....:     @inheritDocFromA
   ....:     def a(self):
   ....:         return False
   ....:     

In [16]: C.a.__doc__
Out[16]: 'The a:ness of it'

Question: Is it possible to construct a general solution for decorator applied docstrings on class properties without hard-coding in the inheritance into the decorator function?

I've also tried decorating the classes but then I hit the wall with the properties docstrings being write-protected.

And finally, I'd like the solution to work for both Python 2.7 and 3.4 if possible.

deinonychusaur
  • 7,094
  • 3
  • 30
  • 44
  • OT: why use `inspect.getmro` when there is the public `__mro__` property of type objects? AFAIK `inspect` should best be reserved for situations when there is no public API for accessing some functionality, or the API is cpython-specific. – user4815162342 Jun 25 '14 at 10:37
  • One solution would be for `.a` to return a proxy object whose `__doc__` returns `T.__dict__.a.__doc__`, T being the first type in `.__mro__` whose `T.__dict__.a.__doc__` is not None. That eliminates the hardcoding of inheritance hierarchy, which is what you requested, at the price of introducing a proxy class and a smell of overengineering. I'll try to write it up into an answer when I get to a real keyboard. – user4815162342 Jun 25 '14 at 10:44
  • What about: http://stackoverflow.com/questions/8100166/inheriting-methods-docstrings-in-python ? – James Mills Jun 25 '14 at 10:55
  • @JamesMills The accepted solution from that question won't work here. As pointed out by the OP: *I've also tried decorating the classes but then I hit the wall with the properties docstrings being write-protected.* – user4815162342 Jun 25 '14 at 11:18
  • Same here.. Been playing around with a few ideas myself. Seems they fixed several or more issues around this in Python 3 though... – James Mills Jun 25 '14 at 11:22
  • @user4815162342 for your first comment, I suppose you are right that .__mro__ is more suitable, for your second comment, do you suggest writing my own property-decorator (because I still need setter-functionality as well as getters)? – deinonychusaur Jun 25 '14 at 12:05
  • @deinonychusaur You don't need to reimplement the whole logic of `property`, you can defer `__set__` (and `__get__` in case instance exists) to the underlying property created by `@property`. See my answer for an example. – user4815162342 Jun 25 '14 at 12:23

1 Answers1

1

It is possible to write a decorator that returns the proper __doc__ when accessed through class — after all, __get__ receives the type and can go through its MRO, find the appropriate __doc__, and set it on itself (or on a proxy created and returned for that purpose). But it is much simpler to work around the issue of __doc__ not being writable.

It turns out that since property is implemented as a type, making the __doc__ of its instances writable is as simple as inheriting from it:

class property_writable_doc(property):
    pass

Then your idea of using a class decorator to inherit the __doc__ of properties can work:

def inherit_doc_class(cls):
    for name, obj in cls.__dict__.iteritems():
        if isinstance(obj, property_writable_doc) and obj.__doc__ is None:
            for t in cls.__mro__:
                if name in t.__dict__ and t.__dict__[name].__doc__ is not None:
                    obj.__doc__ = t.__dict__[name].__doc__
                    break
    return cls

class A(object):
    @property
    def a(self):
        "The a:ness of it"
        return True

@inherit_doc_class
class B(A):
    @property_writable_doc
    def a(self):
        return False

@inherit_doc_class
class C(A):
    @property_writable_doc
    def a(self):
        "The C:ness of it"
        return False
user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • Wow, I'll have to take some time digesting it. If I understand the implementation correctly, requested proxy objects could be stored for future re-use to save some performance at the cost of some memory? – deinonychusaur Jun 25 '14 at 12:33
  • @deinonychusaur You don't need to care about the performance of creating the proxies, they will only be created when you access `a` through a class such as `B`. When you access `a` through an *instance* of the class, no additional proxies will be created. But what you *might* need to worry about is that every attribute instance will go through an additional Python function call. But there's no way around that, other than writing a C extension. – user4815162342 Jun 25 '14 at 12:35
  • Also, see the "alternative approach" for a much simpler implementation that's hopefully easier to digest. – user4815162342 Jun 25 '14 at 12:36
  • I was just writing to comment, that last version, if I get it right would solves the overhead cost you talked about right? And I notice writing an `@a.setter` on `B` or `C` works perfect. Thanks for the great answer(s)! – deinonychusaur Jun 25 '14 at 12:55
  • @deinonychusaur Indeed, the last version doesn't have the lookup overhead. I think I'll drop the previous versions from the answer and just leave that one. – user4815162342 Jun 25 '14 at 13:08
  • Alternatively restructure an move the best answer to the top and have the others as references as they dig into nice deep core python stuff – deinonychusaur Jun 25 '14 at 13:10
  • @deinonychusaur That's another option, but it makes the answer really long. Writing such code is certainly instructive, but I'm not sure if it provides much value to the reader. – user4815162342 Jun 25 '14 at 13:14