Implementing a class property that preserves the d

2019-02-23 07:58发布

问题:

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).

回答1:

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'


回答2:

If you jump through a few hoops, it can be retrieved, but not directly through the property itself like A.test.__doc__ because of the way descriptors work.

class classproperty(object):
    def __init__(self, getter):
        self.getter = getter

    def __get__(self, instance, owner):
        if instance is None:  # instance attribute accessed on class?
            return self
        return self.getter(owner)

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

def docstring(cls, methodname):
    return getattr(cls, methodname).getter.__doc__

print docstring(A, 'test')  # -> test's docstring