• Stars
    star
    692
  • Rank 65,341 (Top 2 %)
  • Language
    Python
  • License
    BSD 3-Clause "New...
  • Created over 10 years ago
  • Updated about 1 month ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A decorator for caching properties in classes.

cached-property

Github Actions status PyPI Code style: black

A decorator for caching properties in classes.

Why?

  • Makes caching of time or computational expensive properties quick and easy.
  • Because I got tired of copy/pasting this code from non-web project to non-web project.
  • I needed something really simple that worked in Python 2 and 3.

How to use it

Let's define a class with an expensive property. Every time you stay there the price goes up by $50!

class Monopoly(object):

    def __init__(self):
        self.boardwalk_price = 500

    @property
    def boardwalk(self):
        # In reality, this might represent a database call or time
        # intensive task like calling a third-party API.
        self.boardwalk_price += 50
        return self.boardwalk_price

Now run it:

>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
600

Let's convert the boardwalk property into a cached_property.

from cached_property import cached_property

class Monopoly(object):

    def __init__(self):
        self.boardwalk_price = 500

    @cached_property
    def boardwalk(self):
        # Again, this is a silly example. Don't worry about it, this is
        #   just an example for clarity.
        self.boardwalk_price += 50
        return self.boardwalk_price

Now when we run it the price stays at $550.

>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550

Why doesn't the value of monopoly.boardwalk change? Because it's a cached property!

Invalidating the Cache

Results of cached functions can be invalidated by outside forces. Let's demonstrate how to force the cache to invalidate:

>>> monopoly = Monopoly()
>>> monopoly.boardwalk
550
>>> monopoly.boardwalk
550
>>> # invalidate the cache
>>> del monopoly.__dict__['boardwalk']
>>> # request the boardwalk property again
>>> monopoly.boardwalk
600
>>> monopoly.boardwalk
600

Working with Threads

What if a whole bunch of people want to stay at Boardwalk all at once? This means using threads, which unfortunately causes problems with the standard cached_property. In this case, switch to using the threaded_cached_property:

from cached_property import threaded_cached_property

class Monopoly(object):

    def __init__(self):
        self.boardwalk_price = 500

    @threaded_cached_property
    def boardwalk(self):
        """threaded_cached_property is really nice for when no one waits
            for other people to finish their turn and rudely start rolling
            dice and moving their pieces."""

        sleep(1)
        self.boardwalk_price += 50
        return self.boardwalk_price

Now use it:

>>> from threading import Thread
>>> from monopoly import Monopoly
>>> monopoly = Monopoly()
>>> threads = []
>>> for x in range(10):
>>>     thread = Thread(target=lambda: monopoly.boardwalk)
>>>     thread.start()
>>>     threads.append(thread)

>>> for thread in threads:
>>>     thread.join()

>>> self.assertEqual(m.boardwalk, 550)

Working with async/await (Python 3.5+)

The cached property can be async, in which case you have to use await as usual to get the value. Because of the caching, the value is only computed once and then cached:

from cached_property import cached_property

class Monopoly(object):

    def __init__(self):
        self.boardwalk_price = 500

    @cached_property
    async def boardwalk(self):
        self.boardwalk_price += 50
        return self.boardwalk_price

Now use it:

>>> async def print_boardwalk():
...     monopoly = Monopoly()
...     print(await monopoly.boardwalk)
...     print(await monopoly.boardwalk)
...     print(await monopoly.boardwalk)
>>> import asyncio
>>> asyncio.get_event_loop().run_until_complete(print_boardwalk())
550
550
550

Note that this does not work with threading either, most asyncio objects are not thread-safe. And if you run separate event loops in each thread, the cached version will most likely have the wrong event loop. To summarize, either use cooperative multitasking (event loop) or threading, but not both at the same time.

Timing out the cache

Sometimes you want the price of things to reset after a time. Use the ttl versions of cached_property and threaded_cached_property.

import random
from cached_property import cached_property_with_ttl

class Monopoly(object):

    @cached_property_with_ttl(ttl=5) # cache invalidates after 5 seconds
    def dice(self):
        # I dare the reader to implement a game using this method of 'rolling dice'.
        return random.randint(2,12)

Now use it:

>>> monopoly = Monopoly()
>>> monopoly.dice
10
>>> monopoly.dice
10
>>> from time import sleep
>>> sleep(6) # Sleeps long enough to expire the cache
>>> monopoly.dice
3
>>> monopoly.dice
3

Note: The ttl tools do not reliably allow the clearing of the cache. This is why they are broken out into seperate tools. See #16.

Credits

  • Pip, Django, Werkzueg, Bottle, Pyramid, and Zope for having their own implementations. This package originally used an implementation that matched the Bottle version.
  • Reinout Van Rees for pointing out the cached_property decorator to me.
  • My awesome wife @audreyfeldroy who created cookiecutter, which meant rolling this out took me just 15 minutes.
  • @tinche for pointing out the threading issue and providing a solution.
  • @bcho for providing the time-to-expire feature

More Repositories

1

django-uni-form

django-uni-form has been deprecated. Please use django-crispy-forms. Link provided in the README.rst
Python
466
star
2

django-wysiwyg

A Django application for making Django textareas rich text editors. Certainly as a template tag and possibly as a form widget.
HTML
465
star
3

cookiecutter-djangopackage

A cookiecutter template for creating reusable Django packages quickly.
Python
436
star
4

dj-notebook

Django + shell_plus + Jupyter notebooks made easy.
Jupyter Notebook
131
star
5

pydanny-event-notes

My notes for the various conferences I attend.
Python
119
star
6

dj-webhooks

Django + Webhooks made Easy
Python
87
star
7

multiple-user-types-django

How to do different user models
Python
73
star
8

webhooks

Python + Webhooks Made Easy
Python
60
star
9

dj-libcloud

Adds easy python 3 and 2.7 support to Django for management of static assets.
Python
53
star
10

simplicity-archive

Converts ReStructuredText into JSON.
Python
47
star
11

django-social-bookmarking

An eggified model based BSD licensed social booking app for Django
Python
47
star
12

django-profiletools

Tools for Profile models in Django.
Python
41
star
13

that

The Anti-Zen of Python
Python
40
star
14

django-party-pack

Sample project that includes sphinx
JavaScript
39
star
15

dj-paginator

Django + Pagination made easy.
Python
36
star
16

contributors

A command-line script to get all the contributors for one or more GitHub projects.
Python
33
star
17

django-tagging-ext

Django Tagging Extensions
Python
31
star
18

dj-ango

Simplifying the import structure of Django.
Python
30
star
19

shortener_project

A Django project for creating and maintaining shortUrls.
Python
25
star
20

fastapi-blog

A markdown-powered blog engine and light CMS for FastAPI.
Python
25
star
21

refry

Refry is a modern, maintained, typed easy-to-use retry decorator.
Python
22
star
22

pydanny.github.com

My blog and programming site.
HTML
21
star
23

dj-spam

Django + Fighting Spam Made Easy
Python
21
star
24

django-wall

This project is functionally dead and is being kept for archival purposes. I suggest use of https://github.com/justquick/django-activity-stream instead.
Python
20
star
25

django-blarg

Django 404 and 500 pages the blarg way.
Python
20
star
26

django-friends-graph

An extension for django-friends that shows the relationships between people in graph format.
JavaScript
20
star
27

daniel-blog-fasthtml

Python
20
star
28

restructuredtext

Python
19
star
29

django-la-facebook

Development is now at https://github.com/cartwheelweb/django-la-facebook
Python
18
star
30

django-nosqladmin

NoSQL Admin for Django
Python
16
star
31

cookiecutter-pymodule

Like cookiecutter_pypackage, but for just a module.
Python
14
star
32

whydjango

Python
14
star
33

django-easy-profiles

Many handy django profile tricks to make it easier for developers. PRE-ALPHA: NOT READY FOR USE!
Python
14
star
34

django-registration-bootstrap

Bootstrap friendly pieces for django-registration
Python
13
star
35

daniel.feldroy.com

Roff
11
star
36

simplicity

A straightforward and opinionated cookiecutter template for building Python packages. Inspired by Audrey Roy Greenfeld's seminal work on Cookiecutter and the cookiecutter-pypackage template.
Python
11
star
37

stringtheory

Adds critically unimportant functionality to Python's str type.
Python
10
star
38

pydanny-computer-setup

How I set things up
8
star
39

configure-django

A package for configuring Django in non-traditional environments.
Python
8
star
40

Python-Wars-Solo

I wrote this using AppleBasic about 30 years ago. Now I'm using Python. This game is a total 45 minute hackfest I did back in 2007 so don't make fun of my coding!
Python
8
star
41

plone-extract

An easy to-use script which serializes Plone data so you can import it into another CMS.
8
star
42

simplicity-complexity-example

An example using simplicity and complexity.
JavaScript
8
star
43

pinax-dances

Tutorial example application for building a pinax flash mob dance site
Python
6
star
44

django-widget-override-test-case

Resolved: Test case demonstrating that overriding Django's form widget input.html template fails
Python
6
star
45

pinax-wall

Tutorial group aware wall application
Python
6
star
46

watdarepo

Determines VCS and host service of a repo.
Python
5
star
47

pycon2012-oauth-sprint

Python
5
star
48

receiptify

Uses complexity and simplicity to generate receipts
Python
5
star
49

slides

Various slide decks
Python
4
star
50

pydanny-v2

Vue
4
star
51

old_pydanny

My in-progress personal website, pydanny.com
JavaScript
4
star
52

jupyter-book-epub

Makes it possible for epubs rendered by Jupyter Book (https://jupyterbook.org/) to build epubs with working tables of content.
Python
4
star
53

tag_project

Fork of the Pinax social_project used to work on django-tagging-ext
Python
3
star
54

listo

An enhanced list for Python
Python
3
star
55

friends_project

My test project for friends breakup in Pinax
Python
3
star
56

strings

Python strings for humans
Python
3
star
57

presentations

Python
3
star
58

bookmanager

A project to manage my books and book wishlist.
JavaScript
3
star
59

shortener_netlify

Using netlify to handle short URLs
HTML
3
star
60

python-party-pack

Python
3
star
61

django-naught

Introspection application for Django
Python
3
star
62

Products.humanator

CAPTCHA alternative for Plone 3.x
Python
3
star
63

djadmin2-theme-1995

An simple example of how to build a django-admin2 theme.
Python
3
star
64

isthisfor

A way to validate your startup idea!
2
star
65

django-uni-form-examples

This provides working examples of django-uni-form in action.
2
star
66

blarg

Python
2
star
67

vue-tailwindcss-starter

Vue
2
star
68

pygments-custom

My personal fork of pygments implemented so I could have a custom theme because I can't figure out how to do this otherwise.
Python
2
star
69

graph_vizualizer

Just some fun hacking to try and model something
Python
2
star
70

roda

JavaScript
2
star
71

map_project

My project to map out applications to each other
Python
2
star
72

oauthlib-examples

Using OAuthLib against various sources.
2
star
73

django-simplegraph

Automatically exported from code.google.com/p/django-simplegraph
Python
2
star
74

releases

Python
2
star
75

awod4

1
star
76

pydanny.com

Vue
1
star
77

dj-notebook-slides

JavaScript
1
star
78

Valley-Capoeira

Where we host content and files.
1
star
79

pydanny

1
star
80

django-application-manager

Automatically exported from code.google.com/p/django-application-manager
Python
1
star
81

dynow-trip-2013

1
star
82

arepa

Arepa-making utility
Makefile
1
star
83

mktg

HTML
1
star
84

lsystem-abop

Python
1
star
85

site-2024

CSS
1
star
86

audreyr.github.com

JavaScript
1
star
87

opencomparison.github.com

Github org page for opencomparison
JavaScript
1
star
88

fasthtml-dashboard-tutorial

Jupyter Notebook
1
star
89

fasthtml-dashboard-tutorial-old

Python
1
star
90

fh-blog

Python
1
star