Any of those help It's tempting to say that __dict__ has to be a descriptor because implementing it as a __dict__ entry would require you to find the __dict__ before you can find the __dict__, but Python already bypasses normal attribute lookup to find __dict__ when looking up other attributes, so that's not quite as compelling as it initially sounds. If the descriptors were replaced with a '__dict__' key in every __dict__, __dict__ would still be findable. There's some space savings by not having a key for '__dict__' in every __dict__, but that's not the big reason. There's also time saved by not having to set a '__dict__' key, and time and space saved by not creating a circular reference, and these benefits are all really nice, but they're still probably smaller than the next thing.
Why does the semantics of a python class attribute change after assignment to an instance?
will be helpful for those in need Because details is created from the original values. After the concatenation occurs, it's entirely independent of the other values. If you want a dynamically constructed string using the current values of name/author, use a property to compute details dynamically on access (without storing it as an attribute at all):
def __init__(self, name, author):
self.name = name
self.author = author
# Don't make a `details` attribute at all
# This method is called whenever you refer to `somebook.details`
return self.name + ' written by ' + self.author