Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing a class property that preserves the docstring

I have a descriptor that turns a method into a property on the class level:

class classproperty(object):

    def __init__(self, getter):
        self.getter = getter
        self.__doc__ = getter.__doc__

    def __get__(self, instance, owner):
        return self.getter(owner)

Used like this:

class A(object):
    @classproperty
    def test(cls):
        "docstring"
        return "Test"

However, I now can't access the __doc__ attribute (which is logical, because accessing A.test.__doc__ will fetch the __doc__ of str, because A.test already returns "Test".

My final goal is that my docstring will appear in sphinx, so it is not feasible to retrieve the docstring in any other way than by accessing the attributes __doc__ property. I find myself wondering if this is even possible in any way.

I know that property solves this issue by returning the class if called without an instance. However, it should be obvious that this collides with my goal.

I am starting to fear that this is not possible in Python.

Note: I am willing to pull any stunt in classproperty as long as it is stable (i.e. not setting __doc__ on the returned value). However, it is not feasible to put any burden on the user of classproperty (i.e. they should only use the decorator and be done with it).

like image 662
javex Avatar asked Mar 21 '23 06:03

javex


1 Answers

Indeed, test is a property returning a string. You'd have to subclass str and give that a __doc__ attribute:

class docstring_str(str):
    def __new__(cls, v, __doc__=''):
        s = super(docstring_str, cls).__new__(cls, v)
        s.__doc__ = __doc__
        return s

Demo:

>>> class docstring_str(str):
...     def __new__(cls, v, __doc__=''):
...         s = super(docstring_str, cls).__new__(cls, v)
...         s.__doc__ = __doc__
...         return s
... 
>>> s = docstring_str('Test', 'docstring')
>>> s
'Test'
>>> s.__doc__
'docstring'

Use as:

class A(object):
    @classproperty
    def test(cls):
        return docstring_str("Test", "docstring')

Because str objects are immutable, you cannot set the __doc__ attribute in a decorator. You'd have to return a proxy object instead that fully wraps the actual return value except for the __doc__ attribute. This gets to be complex and ugly fast.

The alternative is to put a regular property on the metaclass; the class's class:

class MetaClass(type):
    @property
    def test(cls):
        "docstring"
        return "Test"

class A(object):
    __metaclass__ = MetaClass

Now A has a test property, and the docstring can be accessed as MetaClass.test.__doc__ or with type(A).test.__doc__:

>>> A.test
'Test'
>>> type(A).test
<property object at 0x10757d158>
>>> type(A).test.__doc__
'docstring'
like image 102
Martijn Pieters Avatar answered Apr 26 '23 01:04

Martijn Pieters