What's the best practice to define an abstract instance attribute, but not as a property?
I would like to write something like:
class AbstractFoo(metaclass=ABCMeta):
#property
#abstractmethod
def bar(self):
pass
class Foo(AbstractFoo):
def __init__(self):
self.bar = 3
Instead of:
class Foo(AbstractFoo):
def __init__(self):
self._bar = 3
#property
def bar(self):
return self._bar
#bar.setter
def setbar(self, bar):
self._bar = bar
#bar.deleter
def delbar(self):
del self._bar
Properties are handy, but for simple attribute requiring no computation they are an overkill. This is especially important for abstract classes which will be subclassed and implemented by the user (I don't want to force someone to use #property when he just could have written self.foo = foo in the __init__).
Abstract attributes in Python question proposes as only answer to use #property and #abstractmethod: it doesn't answer my question.
The ActiveState recipe for an abstract class attribute via AbstractAttribute may be the right way, but I am not sure. It also only works with class attributes and not instance attributes.
A possibly a bit better solution compared to the accepted answer:
from better_abc import ABCMeta, abstract_attribute # see below
class AbstractFoo(metaclass=ABCMeta):
#abstract_attribute
def bar(self):
pass
class Foo(AbstractFoo):
def __init__(self):
self.bar = 3
class BadFoo(AbstractFoo):
def __init__(self):
pass
It will behave like this:
Foo() # ok
BadFoo() # will raise: NotImplementedError: Can't instantiate abstract class BadFoo
# with abstract attributes: bar
This answer uses same approach as the accepted answer, but integrates well with built-in ABC and does not require boilerplate of check_bar() helpers.
Here is the better_abc.py content:
from abc import ABCMeta as NativeABCMeta
class DummyAttribute:
pass
def abstract_attribute(obj=None):
if obj is None:
obj = DummyAttribute()
obj.__is_abstract_attribute__ = True
return obj
class ABCMeta(NativeABCMeta):
def __call__(cls, *args, **kwargs):
instance = NativeABCMeta.__call__(cls, *args, **kwargs)
abstract_attributes = {
name
for name in dir(instance)
if getattr(getattr(instance, name), '__is_abstract_attribute__', False)
}
if abstract_attributes:
raise NotImplementedError(
"Can't instantiate abstract class {} with"
" abstract attributes: {}".format(
cls.__name__,
', '.join(abstract_attributes)
)
)
return instance
The nice thing is that you can do:
class AbstractFoo(metaclass=ABCMeta):
bar = abstract_attribute()
and it will work same as above.
Also one can use:
class ABC(ABCMeta):
pass
to define custom ABC helper. PS. I consider this code to be CC0.
This could be improved by using AST parser to raise earlier (on class declaration) by scanning the __init__ code, but it seems to be an overkill for now (unless someone is willing to implement).
2021: typing support
You can use:
from typing import cast, Any, Callable, TypeVar
R = TypeVar('R')
def abstract_attribute(obj: Callable[[Any], R] = None) -> R:
_obj = cast(Any, obj)
if obj is None:
_obj = DummyAttribute()
_obj.__is_abstract_attribute__ = True
return cast(R, _obj)
which will let mypy highlight some typing issues
class AbstractFooTyped(metaclass=ABCMeta):
#abstract_attribute
def bar(self) -> int:
pass
class FooTyped(AbstractFooTyped):
def __init__(self):
# skipping assignment (which is required!) to demonstrate
# that it works independent of when the assignment is made
pass
f_typed = FooTyped()
_ = f_typed.bar + 'test' # Mypy: Unsupported operand types for + ("int" and "str")
FooTyped.bar = 'test' # Mypy: Incompatible types in assignment (expression has type "str", variable has type "int")
FooTyped.bar + 'test' # Mypy: Unsupported operand types for + ("int" and "str")
and for the shorthand notation, as suggested by #SMiller in the comments:
class AbstractFooTypedShorthand(metaclass=ABCMeta):
bar: int = abstract_attribute()
AbstractFooTypedShorthand.bar += 'test' # Mypy: Unsupported operand types for + ("int" and "str")
Just because you define it as an abstractproperty on the abstract base class doesn't mean you have to make a property on the subclass.
e.g. you can:
In [1]: from abc import ABCMeta, abstractproperty
In [2]: class X(metaclass=ABCMeta):
...: #abstractproperty
...: def required(self):
...: raise NotImplementedError
...:
In [3]: class Y(X):
...: required = True
...:
In [4]: Y()
Out[4]: <__main__.Y at 0x10ae0d390>
If you want to initialise the value in __init__ you can do this:
In [5]: class Z(X):
...: required = None
...: def __init__(self, value):
...: self.required = value
...:
In [6]: Z(value=3)
Out[6]: <__main__.Z at 0x10ae15a20>
Since Python 3.3 abstractproperty is deprecated. So Python 3 users should use the following instead:
from abc import ABCMeta, abstractmethod
class X(metaclass=ABCMeta):
#property
#abstractmethod
def required(self):
raise NotImplementedError
If you really want to enforce that a subclass define a given attribute, you can use metaclasses:
class AbstractFooMeta(type):
def __call__(cls, *args, **kwargs):
"""Called when you call Foo(*args, **kwargs) """
obj = type.__call__(cls, *args, **kwargs)
obj.check_bar()
return obj
class AbstractFoo(object):
__metaclass__ = AbstractFooMeta
bar = None
def check_bar(self):
if self.bar is None:
raise NotImplementedError('Subclasses must define bar')
class GoodFoo(AbstractFoo):
def __init__(self):
self.bar = 3
class BadFoo(AbstractFoo):
def __init__(self):
pass
Basically the meta class redefine __call__ to make sure check_bar is called after the init on an instance.
GoodFoo() # ok
BadFoo () # yield NotImplementedError
As Anentropic said, you don't have to implement an abstractproperty as another property.
However, one thing all answers seem to neglect is Python's member slots (the __slots__ class attribute). Users of your ABCs required to implement abstract properties could simply define them within __slots__ if all that's needed is a data attribute.
So with something like,
class AbstractFoo(abc.ABC):
__slots__ = ()
bar = abc.abstractproperty()
Users can define sub-classes simply like,
class Foo(AbstractFoo):
__slots__ = 'bar', # the only requirement
# define Foo as desired
def __init__(self):
self.bar = ...
Here, Foo.bar behaves like a regular instance attribute, which it is, just implemented differently. This is simple, efficient, and avoids the #property boilerplate that you described.
This works whether or not ABCs define __slots__ at their class' bodies. However, going with __slots__ all the way not only saves memory and provides faster attribute accesses but also gives a meaningful descriptor instead of having intermediates (e.g. bar = None or similar) in sub-classes.1
A few answers suggest doing the "abstract" attribute check after instantiation (i.e. at the meta-class __call__() method) but I find that not only wasteful but also potentially inefficient as the initialization step could be a time-consuming one.
In short, what's required for sub-classes of ABCs is to override the relevant descriptor (be it a property or a method), it doesn't matter how, and documenting to your users that it's possible to use __slots__ as implementation for abstract properties seems to me as the more adequate approach.
1 In any case, at the very least, ABCs should always define an empty __slots__ class attribute because otherwise sub-classes are forced to have __dict__ (dynamic attribute access) and __weakref__ (weak reference support) when instantiated. See the abc or collections.abc modules for examples of this being the case within the standard library.
The problem isn't what, but when:
from abc import ABCMeta, abstractmethod
class AbstractFoo(metaclass=ABCMeta):
#abstractmethod
def bar():
pass
class Foo(AbstractFoo):
bar = object()
isinstance(Foo(), AbstractFoo)
#>>> True
It doesn't matter that bar isn't a method! The problem is that __subclasshook__, the method of doing the check, is a classmethod, so only cares whether the class, not the instance, has the attribute.
I suggest you just don't force this, as it's a hard problem. The alternative is forcing them to predefine the attribute, but that just leaves around dummy attributes that just silence errors.
I've searched around for this for awhile but didn't see anything I like. As you probably know if you do:
class AbstractFoo(object):
#property
def bar(self):
raise NotImplementedError(
"Subclasses of AbstractFoo must set an instance attribute "
"self._bar in it's __init__ method")
class Foo(AbstractFoo):
def __init__(self):
self.bar = "bar"
f = Foo()
You get an AttributeError: can't set attribute which is annoying.
To get around this you can do:
class AbstractFoo(object):
#property
def bar(self):
try:
return self._bar
except AttributeError:
raise NotImplementedError(
"Subclasses of AbstractFoo must set an instance attribute "
"self._bar in it's __init__ method")
class OkFoo(AbstractFoo):
def __init__(self):
self._bar = 3
class BadFoo(AbstractFoo):
pass
a = OkFoo()
b = BadFoo()
print a.bar
print b.bar # raises a NotImplementedError
This avoids the AttributeError: can't set attribute but if you just leave off the abstract property all together:
class AbstractFoo(object):
pass
class Foo(AbstractFoo):
pass
f = Foo()
f.bar
You get an AttributeError: 'Foo' object has no attribute 'bar' which is arguably almost as good as the NotImplementedError. So really my solution is just trading one error message from another .. and you have to use self._bar rather than self.bar in the init.
Following https://docs.python.org/2/library/abc.html you could do something like this in Python 2.7:
from abc import ABCMeta, abstractproperty
class Test(object):
__metaclass__ = ABCMeta
#abstractproperty
def test(self): yield None
def get_test(self):
return self.test
class TestChild(Test):
test = None
def __init__(self, var):
self.test = var
a = TestChild('test')
print(a.get_test())
Related
I have a class like:
class MyClass:
Foo = 1
Bar = 2
Whenever MyClass.Foo or MyClass.Bar is invoked, I need a custom method to be invoked before the value is returned. Is it possible in Python? I know it is possible if I create an instance of the class and I can define my own __getattr__ method. But my scnenario involves using this class as such without creating any instance of it.
Also I need a custom __str__ method to be invoked when str(MyClass.Foo) is invoked. Does Python provide such an option?
__getattr__() and __str__() for an object are found on its class, so if you want to customize those things for a class, you need the class-of-a-class. A metaclass.
class FooType(type):
def _foo_func(cls):
return 'foo!'
def _bar_func(cls):
return 'bar!'
def __getattr__(cls, key):
if key == 'Foo':
return cls._foo_func()
elif key == 'Bar':
return cls._bar_func()
raise AttributeError(key)
def __str__(cls):
return 'custom str for %s' % (cls.__name__,)
class MyClass(metaclass=FooType):
pass
# # in python 2:
# class MyClass:
# __metaclass__ = FooType
print(MyClass.Foo)
print(MyClass.Bar)
print(str(MyClass))
printing:
foo!
bar!
custom str for MyClass
And no, an object can't intercept a request for a stringifying one of its attributes. The object returned for the attribute must define its own __str__() behavior.
Updated 2023-02-20 for Python 3.x default implementation (python 2 as a comment).
(I know this is an old question, but since all the other answers use a metaclass...)
You can use the following simple classproperty descriptor:
class classproperty(object):
""" #classmethod+#property """
def __init__(self, f):
self.f = classmethod(f)
def __get__(self, *a):
return self.f.__get__(*a)()
Use it like:
class MyClass(object):
#classproperty
def Foo(cls):
do_something()
return 1
#classproperty
def Bar(cls):
do_something_else()
return 2
For the first, you'll need to create a metaclass, and define __getattr__() on that.
class MyMetaclass(type):
def __getattr__(self, name):
return '%s result' % name
class MyClass(object):
__metaclass__ = MyMetaclass
print MyClass.Foo
For the second, no. Calling str(MyClass.Foo) invokes MyClass.Foo.__str__(), so you'll need to return an appropriate type for MyClass.Foo.
Surprised no one pointed this one out:
class FooType(type):
#property
def Foo(cls):
return "foo!"
#property
def Bar(cls):
return "bar!"
class MyClass(metaclass=FooType):
pass
Works:
>>> MyClass.Foo
'foo!'
>>> MyClass.Bar
'bar!'
(for Python 2.x, change definition of MyClass to:
class MyClass(object):
__metaclass__ = FooType
)
What the other answers say about str holds true for this solution: It must be implemented on the type actually returned.
Depending on the case I use this pattern
class _TheRealClass:
def __getattr__(self, attr):
pass
LooksLikeAClass = _TheRealClass()
Then you import and use it.
from foo import LooksLikeAClass
LooksLikeAClass.some_attribute
This avoid use of metaclass, and handle some use cases.
So I'm refactoring my code to be more Pythonic - specifically I've learned that using explicit getters and setters should be replaced with #property. My case is that i have an Example class with initialized bar attribute (initialization helps me to know that user set the bar):
class Example:
def __init__(self):
self.bar = 'initializedValue'
#property
def bar(self):
return self._bar
#bar.setter
def bar(self, b):
self._bar = b
def doIfBarWasSet():
if self.bar != 'initializedValue':
pass
else:
pass
after running foo = Example() my debugger shows that foo has two attributes: _bar and bar, both set to 'initializedValue'. Also, when I run foo.bar = 'changedValue' or foo._bar = 'changedValue', both of them are changed to 'changedValue'. Why there are two attributes? Isn't that redundant? I think I understand why there is _bar attribute - I added it in #bar.setter, but why there is bar as an string attribute? Shouldn't bar be rather a method leading to bar #property?
It's fine. Keep in mind that bar is not an instance attribute, but a class attribute. Since it has type property, it implements the descriptor protocol so that its behavior is different when accessed from an instance. If e is an instance of Example, then e.bar does not give you the instance of property assigned to Example.bar; it gives you the result of Example.bar.__get__(e, Example) (which in this case, happens to be Example.bar.fget(e), where fget is the original function decorated by #property).
In short, every instance has its own _bar attribute, but access to that attribute is mediated by the class attribute Example.bar.
It's easier to see that bar is a class attribute if you write this minimal (and sufficient, since neither the getter nor setter in this case requires a def statement) definition.
class Example:
def __init__(self):
self.bar = "initalizedValue"
bar = property(lambda self: self._bar, lambda self, b: setattr(self, '_bar', b))
or more generally
def bar_getter(self):
return self._bar
def bar_setter(self, b):
self._bar = b
class Example:
def __init__(self):
self.bar = "initalizedValue"
bar = property(bar_getter, bar_setter)
I would like to enumerate some instance methods inside a class. The operate function needs to use foo1, foo2,.. as Foo.FOO1, Foo.FOO2,.. .
class Machine:
def __init__(self):
self.operate()
def foo1(self):
pass
def foo2(self):
pass
..
class Foo(Enum):
FOO1 = Machine.foo1 # Machine is not defined
FOO2 = Machine.foo2 # Machine is not defined
..
def operate(self):
# use self.Foo.FOO1, self.Foo.FOO2,..
I do not know how to define the enum class.
The solution proposed by #giannisl9 is bugged, although it apparently works at first sight, a closer inspection reveals the Enum is broken:
from enum import Enum
class Machine:
def __init__(self):
class Foo(Enum):
FOO1 = self.foo1
self.foo = Foo
self.operate()
def foo1(self):
pass
def operate(self):
# breaks Enum contract, breaks syntax, breaks functionality...
self.foo.FOO1() # Enum member is NOT available! Method of class Machine bound in its place.
print(type(self.foo)) # {type}<class'enum.EnumMeta'> - Enum 'Foo'
print(type(self.foo.FOO1)) # {type} <class 'method'> - should be Enum member
print(type(self.foo.FOO1.name)) # {AttributeError}'function'object has no attribute 'name'
print(type(self.foo.FOO1.value)) # {AttributeError}'function'object has no attribute 'value'
Building on the answer by #Epic Programmer -since the original question only stated as requirement defining an Enum to run instance methods- given the application, organizing procedures in the __init__ or other methods, could suffice:
from inspect import ismethod
from inspect import isbuiltin
class Machine(object):
def operate(self):
for method in self.__dir__():
if ismethod(getattr(self, method)) \
and not isbuiltin(getattr(self, method)) \
and '__' not in method \
and 'operate' != method: # delete this to see a recursion
self.__getattribute__(method)() # after much filtering runs the method
def __init__(self):
self.operate()
def foo1(self):
print("drinks at bar1")
However, as I understand the question, it makes perfect sense the Enum should be internal to the class, since ontologically it pertains to encode/abbreviate a set of states proper to all instances of the class. That makes lots of sense!
It doesn't make much sense declaring it inside the __init__ as a self instance constant. Instead, it should be used as a symbolic class constant allowing to encode everything that in common may pertain to the instances.
from enum import Enum
class Machine:
class Foo(Enum):
# you could comma separate any combination for a given state
FOO1 = "foo1"
FOO2 = "foo2"
def __init__(self, arg_foo):
self.foo = arg_foo
self.operate()
self.all_operations()
def foo1(self):
print('drinks at bar1')
def foo2(self):
print('drinks at bar2')
def all_operations(self):
for one_member in Machine.Foo:
self.__getattribute__(one_member.value)()
def operate(self):
self.__getattribute__(str(self.foo.value))()
go_bar1 = Machine(Machine.Foo.FOO1)
go_bar2 = Machine(Machine.Foo.FOO2)
go_bar1.all_operations() # bar crawl
Or perhaps this is, approximately, what you're looking for:
from enum import Enum
class Machine:
def __init__(self, receive: Enum):
for one in receive.value:
if one is not None:
one(self) # Zen of Python
def foo1(self):
print('drinks at bar1')
def foo2(self):
print('drinks at bar2')
class Runner(Enum):
FOO1 = getattr(Machine, 'foo1'), getattr(Machine, 'foo2')
FOO2 = getattr(Machine, 'foo2'), None
first = Machine(Runner.FOO1)
second = Machine(Runner.FOO2)
I hope this helps.
Provided all methods in the Foo class that do not start with _ are methods you want to use, just iterate over the contents of the Foo class and get the attributes of the methods that match:
class Machine:
def operate(self):
for attribute in dir(self.Foo):
if attribute[0] != "_":
getattr(self.Foo, attribute)()
Following How to use class name in class scope?
and what made the most sense for my case, defining the enum inside the init method seems the way to go.
class Machine:
def __init__(self):
class Foo(Enum):
FOO1 = self.foo1
FOO2 = self.foo2
..
self.Foo = Foo
self.operate()
def foo1(self):
pass
def foo2(self):
pass
..
def operate(self):
#self.Foo.FOO1(), self.Foo.FOO2(),.. availabe
#self.Foo holds the enumeration
Whenever I define a class whose instances create objects of other classes, I like defining the types of those other objects as class members:
class Foo(object):
DICT_TYPE = dict # just a trivial example
def __init__(self):
self.mydict = self.DICT_TYPE()
class Bar(Foo):
DICT_TYPE = OrderedDict # no need to override __init__ now
The idea is to allow potential subclasses to easily override it.
I've just found a problem with this habbit, when the "type" I use is not really a type, but a factory function. For example, RLock is confusingly not a class:
def RLock(*args, **kwargs):
return _RLock(*args, **kwargs)
Thus using it the same way is no good:
class Foo(object):
LOCK_TYPE = threading.RLock # alas, RLock() is a function...
def __init__(self):
self.lock = self.LOCK_TYPE()
The problem here is that since RLock is a function, self.LOCK_TYPE gets bound to self, resulting with a bound-method, consequently leading to an error.
Here's a quick demonstration of how things go wrong when a function is used instead of a class (for a case simpler than RLock above):
def dict_factory():
return {}
class Foo(object):
DICT_TYPE1 = dict
DICT_TYPE2 = dict_factory
f = Foo()
f.DICT_TYPE1()
=> {}
f.DICT_TYPE2()
=> TypeError: dict_factory() takes no arguments (1 given)
Does anybody have a good solution for this problem? Is my habbit of defining those class members fundamentally wrong?
I guess I could replace it with a factory method. Would that be a better approach?
class Foo(object);
def __init__(self):
self.lock = self._make_lock()
def _make_lock(self):
return threading.RLock()
you could use the staticmethod decorator to ensure your class does not get passed in
>>> class Foo(object):
... DICT_TYPE = staticmethod(my_dict)
...
>>> f = Foo()
>>> f.DICT_TYPE()
{}
The problem can be bypassed by using a classproperty (e.g. as defined in this answer):
class Foo(object):
#classproperty
def DICT_TYPE(cls):
return dict_factory
There is a question about Inherit docstrings in Python class inheritance, but the answers there deal with method docstrings.
My question is how to inherit a docstring of a parent class as the __doc__ attribute. The usecase is that Django rest framework generates nice documentation in the html version of your API based on your view classes' docstrings. But when inheriting a base class (with a docstring) in a class without a docstring, the API doesn't show the docstring.
It might very well be that sphinx and other tools do the right thing and handle the docstring inheritance for me, but django rest framework looks at the (empty) .__doc__ attribute.
class ParentWithDocstring(object):
"""Parent docstring"""
pass
class SubClassWithoutDoctring(ParentWithDocstring):
pass
parent = ParentWithDocstring()
print parent.__doc__ # Prints "Parent docstring".
subclass = SubClassWithoutDoctring()
print subclass.__doc__ # Prints "None"
I've tried something like super(SubClassWithoutDocstring, self).__doc__, but that also only got me a None.
Since you cannot assign a new __doc__ docstring to a class (in CPython at least), you'll have to use a metaclass:
import inspect
def inheritdocstring(name, bases, attrs):
if not '__doc__' in attrs:
# create a temporary 'parent' to (greatly) simplify the MRO search
temp = type('temporaryclass', bases, {})
for cls in inspect.getmro(temp):
if cls.__doc__ is not None:
attrs['__doc__'] = cls.__doc__
break
return type(name, bases, attrs)
Yes, we jump through an extra hoop or two, but the above metaclass will find the correct __doc__ however convoluted you make your inheritance graph.
Usage:
>>> class ParentWithDocstring(object):
... """Parent docstring"""
...
>>> class SubClassWithoutDocstring(ParentWithDocstring):
... __metaclass__ = inheritdocstring
...
>>> SubClassWithoutDocstring.__doc__
'Parent docstring'
The alternative is to set __doc__ in __init__, as an instance variable:
def __init__(self):
try:
self.__doc__ = next(cls.__doc__ for cls in inspect.getmro(type(self)) if cls.__doc__ is not None)
except StopIteration:
pass
Then at least your instances have a docstring:
>>> class SubClassWithoutDocstring(ParentWithDocstring):
... def __init__(self):
... try:
... self.__doc__ = next(cls.__doc__ for cls in inspect.getmro(type(self)) if cls.__doc__ is not None)
... except StopIteration:
... pass
...
>>> SubClassWithoutDocstring().__doc__
'Parent docstring'
As of Python 3.3 (which fixed issue 12773), you can finally just set the __doc__ attribute of custom classes, so then you can use a class decorator instead:
import inspect
def inheritdocstring(cls):
for base in inspect.getmro(cls):
if base.__doc__ is not None:
cls.__doc__ = base.__doc__
break
return cls
which then can be applied thus:
>>> #inheritdocstring
... class SubClassWithoutDocstring(ParentWithDocstring):
... pass
...
>>> SubClassWithoutDocstring.__doc__
'Parent docstring'
In this particular case you could also override how REST framework determines the name to use for the endpoint, by overriding the .get_name() method.
If you do take that route you'll probably find yourself wanting to define a set of base classes for your views, and override the method on all your base view using a simple mixin class.
For example:
class GetNameMixin(object):
def get_name(self):
# Your docstring-or-ancestor-docstring code here
class ListAPIView(GetNameMixin, generics.ListAPIView):
pass
class RetrieveAPIView(GetNameMixin, generics.RetrieveAPIView):
pass
Note also that the get_name method is considered private, and is likely to change at some point in the future, so you would need to keep tabs on the release notes when upgrading, for any changes there.
The simplest way is to assign it as a class variable:
class ParentWithDocstring(object):
"""Parent docstring"""
pass
class SubClassWithoutDoctring(ParentWithDocstring):
__doc__ = ParentWithDocstring.__doc__
parent = ParentWithDocstring()
print parent.__doc__ # Prints "Parent docstring".
subclass = SubClassWithoutDoctring()
assert subclass.__doc__ == parent.__doc__
It's manual, unfortunately, but straightforward. Incidentally, while string formatting doesn't work the usual way, it does with the same method:
class A(object):
_validTypes = (str, int)
__doc__ = """A accepts the following types: %s""" % str(_validTypes)
A accepts the following types: (<type 'str'>, <type 'int'>)
You can also do it using #property
class ParentWithDocstring(object):
"""Parent docstring"""
pass
class SubClassWithoutDocstring(ParentWithDocstring):
#property
def __doc__(self):
return None
class SubClassWithCustomDocstring(ParentWithDocstring):
def __init__(self, docstring, *args, **kwargs):
super(SubClassWithCustomDocstring, self).__init__(*args, **kwargs)
self.docstring = docstring
#property
def __doc__(self):
return self.docstring
>>> parent = ParentWithDocstring()
>>> print parent.__doc__ # Prints "Parent docstring".
Parent docstring
>>> subclass = SubClassWithoutDocstring()
>>> print subclass.__doc__ # Prints "None"
None
>>> subclass = SubClassWithCustomDocstring('foobar')
>>> print subclass.__doc__ # Prints "foobar"
foobar
You can even overwrite a docstring.
class SubClassOverwriteDocstring(ParentWithDocstring):
"""Original docstring"""
def __init__(self, docstring, *args, **kwargs):
super(SubClassOverwriteDocstring, self).__init__(*args, **kwargs)
self.docstring = docstring
#property
def __doc__(self):
return self.docstring
>>> subclass = SubClassOverwriteDocstring('new docstring')
>>> print subclass.__doc__ # Prints "new docstring"
new docstring
One caveat, the property can't be inherited by other classes evidently, you have to add the property in each class that you want to overwrite the docstring.
class SubClassBrokenDocstring(SubClassOverwriteDocstring):
"""Broken docstring"""
def __init__(self, docstring, *args, **kwargs):
super(SubClassBrokenDocstring, self).__init__(docstring, *args, **kwargs)
>>> subclass = SubClassBrokenDocstring("doesn't work")
>>> print subclass.__doc__ # Prints "Broken docstring"
Broken docstring
Bummer! But definitely easier than doing the meta class thing!