Adding docstring to __slots__ descriptor?
<p>I'm writing a storage automation module that provisions volumes. Instead of passing the half dozen or more arguments needed to actually create a volume on the storage controller, I created a parameter class using <code>__slots__</code> that is passed into the create method like this:</p>

<pre><code>from mock import Mock
from six import with_metaclass

class VolumeParameterMeta(type):
    def __new__(mcs, name, bases, dct):
        # set __slots__ docstrings here?
        return super(VolumeParameterMeta, mcs).__new__(mcs, name, bases, dct)

class VolumeParameter(with_metaclass(VolumeParameterMeta, object)):
    __slots__ = ('name', 'size', 'junctionPath', 'svmName', 'securityStyle'
                 'spaceReserve')

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

class Volume(object):
    def __init__(self, driver, name):
        self.driver = driver
        self.name = name

    @classmethod
    def create(cls, driver, param):
        # do sanity check on param (volume not too large, etc)
        driver.provision(param)
        return cls(driver, param.name)

if __name__ == '__main__':
    param = VolumeParameter('volume1')
    param.svmName = 'vserver1'
    param.junctionPath = '/some/path'
    param.size = 2 ** 30
    param.spaceReserve = param.size * 0.1
    param.securityStyle = 'mixed'
    volume = Volume.create(driver=Mock(), param=param)
</code></pre>

<p>The above example works great with one small exception.  It's not obvious how to add docstrings to the descriptors in the parameter class.  It <em>seems</em> like it should be possible with a metaclass, but the descriptors aren't defined when the metaclass is instantiated.</p>

<p>I'm keenly aware that some may disagree with my using a side-effect of <code>__slots__</code>, but I like that it helps eliminate typos. Try to set a parameter that doesn't exist and boom, <code>AttributeError</code> is raised.  All without any boilerplate code.  It's arguably more Pythonic to let it fail at volume creation time, but the result would be that a default is used instead of an incorrectly spelled parameter. It would effectively be a silent failure.</p>

<p>I realize it's possible to simply expand the parameter class docstring, but the result of <code>pydoc</code> and other documentation building scripts is a large free-form docstring for the class and <em>empty</em> docstrings for each of the descriptors.</p>

<p><strong>Surely there must be a way to define docstrings for the descriptors created by <code>__slots__</code>? Perhaps there's another way apart from slots?  A mutable <code>namedtuple</code> or similar?</strong></p>

<p>No, there is no option to add docstrings to the descriptor objects that are generated for names defined in <code>__slots__</code>. They are like regular non-descriptor attributes in that regard.</p>

<p>On a regular object without slots, you can, at most, add comments and set default values:</p>

<pre><code>class VolumeParameter(object):
     # the name of the volume (str)
     name = ''
     # volume size in bytes (int)
     size = 0
     # ...
</code></pre>

<p>The same still applies even if you declared all those attributes as <code>__slots__</code>.</p>

<p>You could 'wrap' each slot in another descriptor in the form of a <code>property</code> object:</p>

<pre><code>class VolumeParameterMeta(type):
    @staticmethod
    def _property_for_name(name, docstring):
        newname = '_' + name
        def getter(self):
            return getattr(self, newname)
        def setter(self, value):
            setattr(self, newname, value)
        def deleter(self):
            delattr(self, newname)
        return newname, property(getter, setter, deleter, docstring)

    def __new__(mcs, name, bases, dct):
        newslots = []
        clsslots = dct.pop('__slots__', ())
        slotdocs = dct.pop('__slot_docs__', {})
        if isinstance(clsslots, str):
            clsslots = clsslots.split()
        for name in clsslots:
            newname, prop = mcs._property_for_name(name, slotdocs.get(name))
            newslots.append(newname)
            dct[name] = prop
        if newslots:
            dct['__slots__'] = tuple(newslots)
        return super(VolumeParameterMeta, mcs).__new__(mcs, name, bases, dct)

class VolumeParameter(with_metaclass(VolumeParameterMeta, object)):
    __slots__ = ('name', 'size', 'junctionPath', 'svmName', 'securityStyle'
                 'spaceReserve')
    __slot_docs__ = {
        'name': 'the name of the volume (str)',
        # ...
    }
</code></pre>

<p>Note that this almost doubles the number of descriptors on the class:</p>

<pre><code>&gt;&gt;&gt; pprint(VolumeParameter.__dict__)
mappingproxy({'__doc__': None,
              '__module__': '__main__',
              '__slots__': ('_name',
                            '_size',
                            '_junctionPath',
                            '_svmName',
                            '_securityStylespaceReserve'),
              '_junctionPath': &lt;member '_junctionPath' of 'securityStylespaceReserve' objects&gt;,
              '_name': &lt;member '_name' of 'securityStylespaceReserve' objects&gt;,
              '_securityStylespaceReserve': &lt;member '_securityStylespaceReserve' of 'securityStylespaceReserve' objects&gt;,
              '_size': &lt;member '_size' of 'securityStylespaceReserve' objects&gt;,
              '_svmName': &lt;member '_svmName' of 'securityStylespaceReserve' objects&gt;,
              'junctionPath': &lt;property object at 0x105edbe08&gt;,
              'name': &lt;property object at 0x105edbd68&gt;,
              'securityStylespaceReserve': &lt;property object at 0x105edb098&gt;,
              'size': &lt;property object at 0x105edbdb8&gt;,
              'svmName': &lt;property object at 0x105edb048&gt;})
</code></pre>

<p>but now your properties at least have docstrings:</p>

<pre><code>&gt;&gt;&gt; VolumeParameter.name.__doc__
'the name of the volume (str)'
</code></pre>

python