From c8f8c2c1a3fcdba7bb927019732207ec0edb0998 Mon Sep 17 00:00:00 2001 From: jbranchaud Date: Thu, 25 Jun 2026 11:30:18 -0500 Subject: [PATCH] Add Turn Method Into Cached Property On Class Instance as a Python TIL --- README.md | 3 +- ...-into-cached-property-on-class-instance.md | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 python/turn-method-into-cached-property-on-class-instance.md diff --git a/README.md b/README.md index 6130e0a..e37d6cc 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/). For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter). -_1802 TILs and counting..._ +_1803 TILs and counting..._ See some of the other learning resources I work on: @@ -1090,6 +1090,7 @@ If you've learned something here, support my efforts writing daily TILs by - [Start The Debugger When A Test Errors](python/start-the-debugger-when-a-test-errors.md) - [Store And Access Immutable Data In A Tuple](python/store-and-access-immutable-data-in-a-tuple.md) - [Test A Function With Pytest](python/test-a-function-with-pytest.md) +- [Turn Method Into Cached Property On Class Instance](python/turn-method-into-cached-property-on-class-instance.md) - [Use pipx To Install End User Apps](python/use-pipx-to-install-end-user-apps.md) - [Use `__post_init__` For `dataclass` Validations](python/use-post-init-for-dataclass-validations.md) - [Use Verbose Flag To Get More Diff](python/use-verbose-flag-to-get-more-diff.md) diff --git a/python/turn-method-into-cached-property-on-class-instance.md b/python/turn-method-into-cached-property-on-class-instance.md new file mode 100644 index 0000000..b0854d8 --- /dev/null +++ b/python/turn-method-into-cached-property-on-class-instance.md @@ -0,0 +1,48 @@ +# Turn Method Into Cached Property On Class Instance + +I have a class that encapsulates a few things including a somewhat expensive +data lookup from a file on disk. When this class is instantiated, it is +short-lived and the data that gets pulled from the file on disk is considered +fresh for the life of the instance. + +```python +class CliContext: + def __init__(self, verbose: bool) -> None: + # ... + self.repo = JsonRepository() + # ... + + def session_log(self) -> list[Session]: + return self.repo.load_session_log() +``` + +Because this method gets called from a couple places during a single lifecycle, +this class would benefit from caching it via the [`@cached_property` +decorator](https://docs.python.org/3/library/functools.html#functools.cached_property). + +```python +from functools import cached_property + +class CliContext: + def __init__(self, verbose: bool) -> None: + # ... + self.repo = JsonRepository() + # ... + + @cached_property + def session_log(self) -> list[Session]: + return self.repo.load_session_log() +``` + +Now `session_log` can be treated like a property instead of a method. That means +when I want to load and access the session log, I can do `self.session_log` (no +parentheses) like I would any other property. The first time I reference it, the +method will run. Then that value will be cached and all subsequent references +will use that cache. + +> Transform a method of a class into a property whose value is computed once and +> then cached as a normal attribute for the life of the instance. + +Of course, anytime we use caching, we can create a footgun for ourselves. We +have to be careful that our program doesn't evolve in such a way where the +caching will create a subtle bug due to stale data.