Dynamically change docstring of instance method

47 Views Asked by At

Here is a minimum example of a class that holds a function as an attribute and has a method that is calling said function.

class MyClass:

    def __init__(self, f):
        self.f = f

    def func(self):
        """This is the docstring to be replaced."""
        return self.f()

The function f can change during runtime and I would like to dynamically change the docstring of func to reflect the current function stored in f.

I was thinking of achieving this by using properties and setter to change the docstring of func as soon as a new f is assigned.

class MyClass:

    def __init__(self, f):
        self.f = f
    
    @property
    def f(self):
        return self._f

    @f.setter
    def f(self, value):
        self._f = value
        self.func.__doc__ = value.__doc__

    def func(self):
        """This is the docstring to be replaced"""
        return self.f()

The usage would be as follows

def func():
    """This is the docstring to be shown"""
    return True

obj = MyClass(func)

with the goal that calling help(obj.func) would display "This is the docstring to be shown".

However, it seems like docstrings of instance methods are not replaceable.

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[3], line 1
----> 1 obj = MyClass(func)

Cell In[1], line 4, in MyClass.__init__(self, f)
      3 def __init__(self, f):
----> 4     self.f = f

Cell In[1], line 13, in MyClass.f(self, value)
     10 @f.setter
     11 def f(self, value):
     12     self._f = value
---> 13     self.func.__doc__ = value.__doc__

AttributeError: attribute '__doc__' of 'method' objects is not writable

Is there any way of achieving this?

1

There are 1 best solutions below

1
chepner On

You need to distinguish between the function-valued class attribute that has a docstring and the method object created on demand for the evaluation of the expression self.func. self.func and MyClass.func are not the same object.

You'll need to use type(self) or more explicitly MyClass.func in order to change the docstring of the correct object.

class MyClass:

    def __init__(self, f):
        self.f = f
    
    @property
    def f(self):
        return self._f

    @f.setter
    def f(self, value):
        self._f = value
        type(self).func.__doc__ = value.__doc__

    def func(self):
        """This is the docstring to be replaced"""
        return self.f()

Keep in mind, though, that this is not a way to create instance-specific doc strings. MyClass.func is a single object shared by all instances. The problem before is that given instances a and b of MyClass, not only are a.func and b.func different method objects derived from that function, but even x = a.func and y = a.func are separate instances of method derived from the same function.