def cached_property(fget):
    """Create a read-only attribute that caches its value."""
    def cached_getter(self):
        try:
            return self._cache[fget.__name__]
        except KeyError:
            pass
        value = fget(self)
        self._cache[fget.__name__] = value
        return value
    return property(cached_getter, doc=fget.__doc__)