How to force help to use overridden __doc__

701 views Asked by At

Let's assume I define a simple class Foo:

class Foo(object):
    """This is Foo"""
    def __init__(self, label):
        self.__doc__ = "This is {} Foo".format(label)

    def __call(self, *args):
        ... # Some behavior which depends on the constructor arguments.

I see that object __doc__ is picked up by ? in IPython:

In [1]: %doctest_mode
Exception reporting mode: Plain
Doctest mode is: ON
>>> some = Foo("some")
>>> ?some
Type:            Foo
String form:     <__main__.Foo object at 0x7f01620a49b0>
Docstring:       This is some Foo
Class docstring: This is Foo

but ignored by help:

>>> help(some)

Help on Foo in module __main__ object:

class Foo(builtins.object)
 |  This is Foo
 |  
 |  Methods defined here:
 |  
 |  __init__(self, label)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
(END)

Is it possible to influence help behavior without modifying Class docstring? I would like to Class docstring to be ignored when working with instances.

2

There are 2 answers

1
MSeifert On BEST ANSWER

First: I wouldn't recommend it! If you seriously change the logic depending on the arguments, you are probably better of using a factory function and several distinct (sub-)classes.

After having said that: it's possible. One way would be using dynamic types and subclassing. However you need to use __new__ instead of __init__:

class Foo(object):
    """This is Foo"""
    def __new__(cls, label):
        newcls = type('FooSubclass', (Foo, ), {'__doc__': "This is {} Foo".format(label)})
        return newcls

>>> help(Foo('a'))
Help on class FooSubclass in module __main__:

class FooSubclass(Foo)
 |  This is a Foo
 |  
 |  Method resolution order:
 |      FooSubclass
 |      Foo
 |      __builtin__.object
 |  
 |  Static methods inherited from Foo:
 |  
 |  __new__(cls, label)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from Foo:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
0
Dimitris Fasarakis Hilliard On

Forcing the help function requires changes to its implementation from what I've understood. It is much easier to create a little helper function that temporarily re-assigns __doc__ on the class:

def my_help(obj):
    if not isinstance(obj, type):  # instances
        old_doc, type(obj).__doc__ = type(obj).__doc__, obj.__doc__
        help(type(obj))
        type(obj).__doc__ = old_doc
    else:
        help(obj)

This yields the, from what I've understood, wanted effect of having the docstring of the instance replace that of the class:

my_help(Foo('foo'))
Help on class Foo in module __main__:

class Foo(builtins.object)
 |  This is foo Foo
 |  
 |  Methods defined here:
 |  
 |  __init__(self, label)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 #  .. snipped ..

I don't really like this, though, and also know that you must perform a try-except in there for classes that don't allow you to set attributes on them like that. In short, doable but clunky.