Programatically, the starting point we need for a cached property transformation is just a list of the property names in question.
Considering that, we simply let the caller declare the cached property names through an attribute in the class body (_cached_). This allows us to handle both implementations (descriptor or __getattr__) in an equally elegant fashion.
Sticking with a decorator API, on the other hand, would disadvantage the __getattr__ approach: We'd have to search for any cached_property instances among the members, unwrap the original method, and throw away the unnecessarily constructed wrapper that just served as a marker for us to track down the property names.
(An alternative attempt was https://gist.github.com/mara004/fd944fe3a4a3fc514171e9cf617292d7, with classical decorator API / inference, but it is messier)