{"id":13502285,"url":"https://github.com/pydanny/cached-property","last_synced_at":"2025-04-23T20:57:11.345Z","repository":{"id":17131580,"uuid":"19897931","full_name":"pydanny/cached-property","owner":"pydanny","description":"A decorator for caching properties in classes.","archived":false,"fork":false,"pushed_at":"2024-12-07T05:23:03.000Z","size":193,"stargazers_count":705,"open_issues_count":41,"forks_count":78,"subscribers_count":16,"default_branch":"main","last_synced_at":"2025-04-11T08:56:16.243Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pydanny.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS.md","dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"pydanny","patreon":"feldroy","open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2014-05-17T22:42:24.000Z","updated_at":"2025-04-07T16:26:20.000Z","dependencies_parsed_at":"2023-02-14T11:00:29.245Z","dependency_job_id":"be84ea92-f8fd-410f-8373-13e99606f7d2","html_url":"https://github.com/pydanny/cached-property","commit_stats":{"total_commits":179,"total_committers":27,"mean_commits":6.62962962962963,"dds":0.7374301675977654,"last_synced_commit":"c032196d9afdf4b21d041c7d1492c95f5d1d6740"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pydanny%2Fcached-property","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pydanny%2Fcached-property/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pydanny%2Fcached-property/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pydanny%2Fcached-property/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pydanny","download_url":"https://codeload.github.com/pydanny/cached-property/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250167811,"owners_count":21386005,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T22:02:08.619Z","updated_at":"2025-04-23T20:57:11.325Z","avatar_url":"https://github.com/pydanny.png","language":"Python","funding_links":["https://github.com/sponsors/pydanny","https://patreon.com/feldroy"],"categories":["Python","Data Caching","Python decorator in the wild"],"sub_categories":[],"readme":"# cached-property\n\n[![Github Actions status](https://github.com/pydanny/cached-property/workflows/Python%20package/badge.svg)](https://github.com/pydanny/cached-property/actions)\n[![PyPI](https://img.shields.io/pypi/v/cached-property.svg)](https://pypi.python.org/pypi/cached-property)\n[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)\n\nA decorator for caching properties in classes.\n\n## Why?\n\n* Makes caching of time or computational expensive properties quick and easy.\n* Because I got tired of copy/pasting this code from non-web project to non-web project.\n* I needed something really simple that worked in Python 2 and 3.\n  (Python 3.8 added a version of this decorator as [`@functools.cached_property`](https://docs.python.org/3.12/library/functools.html#functools.cached_property).)\n\n## How to use it\n\nLet's define a class with an expensive property. Every time you stay there the\nprice goes up by $50!\n\n```python\nclass Monopoly:\n\n    def __init__(self):\n        self.boardwalk_price = 500\n\n    @property\n    def boardwalk(self):\n        # In reality, this might represent a database call or time\n        # intensive task like calling a third-party API.\n        self.boardwalk_price += 50\n        return self.boardwalk_price\n```\n\nNow run it:\n\n```python\n\u003e\u003e\u003e monopoly = Monopoly()\n\u003e\u003e\u003e monopoly.boardwalk\n550\n\u003e\u003e\u003e monopoly.boardwalk\n600\n```\n\nLet's convert the boardwalk property into a `cached_property`.\n\n```python\nfrom cached_property import cached_property\n\nclass Monopoly(object):\n\n    def __init__(self):\n        self.boardwalk_price = 500\n\n    @cached_property\n    def boardwalk(self):\n        # Again, this is a silly example. Don't worry about it, this is\n        #   just an example for clarity.\n        self.boardwalk_price += 50\n        return self.boardwalk_price\n```\n\nNow when we run it the price stays at $550.\n\n```python\n\u003e\u003e\u003e monopoly = Monopoly()\n\u003e\u003e\u003e monopoly.boardwalk\n550\n\u003e\u003e\u003e monopoly.boardwalk\n550\n\u003e\u003e\u003e monopoly.boardwalk\n550\n```\n\nWhy doesn't the value of `monopoly.boardwalk` change? Because it's a **cached property**!\n\n## Invalidating the Cache\n\nResults of cached functions can be invalidated by outside forces. Let's demonstrate how to force the cache to invalidate:\n\n```python\n\u003e\u003e\u003e monopoly = Monopoly()\n\u003e\u003e\u003e monopoly.boardwalk\n550\n\u003e\u003e\u003e monopoly.boardwalk\n550\n\u003e\u003e\u003e # invalidate the cache\n\u003e\u003e\u003e del monopoly.__dict__['boardwalk']\n\u003e\u003e\u003e # request the boardwalk property again\n\u003e\u003e\u003e monopoly.boardwalk\n600\n\u003e\u003e\u003e monopoly.boardwalk\n600\n```\n\n## Working with Threads\n\nWhat if a whole bunch of people want to stay at Boardwalk all at once? This means using threads, which\nunfortunately causes problems with the standard `cached_property`. In this case, switch to using the\n`threaded_cached_property`:\n\n```python\nfrom cached_property import threaded_cached_property\n\nclass Monopoly:\n\n    def __init__(self):\n        self.boardwalk_price = 500\n\n    @threaded_cached_property\n    def boardwalk(self):\n        \"\"\"threaded_cached_property is really nice for when no one waits\n            for other people to finish their turn and rudely start rolling\n            dice and moving their pieces.\"\"\"\n\n        sleep(1)\n        self.boardwalk_price += 50\n        return self.boardwalk_price\n```\n\nNow use it:\n\n```python\n\u003e\u003e\u003e from threading import Thread\n\u003e\u003e\u003e from monopoly import Monopoly\n\u003e\u003e\u003e monopoly = Monopoly()\n\u003e\u003e\u003e threads = []\n\u003e\u003e\u003e for x in range(10):\n\u003e\u003e\u003e     thread = Thread(target=lambda: monopoly.boardwalk)\n\u003e\u003e\u003e     thread.start()\n\u003e\u003e\u003e     threads.append(thread)\n\n\u003e\u003e\u003e for thread in threads:\n\u003e\u003e\u003e     thread.join()\n\n\u003e\u003e\u003e self.assertEqual(m.boardwalk, 550)\n```\n\n## Working with async/await\n\nThe cached property can be async, in which case you have to use await\nas usual to get the value. Because of the caching, the value is only\ncomputed once and then cached:\n\n```python\nfrom cached_property import cached_property\n\nclass Monopoly:\n\n    def __init__(self):\n        self.boardwalk_price = 500\n\n    @cached_property\n    async def boardwalk(self):\n        self.boardwalk_price += 50\n        return self.boardwalk_price\n```\n\nNow use it:\n\n```python\n\u003e\u003e\u003e async def print_boardwalk():\n...     monopoly = Monopoly()\n...     print(await monopoly.boardwalk)\n...     print(await monopoly.boardwalk)\n...     print(await monopoly.boardwalk)\n\u003e\u003e\u003e import asyncio\n\u003e\u003e\u003e asyncio.get_event_loop().run_until_complete(print_boardwalk())\n550\n550\n550\n```\n\nNote that this does not work with threading either, most asyncio\nobjects are not thread-safe. And if you run separate event loops in\neach thread, the cached version will most likely have the wrong event\nloop. To summarize, either use cooperative multitasking (event loop)\nor threading, but not both at the same time.\n\n## Timing out the cache\n\nSometimes you want the price of things to reset after a time. Use the `ttl`\nversions of `cached_property` and `threaded_cached_property`.\n\n```python\nimport random\nfrom cached_property import cached_property_with_ttl\n\nclass Monopoly(object):\n\n    @cached_property_with_ttl(ttl=5) # cache invalidates after 5 seconds\n    def dice(self):\n        # I dare the reader to implement a game using this method of 'rolling dice'.\n        return random.randint(2,12)\n```\n\nNow use it:\n\n```python\n\u003e\u003e\u003e monopoly = Monopoly()\n\u003e\u003e\u003e monopoly.dice\n10\n\u003e\u003e\u003e monopoly.dice\n10\n\u003e\u003e\u003e from time import sleep\n\u003e\u003e\u003e sleep(6) # Sleeps long enough to expire the cache\n\u003e\u003e\u003e monopoly.dice\n3\n\u003e\u003e\u003e monopoly.dice\n3\n```\n\n**Note:** The `ttl` tools do not reliably allow the clearing of the cache. This\nis why they are broken out into seperate tools. See https://github.com/pydanny/cached-property/issues/16.\n\n## Credits\n\n* Pip, Django, Werkzeug, Bottle, Pyramid, and Zope for having their own implementations. This package originally used an implementation that matched the Bottle version.\n* Reinout Van Rees for pointing out the `cached_property` decorator to me.\n* My awesome wife [@audreyfeldroy](https://github.com/audreyfeldroy) who created [`cookiecutter`](https://github.com/cookiecutter/cookiecutter), which meant rolling this out took me just 15 minutes.\n* @tinche for pointing out the threading issue and providing a solution.\n* @bcho for providing the time-to-expire feature\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpydanny%2Fcached-property","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpydanny%2Fcached-property","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpydanny%2Fcached-property/lists"}