Docstrings threated differently for static method and class method

Hello,

why if I define a class like this

class Callable:
    """A callable."""

    def __call__(*args):
        return True


class Foo:
    a = staticmethod(Callable())
    b = classmethod(Callable())

when I invoke help(Foo) I get the docstring of the Callable class for the class method but not the static method?

Help on class Foo in module foo:

class Foo(builtins.object)
 |  Class methods defined here:
 |  
 |  b = <bound method ? of <class 'foo.Foo'>>
 |      A callable.
 |  
 |  ----------------------------------------------------------------------
 |  Static methods defined here:
 |  
 |  a = <foo.Callable object>
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

How would you go about defining the docstrings for static and class methods defined in this form? What about typing annotations?

Thank you.

1 Like

The docstring is still there:

>>> class Callable:
...     """A callable."""
...     def __call__(*args):
...         return True
... 
>>> 
>>> class Foo:
...     a = staticmethod(Callable())
...     b = classmethod(Callable())
... 
>>> Foo().a.__doc__
'A callable.'
>>> Foo().b.__doc__
'A callable.'
>>> 

It’s just that the help() function doesn’t think to look for a docstring because a is neither a method nor a function (nor a class), but a plain old object that happens to be callable.

1 Like

What I consider funny is that the static methods and class methods are treated differently. And, while the method is neither a function or a method, it is a staticmethod object, thus it is clearly marked as not being a plain old object that happens to be callable.

1 Like

No, it’s not.

>>> class Callable:       
...     """A callable."""
...     def __call__(*args):
...         return True
...
>>> class Foo:
...     a = staticmethod(Callable())
...     b = classmethod(Callable())
...
>>> Foo().a
<__main__.Callable object at 0x7fef1db4ed90>
>>> Foo().b
<bound method ? of <class '__main__.Foo'>>
>>> type(Foo().a)
<class '__main__.Callable'>
>>> type(Foo().b)
<class 'method'>

Of course, you are right. I was tricked by the fact that staticmethod() returns a staticmethod object, but the wrapping is removed when the class definition is “assembled”. Playing around a bit I found another thing that I found surprising at a first sight, but that makes completely sense after thinking about it for a few moments:

>>> class Foo:
...     @staticmethod
...     def a():
...         pass
...     a.x = 42
... 
...     def b():
...        pass
...     b.x = 42
... 
...     @staticmethod
...     def c():
...         pass
...     c.__func__.x = 42
... 
>>> f = Foo()
>>> f.a.x
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-344-d6ccf2a0ad21> in <module>
----> 1 f.a.x

AttributeError: 'function' object has no attribute 'x'

>>> f.b.x
42

>>> f.c.x
42