Using the descriptor protocol a developer can implement their own property
like class which operates similarly with the output of help()
where the instance __doc__
shows up under the name of the custom property and under the data descriptors section of help()
. An issue occurs when you want to slot your custom property. You cannot slot your custom property, have a class docstring, and still be able to have a __doc__
on an instance of the custom property.
Examples:
class Foo:
"""Docstring on class, no slots, can override __doc__ on instance and get
desired help() documentation.
"""
def __init__(self, doc):
self.__doc__ = doc
def __get__(self, instance, owner):
...
def __set__(self, instance, value):
...
class WithFoo:
custom_property = Foo("Property doc string")
Now looking at the help of both
help(Foo)
class Foo(builtins.object)
| Foo(doc)
|
| Docstring on class, no slots, can override __doc__ on instance and get
| desired help() documentation.
|
| Methods defined here:
|
| __get__(self, instance, owner)
|
| __init__(self, doc)
| Initialize self. See help(type(self)) for accurate signature.
|
| __set__(self, instance, value)
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
help(WithFoo)
Help on class WithFoo in module __main__:
class WithFoo(builtins.object)
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| custom_property
| Property doc string
Now if we try to __slot__
our custom property things start to get hairy.
class Foo:
"""Docstring on class, with slots, __doc__ not in slots, cannot override
__doc__ on instance and get desired help() documentation. Raises an
AttributeError on instantiation.
"""
__slots__ = ()
def __init__(self, doc):
self.__doc__ = doc
def __get__(self, instance, owner):
...
def __set__(self, instance, value):
...
class WithFoo:
custom_property = Foo("Property doc string")
This raises an error
AttributeError Traceback (most recent call last)
Input In [41], in <cell line: 20>()
17 def __set__(self, instance, value):
18 ...
---> 20 class WithFoo:
21 custom_property = Foo("Property doc string")
Input In [41], in WithFoo()
20 class WithFoo:
---> 21 custom_property = Foo("Property doc string")
Input In [41], in Foo.__init__(self, doc)
8 def __init__(self, doc):
----> 9 self.__doc__ = doc
AttributeError: 'Foo' object attribute '__doc__' is read-only
So naturally add __doc__
to __slots__
but then we cannot have a class docstring as this situation raises an error too.
class Foo:
"""Docstring on class, with slots, __doc__ in slots, cannot define class at
all. Raises attribute error.
"""
__slots__ = ("__doc__",)
def __init__(self, doc):
self.__doc__ = doc
def __get__(self, instance, owner):
...
def __set__(self, instance, value):
...
class WithFoo:
custom_property = Foo("Property doc string")
ValueError Traceback (most recent call last)
Input In [43], in <cell line: 1>()
----> 1 class Foo:
2 """Docstring on class, with slots, __doc__ in slots, cannot define class at
3 all. Raises attribute error.
4 """
6 __slots__ = ("__doc__",)
ValueError: '__doc__' in __slots__ conflicts with class variable
So in closing if we want to slot a descriptor which is to act like a custom version of property
then we are stuck with either defining a class docstring or an instance docstring. I think it would be a good idea to allow some sort of special casing, overriding __doc__
on slotted classes that also define a __get__
and __set__
so that there can exist a class docstring while simultaneously overriding it on the instance and obtaining the desired help()
when the descriptor is instantiated.