[Python-ideas] Dynamic getting of __doc__ of a function

Marko Ristin-Kaufmann marko.ristin at gmail.com
Sun Oct 7 10:40:17 EDT 2018


Hi Steve,

Here are a couple of code snippets. I hope it's not too verbose :)

Setting property on the __doc__ works on the instance, but not on the class:

class A:
    @property
    def __doc__(self):
        return "Works only on the instance"

a = A()
print('A.__doc__ is {}'.format(A.__doc__))
# A.__doc__ is <property object at 0x7fa48af86e58>

print('a.__doc__ is {}'.format(a.__doc__))
# a.__doc__ is Works only on the instance

help(A)
# Help on class A in module __main__:
#
# class A(builtins.object)
#  |  Data descriptors defined here:
#  |
#  |  __dict__
#  |      dictionary for instance variables (if defined)
#  |
#  |  __weakref__
#  |      list of weak references to the object (if defined)


However, you can fix this with a separate class that follows the property
protocol:

some_var = "oi"

class DocProperty:
    def __get__(self, instance, owner):
        return "Dynamic class doc: {}".format(some_var)

class A:
    """Static doc"""
    __doc__ = DocProperty()


print(A.__doc__)
# Dynamic class doc: oi

some_var = "noi"

print(A.__doc__)
# Dynamic class doc: noi

help(A)
# Help on class A in module __main__:
#
# class A(builtins.object)
#  |  Dynamic class doc: noi
#  |
#  |  Data descriptors defined here:
#  |
#  |  __dict__
#  |      dictionary for instance variables (if defined)
#  |
#  |  __weakref__
#  |      list of weak references to the object (if defined)


This worked well when I decorate a *class.* Consider now the case where we
have to decorate a *function*.

I could decorate with a callable class, but __doc__ now does not work and
help() becomes misleading (the function signature is lost and the help says
"Help on Wrapper"):

import functools


class DocProperty:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        return "Func doc is: {!r} and now my part...".format(self.func.__doc__)


def decorate(func):
    class Wrapper:
        __doc__ = DocProperty(func)

        def __call__(self, *args, **kwargs):
            print("Before the call")
            return func(*args, **kwargs)

    wrapper = Wrapper()

    functools.update_wrapper(wrapper=wrapper, wrapped=func)

    wrapper.__doc__ = DocProperty(func)

    return wrapper


@decorate
def some_func(x):
    """Original doc."""
    print("Hi from some_func")


print('some_func.__doc__ is {}'.format(some_func.__doc__))
# some_func.__doc__ is <__main__.DocProperty object at 0x7fd134a1d668>

help(some_func)
# Help on Wrapper in module __main__ object:
#
# some_func = class Wrapper(builtins.object)
#  |  Func doc is: 'Original doc.' and now my part...
#  |
#  |  Methods defined here:
#  |
#  |  __call__(self, *args, **kwargs)
#  |
#  |  ----------------------------------------------------------------------
#  |  Data descriptors defined here:
#  |
#  |  __dict__
#  |      dictionary for instance variables (if defined)
#  |
#  |  __weakref__
#  |      list of weak references to the object (if defined)


I tried to decorate the function with a DocProperty, but then hit the wall.
Both __doc__ is not "getted" nor does help() works:

import functools

class DocProperty:
    def __init__(self, func):
        self.func = func

    def __get__(self, instance, owner):
        return "Func doc is: {!r} and now my part...".format(self.func.__doc__)

def decorate(func):
    def wrapper(*args, **kwargs):
        print("Before the call")
        return func(*args, **kwargs)

    functools.update_wrapper(wrapper=wrapper, wrapped=func)

    wrapper.__doc__ = DocProperty(func)

    return wrapper

@decorate
def some_func(x):
    """Original doc."""
    print("Hi from some_func")

some_func(x=3)
# Before the call
# Hi from some_func

print('some_func.__doc__ is {}'.format(some_func.__doc__))
# some_func.__doc__ is <__main__.DocProperty object at 0x7f0551eea438>

help(some_func)
# Help on function some_func in module __main__:
#
# some_func(x)

I just couldn't figure out how to make the __doc__ attribute of a function
a getter. Is this a bug or am I making a mistake? If it's my mistake, is
there any other way how to dynamically __doc__ a function or am I forced to
set __doc__ of a function at the decoration time?

(If you wonder about the use case: I'd like to dynamically generate the
docstrings when functions are decorated with contracts from icontract
library. Condition functions need to be parsed and re-formatted, so this is
something that should be done on-demand, when the user either wants to see
the help() or when the sphinx documentation is automatically generated. The
docs should not inflict computational overhead during the decoration since
normal course of program operation does not need pretty-printed contracts.)

Thanks for any pointers! Please let me know if you'd like me to compress
one or the other example or explain something in more detail.

Cheers,
Marko

On Sun, 7 Oct 2018 at 11:07, Steven D'Aprano <steve at pearwood.info> wrote:

> On Sun, Oct 07, 2018 at 09:25:13AM +0200, Marko Ristin-Kaufmann wrote:
> > Hi,
> > I'm working on decorators that have a dynamic __doc__ property computed
> at
> > runtime and not at decoration time.
>
> You mean like this?
>
> py> class X:
> ...     @property
> ...     def __doc__(self):
> ...         return "NOBODY expects the %s Inquisition!" % self.nationality
> ...     def __init__(self, nationality):
> ...         self.nationality = nationality
> ...
> py> a = X("Spanish")
> py> b = X("Belgian")
> py> a.__doc__
> 'NOBODY expects the Spanish Inquisition!'
> py> b.__doc__
> 'NOBODY expects the Belgian Inquisition!'
>
>
> The only downside is that help(a) doesn't do the right thing. I believe
> that counts as a bug in help().
>
>
>
>
> > The decorator must return the wrapper as a function and can not return a
> > callable object with __call__ since the "self" argument would not be
> > properly passed through the decorator.
>
> Sorry, I don't get this. Can you demonstrate? Code speaks much louder
> than words.
>
>
>
> --
> Steve
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20181007/146481e8/attachment-0001.html>


More information about the Python-ideas mailing list