==========
kids.cache
==========
.. image:: http://img.shields.io/pypi/v/kids.cache.svg?style=flat
:target: https://pypi.python.org/pypi/kids.cache/
:alt: Latest PyPI version
.. image:: http://img.shields.io/pypi/dm/kids.cache.svg?style=flat
:target: https://pypi.python.org/pypi/kids.cache/
:alt: Number of PyPI downloads
.. image:: http://img.shields.io/travis/0k/kids.cache/master.svg?style=flat
:target: https://travis-ci.org/0k/kids.cache/
:alt: Travis CI build status
.. image:: http://img.shields.io/coveralls/0k/kids.cache/master.svg?style=flat
:target: https://coveralls.io/r/0k/kids.cache
:alt: Test coverage
``kids.cache`` is a Python library providing a cache decorator.
It's part of 'Kids' (for Keep It Dead Simple) library. It has
no dependency to any python library.
Its main concern is to offer a very simple default usage scheme,
without forgetting to offer full power inside when needed.
Maturity
========
This code is around ~100 lines of python, and it has a 100% test
coverage.
However it still considered beta stage currently.
Compatibility
=============
It is small and simple and should work anywhere.
To put it in longer details: the current code is simple enough that it
use a common subset of python that is compatible with any platform on
python 2.7 and python >= 3... and this without any specific
modification.
Even then, You'll be happy to know that, this code is tested for
compatibility at each commit with python 2.7, 3.4, 3.5, 3.6 on linux
and windows platform.
Features
========
- Use one simple call to ``@cache``, and a majority of all hidden complexity
will vanish.
- works out of the box everywhere you can stick a decorator
(function, methods, property, classes...).
- support to be called before or after common decorators as
``@property``, ``@classmethod``, ``@staticmethod``.
- With ``@cache`` several design pattern can be achieved:
- *memoization* when used on function with arguments.
- *lazy evaluation* when placed on properties.
- *singleton* patterns when placed on classes.
- Full customization at disposition:
- cache clearing or cache stats functionality.
- support of any cache store mecanism from `cachetools`_ package.
- support of custom key function which allows:
- support of your exotic unhashable objects
- fine tune which function calls can be considered identic
- hand pick function dependencies in object (for method)
.. _cachetools: https://github.com/tkem/cachetools
Basic Usage
===========
Function
--------
This cache decorator is quite straightforward to use::
>>> from kids.cache import cache
>>> @cache
... def answer_to_everything():
... print("many insightfull calculation")
... return 42
Then the function ``answer_to_everything`` would only do the
calculation the first time called, and would save the result, and
directly return it the next calls::
>>> answer_to_everything()
many insightfull calculation
42
>>> answer_to_everything()
42
The body of the function was not executed anymore and the cache value
was used.
It'll work with arguments::
>>> @cache
... def mysum(*args):
... print("calculating...")
... return sum(args)
>>> mysum(2, 2, 3)
calculating...
7
>>> mysum(1, 1, 1, 1)
calculating...
4
>>> mysum(2, 2, 3)
7
>>> mysum(1, 1, 1, 1)
4
And notice that by default, object are not typed, thus::
>>> mysum(1.0, 1, 1, 1)
4
Did trigger the cache, despite the first argument is a float and not
an integer.
Methods
-------
With methods::
>>> class MyObject(object):
... def __init__(self, a, b):
... self.a, self.b = a, b
...
... @cache
... def total(self):
... print("calculating...")
... return self.a + self.b
>>> xx = MyObject(2, 3)
>>> xx.total()
calculating...
5
>>> xx.total()
5
Cache is not shared between instances::
>>> yy = MyObject(2, 3)
>>> yy.total()
calculating...
5
Of course, if you change the inner values of the instance, this
will NOT be detected by the caching method::
>>> xx.a = 5
>>> xx.total()
5
Look at advanced usages to see how to changes some of these behaviors.
Property
--------
You can use the ``cache`` decorator with properties, and
provides a good way to have lazy evaluated attributes::
>>> class WithProperty(MyObject):
...
... @property
... @cache
... def total(self):
... print("evaluating...")
... return self.a + self.b
>>> xx = WithProperty(1, 1)
>>> xx.total
evaluating...
2
>>> xx.total
2
You can use ``@cache`` decorator before or after ``@property``
decorator::
>>> class WithProperty(MyObject):
...
... @cache
... @property
... def total(self):
... print("evaluating...")
... return self.a + self.b
>>> xx = WithProperty(2, 2)
>>> xx.total
evaluating...
4
>>> xx.total
4
classmethod
-----------
You can use the ``cache`` decorator with classmethods, and
provides a good way to share cache between instances::
>>> class WithClassMethod(MyObject):
...
... a = 2
... b = 3
...
... @classmethod
... @cache
... def total(cls):
... print("evaluating...")
... return cls.a + cls.b
>>> WithClassMethod.total()
evaluating...
5
>>> WithClassMethod.total()
5
You can use ``@cache`` decorator before or after ``@property``
decorator::
>>> class WithClassMethod(MyObject):
...
... a = 1
... b = 6
...
... @cache
... @classmethod
... def total(cls):
... print("evaluating...")
... return cls.a + cls.b
>>> WithClassMethod.total()
evaluating...
7
>>> WithClassMethod.total()
7
staticmethod
------------
You can use the ``cache`` decorator with staticmethods::
>>> class WithStaticMethod(MyObject):
...
... @staticmethod
... @cache
... def total(a, b):
... print("evaluating...")
... return a + b
>>> WithStaticMethod.total(1, 3)
evaluating...
4
>>> WithStaticMethod.total(1, 3)
4
You can use ``@cache`` decorator before or after ``@property``
decorator::
>>> class WithStaticMethod(MyObject):
...
... @cache
... @staticmethod
... def total(a, b):
... print("evaluating...")
... return a + b
>>> WithStaticMethod.total(2, 6)
evaluating...
8
>>> WithStaticMethod.total(2, 6)
8
class
-----
Using ``cache`` with classes will allow variations around the
notion of singletons. A singleton shares the same id in memory,
so this shows a classical non-singleton behavior::
>>> a, b = object(), object()
>>> id(a) == id(b)
False
Factory based singleton
~~~~~~~~~~~~~~~~~~~~~~~
You can use the ``cache`` decorator with classes, effectively
implementing a factory pattern for creating singleton::
>>> @cache
... class MySingleton(MyObject):
... def __new__(cls):
... print("instanciating...")
... return MyObject.__new__(cls)
... def __init__(self):
... print("initializing...")
>>> a, b = MySingleton(), MySingleton()
instanciating...
initializing...
>>> id(a) == id(b)
True
Notice that both instance are the same object, so it was only
instanciated and initialized once.
But be warned: this is not anymore a class::
>>> MySingleton
<function MySingleton at ...>
Instanciation based singletons
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Slightly different, the class singleton pattern can be achieved by
caching ``__new__``::
>>> class MySingleton(MyObject):
... @cache
... def __new__(cls):
... print("instanciating...")
... return MyObject.__new__(cls)
... def __init__(self):
... print("initializing...")
>>> a, b = MySingleton(), MySingleton()
instanciating...
initializing...
initializing...
>>> id(a) == id(b)
True
Notice that both instance are the same object, so it was only
instanciated once. But the ``__init__`` was called both times.
This is sometimes perfectly valid, but you might want to avoid this
also.
So if you don't want this, you should cache also ``__init__`` method::
>>> class MySingleton(MyObject):
... @cache
... def __new__(cls):
... print("instanciating...")
... return MyObject.__new__(cls)
... @cache
... def __init__(self):
... print("initializing...")
>>> a, b = MySingleton(), MySingleton()
instanciating...
initializing...
>>> id(a) == id(b)
True
For both cases you'll keep your full object untouched of course::
>>> MySingleton
<class 'MySingleton'>
Singleton with arguments
~~~~~~~~~~~~~~~~~~~~~~~~
Actually, these are only singletons if you call them successively with
the same arguments.
Or to be more precise, you can share your classes when their
instanciation's arguments are the same::
>>> @cache
... class MySingleton(MyObject):
... def __init__(self, a):
... self.a = a
... print("evaluating...")
>>> a, b = MySingleton(1), MySingleton(2)
evaluating...
evaluating...
>>> id(a) == id(b)
False
But::
>>> c = MySingleton(1)
>>> id(a) == id(c)
True
If you want a singleton that give you the same instance even if your
successive calls differs, you should check the advanced usage section
and the ``key`` argument.
Advanced Usage
==============
Most of the advanced usage implies to call the ``@cache`` decorator with
arguments. Please notice that::
>>> @cache
... def mysum1(*args):
... print("calculating...")
... return sum(args)
Or::
>>> @cache()
... def mysum2(*args):
... print("calculating...")
... return sum(args)
is equivalent::
>>> mysum1(1,1)
calculating...
2
>>> mysum1(1,1)
2
>>> mysum2(1,1)
calculating...
2
>>> mysum2(1,1)
2
Provide a key function
----------------------
Providing a key function can be extremely powerfull and will allow to
fine tune when the cache should be recalculated.
``hashing`` functions will receive exactly the same arguments than the
main function called. It must return an hashable structure
(combination of ``tuples``, ``int``, ``string``... avoid list, dicts and
sets). This will identify uniquely the result.
For example you could::
>>> class WithKey(MyObject):
... @cache(key=lambda s: (id(s), s.a, s.b))
... def total(self):
... print("calculating...")
... return self.a + self.b
>>> xx = WithKey(2, 3)
>>> xx.total()
calculating...
5
>>> xx.total()
5
It should detect changes of the given values of the instance::
>>> xx.a = 5
>>> xx.total()
calculating...
8
Without bothering to recalculate when other values change::
>>> xx.c = 7
>>> xx.total()
8
But it should still make a difference between instances::
>>> yy = WithKey(2, 3)
>>> yy.total()
calculating...
5
This last example is important as you could have wanted to share the
cache between all instances. You could have done this easily by
avoiding returning ``id(s)`` in the ``key`` function.
Typed key functions
-------------------
You could ask for ``typed`` argument to NOT be treated the same::
>>> @cache(typed=True)
... def mysum(*args):
... print("calculating...")
... return sum(args)
>>> mysum(1, 1)
calculating...
2
>>> mysum(1.0, 1)
calculating...
2.0
default key functions
---------------------
The default key function if not provided is a bold try to make ``list``
and ``dict``, ``set`` also keyable despite these not being hashable.
The name of the key function is called ``hippie_hashing``, and this is
the default value for the key argument::
>>> from kids.cache import hippie_hashing
>>> @cache(key=hippie_hashing)
... def mylength(obj):
... return len(obj)
This allows you to use the function with list, dict or combination of these::
>>> mylength([set([3]), 2, {1: 2}])
3
Even your objects could be used as key, as long as they are hashable::
>>> class MyObj(object): ## object subclasses have a default hash
... length = 5
... def __len__(self, ):
... print('calculating...')
... return self.length
>>> myobj = MyObj()
>>> mylength(myobj)
calculating...
5
>>> mylength(myobj)
5
Be assured that hash collision (they happen!) won't generate cache collisions::
>>> class MyCollidingHashObj(MyObj):
... def __init__(self, length):
... self.length = length
... def __hash__(self):
... return 1
>>> hash_collide1 = MyCollidingHashObj(6)
>>> hash_collide2 = MyCollidingHashObj(7)
>>> mylength(hash_collide1)
calculating...
6
>>> mylength(hash_collide2)
calculating...
7
But try to avoid them for performance's sake !! And you should
probably be aware that if your object compare equal, then THERE WILL
BE a cache collision (but at this point, this is probably what you
wanted, heh ?)::
>>> class MyEqCollidingHashObj(MyCollidingHashObj):
... def __eq__(self, value):
... return True
... def __hash__(self):
... return 1
>>> eq_and_hash_collide1 = MyEqCollidingHashObj(8)
>>> eq_and_hash_collide2 = MyEqCollidingHashObj(9)
>>> mylength(eq_and_hash_collide1)
calculating...
8
>>> mylength(eq_and_hash_collide2)
8
Huh oh. This is not what was probably expected in this example, but
you really had to work hard to make this happen. And most of the time,
you'll probably find this convenient and will use it at you advantage.
It's a little bit like an extension of the ``key`` mecanism that is
the objects responsability.
.. note:: Please verify also that if your object compares the same, their
hash HAS TO BE the same. For this very reason, in Python3, when you
define the ``__eq__`` method, it'll remove the default ``__hash__``
from objects.
Of course, ``hippie_hashing`` will fail on special unhashable object::
>>> class Unhashable(object):
... def __hash__(self):
... raise ValueError("unhashable!")
>>> hippie_hashing(Unhashable()) ## doctest: +ELLIPSIS
Traceback (most recent call last):
...
ValueError: <Unhashable ...> can not be hashed. Try providing a custom key function.
If you are not a hippie, you should consider using ``strict=True`` and a
much more limited method will be used to make a key from your
arguments::
>>> @cache(strict=True)
... def mylength(obj):
... return len(obj)
>>> mylength("hello")
5
But then, don't be surprised if it fails with dict, list, or set arguments::
>>> mylength([set([3]), 2, {1: 2}])
Traceback (most recent call last):
...
TypeError: unhashable type: 'list'
And ``typed=True`` can be used in combination with ``strict=True``::
>>> @cache(strict=True, typed=True)
... def mysum(*args):
... print("calculating...")
... return sum(args)
>>> mysum(1, 1)
calculating...
2
>>> mysum(1.0, 1)
calculating...
2.0
A good key function can:
- make some cache timeout (but you should then look at cache store
section to limit the size of the cache)
- finely select which argument are pertinent to the method to avoid
re-evaluating the function when it is non-necessary.
- allow you to cache callables that have very special arguments that
can't be hashed properly.
Cleaning Cache
--------------
``kids.cache`` uses some ``lru_cache`` ideas of python 3
implementation, and each function cached received a ``cache_clear``
method::
>>> @cache
... def mysum(*args):
... print("calculate...")
... return sum(args)
>>> mysum(1,1)
calculate...
2
>>> mysum(1,1)
2
By calling ``cache_clear`` method, we flush all previous cached value::
>>> mysum.cache_clear()
>>> mysum(1,1)
calculate...
2
Cache stats
-----------
``kids.cache`` uses some ``lru_cache`` ideas of python 3
implementation, and each function cached received a ``cache_info``
method::
>>> @cache
... def mysum(*args):
... print("calculate...")
... return sum(args)
>>> mysum(1,1)
calculate...
2
>>> mysum(1,1)
2
>>> mysum.cache_info()
CacheInfo(type='dict', hits=1, misses=1, maxsize=None, currsize=1)
Cache Store
-----------
``kids.cache`` can use any dict-like structure as a cache store. This
means you can provide some more clever cache stores. For example, you
can use ``cachetools`` caches under the hood to manage the caching store.
Keep in mind that the default cache store is... a dict ! which is not
a good idea if your program will run for a long time and you have
cached function calls that will be different throughout the running
time: the cache store will then grow for each new call making the
memory usage of your process grow... perhaps out of bounds.
In these scenario, you must think about using managed cache stores that
will clean and remove old unused cache entries. There are many cache
store provided in ``cachetools`` and ``kids.cache`` supports them all.
So if you need any caching store from ``cachetools`` you can provide
it::
>>> from cachetools import LRUCache
LRU stands for Least Recent Used... ::
>>> @cache(use=LRUCache(maxsize=2))
... def mysum(*args):
... print("calculate...")
... return sum(args)
>>> mysum(1, 1)
calculate...
2
>>> mysum(1, 2)
calculate...
3
>>> mysum(1, 3)
calculate...
4
We have exceeded the cache memory and the least recent used have been
tossed away::
>>> mysum(1, 1)
calculate...
2
But we still have this one in memory::
>>> mysum(1, 3)
4
Contributing
============
Any suggestion or issue is welcome. Push request are very welcome,
please check out the guidelines.
Push Request Guidelines
-----------------------
You can send any code. I'll look at it and will integrate it myself in
the code base and leave you as the author. This process can take time and
it'll take less time if you follow the following guidelines:
- check your code with PEP8 or pylint. Try to stick to 80 columns wide.
- separate your commits per smallest concern.
- each commit should pass the tests (to allow easy bisect)
- each functionality/bugfix commit should contain the code, tests,
and doc.
- prior minor commit with typographic or code cosmetic changes are
very welcome. These should be tagged in their commit summary with
``!minor``.
- the commit message should follow gitchangelog rules (check the git
log to get examples)
- if the commit fixes an issue or finished the implementation of a
feature, please mention it in the summary.
If you have some questions about guidelines which is not answered here,
please check the current ``git log``, you might find previous commit that
would show you how to deal with your issue.
License
=======
Copyright (c) 2017 Valentin Lab.
Licensed under the `BSD License`_.
.. _BSD License: http://raw.github.com/0k/kids.cache/master/LICENSE
Changelog
=========
0.0.7 (2017-11-16)
------------------
Fix
~~~
- ReST inconsistency between generated changelog and ``README.rst``.
[Valentin Lab]
This prevented PyPI page to be rendered properly.
0.0.6 (2017-11-16)
------------------
Fix
~~~
- Fixed import time performance issue due to obsolete namespacing
pattern. (fixes #9) [Valentin Lab]
0.0.4 (2015-04-27)
------------------
New
~~~
- Support being called before or after ``staticmethod`` decorator.
[Valentin Lab]
- Support being called before or after ``classmethod`` decorator.
[Valentin Lab]
Changes
~~~~~~~
- Documenting the singleton pattern usage when used in conjunction with
``class``. [Valentin Lab]
0.0.3 (2015-02-24)
------------------
Fix
~~~
- Nasty cache collision if two custom objects shared the same hash and
type but where not ``equal``. [Valentin Lab]
And as a matter of fact, this happens. For instance, all instance of
``object`` or any subclass will inherit a special ``hash`` method that
uses ``id``, but in some version of python (the recent ones), the ``id``
value is divided by ``16``. And hash collisions are to be expected
anyway, and of course should not cause cache collisions.
0.0.2 (2015-02-02)
------------------
New
~~~
- Added type to cache stats, removed dependency to ``cachetools``.
[Valentin Lab]
Changes
~~~~~~~
- Default cache store's ``currsize`` use the ``len()`` instead of None.
[Valentin Lab]
And this makes sense for the default dict implementation.
Fix
~~~
- Wrong attribution for ``cache_clear`` and ``cache_info`` functions.
[Valentin Lab]
- Similar ``set`` could get different hash. [Valentin Lab]
``set`` weren't sorted prior to introspection for hashing.
0.0.1 (2014-05-23)
------------------
- First import. [Valentin Lab]
Raw data
{
"_id": null,
"home_page": "http://github.com/0k/kids.cache",
"name": "kids.cache",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "",
"author": "Valentin LAB",
"author_email": "valentin.lab@kalysto.org",
"download_url": "https://files.pythonhosted.org/packages/36/fe/12a3ac5da6f3b5b70da5e59db908b24d0fa1797954a3210bb7146020153c/kids.cache-0.0.7.tar.gz",
"platform": "",
"description": "==========\nkids.cache\n==========\n\n\n.. image:: http://img.shields.io/pypi/v/kids.cache.svg?style=flat\n :target: https://pypi.python.org/pypi/kids.cache/\n :alt: Latest PyPI version\n\n.. image:: http://img.shields.io/pypi/dm/kids.cache.svg?style=flat\n :target: https://pypi.python.org/pypi/kids.cache/\n :alt: Number of PyPI downloads\n\n.. image:: http://img.shields.io/travis/0k/kids.cache/master.svg?style=flat\n :target: https://travis-ci.org/0k/kids.cache/\n :alt: Travis CI build status\n\n.. image:: http://img.shields.io/coveralls/0k/kids.cache/master.svg?style=flat\n :target: https://coveralls.io/r/0k/kids.cache\n :alt: Test coverage\n\n\n``kids.cache`` is a Python library providing a cache decorator.\nIt's part of 'Kids' (for Keep It Dead Simple) library. It has\nno dependency to any python library.\n\nIts main concern is to offer a very simple default usage scheme,\nwithout forgetting to offer full power inside when needed.\n\n\n\nMaturity\n========\n\nThis code is around ~100 lines of python, and it has a 100% test\ncoverage.\n\nHowever it still considered beta stage currently.\n\n\nCompatibility\n=============\n\n\nIt is small and simple and should work anywhere.\n\nTo put it in longer details: the current code is simple enough that it\nuse a common subset of python that is compatible with any platform on\npython 2.7 and python >= 3... and this without any specific\nmodification.\n\nEven then, You'll be happy to know that, this code is tested for\ncompatibility at each commit with python 2.7, 3.4, 3.5, 3.6 on linux\nand windows platform.\n\n\nFeatures\n========\n\n- Use one simple call to ``@cache``, and a majority of all hidden complexity\n will vanish.\n\n - works out of the box everywhere you can stick a decorator\n (function, methods, property, classes...).\n - support to be called before or after common decorators as\n ``@property``, ``@classmethod``, ``@staticmethod``.\n\n- With ``@cache`` several design pattern can be achieved:\n\n - *memoization* when used on function with arguments.\n - *lazy evaluation* when placed on properties.\n - *singleton* patterns when placed on classes.\n\n- Full customization at disposition:\n\n - cache clearing or cache stats functionality.\n - support of any cache store mecanism from `cachetools`_ package.\n - support of custom key function which allows:\n\n - support of your exotic unhashable objects\n - fine tune which function calls can be considered identic\n - hand pick function dependencies in object (for method)\n\n\n.. _cachetools: https://github.com/tkem/cachetools\n\n\nBasic Usage\n===========\n\nFunction\n--------\n\nThis cache decorator is quite straightforward to use::\n\n >>> from kids.cache import cache\n\n >>> @cache\n ... def answer_to_everything():\n ... print(\"many insightfull calculation\")\n ... return 42\n\nThen the function ``answer_to_everything`` would only do the\ncalculation the first time called, and would save the result, and\ndirectly return it the next calls::\n\n >>> answer_to_everything()\n many insightfull calculation\n 42\n\n >>> answer_to_everything()\n 42\n\nThe body of the function was not executed anymore and the cache value\nwas used.\n\nIt'll work with arguments::\n\n >>> @cache\n ... def mysum(*args):\n ... print(\"calculating...\")\n ... return sum(args)\n\n >>> mysum(2, 2, 3)\n calculating...\n 7\n >>> mysum(1, 1, 1, 1)\n calculating...\n 4\n >>> mysum(2, 2, 3)\n 7\n >>> mysum(1, 1, 1, 1)\n 4\n\nAnd notice that by default, object are not typed, thus::\n\n >>> mysum(1.0, 1, 1, 1)\n 4\n\nDid trigger the cache, despite the first argument is a float and not\nan integer.\n\n\nMethods\n-------\n\nWith methods::\n\n >>> class MyObject(object):\n ... def __init__(self, a, b):\n ... self.a, self.b = a, b\n ...\n ... @cache\n ... def total(self):\n ... print(\"calculating...\")\n ... return self.a + self.b\n\n >>> xx = MyObject(2, 3)\n >>> xx.total()\n calculating...\n 5\n >>> xx.total()\n 5\n\nCache is not shared between instances::\n\n >>> yy = MyObject(2, 3)\n >>> yy.total()\n calculating...\n 5\n\nOf course, if you change the inner values of the instance, this\nwill NOT be detected by the caching method::\n\n >>> xx.a = 5\n >>> xx.total()\n 5\n\nLook at advanced usages to see how to changes some of these behaviors.\n\n\nProperty\n--------\n\nYou can use the ``cache`` decorator with properties, and\nprovides a good way to have lazy evaluated attributes::\n\n >>> class WithProperty(MyObject):\n ...\n ... @property\n ... @cache\n ... def total(self):\n ... print(\"evaluating...\")\n ... return self.a + self.b\n\n >>> xx = WithProperty(1, 1)\n >>> xx.total\n evaluating...\n 2\n >>> xx.total\n 2\n\nYou can use ``@cache`` decorator before or after ``@property``\ndecorator::\n\n >>> class WithProperty(MyObject):\n ...\n ... @cache\n ... @property\n ... def total(self):\n ... print(\"evaluating...\")\n ... return self.a + self.b\n\n >>> xx = WithProperty(2, 2)\n >>> xx.total\n evaluating...\n 4\n >>> xx.total\n 4\n\nclassmethod\n-----------\n\nYou can use the ``cache`` decorator with classmethods, and\nprovides a good way to share cache between instances::\n\n >>> class WithClassMethod(MyObject):\n ...\n ... a = 2\n ... b = 3\n ...\n ... @classmethod\n ... @cache\n ... def total(cls):\n ... print(\"evaluating...\")\n ... return cls.a + cls.b\n\n >>> WithClassMethod.total()\n evaluating...\n 5\n >>> WithClassMethod.total()\n 5\n\nYou can use ``@cache`` decorator before or after ``@property``\ndecorator::\n\n >>> class WithClassMethod(MyObject):\n ...\n ... a = 1\n ... b = 6\n ...\n ... @cache\n ... @classmethod\n ... def total(cls):\n ... print(\"evaluating...\")\n ... return cls.a + cls.b\n\n >>> WithClassMethod.total()\n evaluating...\n 7\n >>> WithClassMethod.total()\n 7\n\nstaticmethod\n------------\n\nYou can use the ``cache`` decorator with staticmethods::\n\n >>> class WithStaticMethod(MyObject):\n ...\n ... @staticmethod\n ... @cache\n ... def total(a, b):\n ... print(\"evaluating...\")\n ... return a + b\n\n >>> WithStaticMethod.total(1, 3)\n evaluating...\n 4\n >>> WithStaticMethod.total(1, 3)\n 4\n\nYou can use ``@cache`` decorator before or after ``@property``\ndecorator::\n\n >>> class WithStaticMethod(MyObject):\n ...\n ... @cache\n ... @staticmethod\n ... def total(a, b):\n ... print(\"evaluating...\")\n ... return a + b\n\n >>> WithStaticMethod.total(2, 6)\n evaluating...\n 8\n >>> WithStaticMethod.total(2, 6)\n 8\n\n\nclass\n-----\n\nUsing ``cache`` with classes will allow variations around the \nnotion of singletons. A singleton shares the same id in memory,\nso this shows a classical non-singleton behavior::\n\n >>> a, b = object(), object()\n >>> id(a) == id(b)\n False\n\n\nFactory based singleton\n~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can use the ``cache`` decorator with classes, effectively\nimplementing a factory pattern for creating singleton::\n\n >>> @cache\n ... class MySingleton(MyObject):\n ... def __new__(cls):\n ... print(\"instanciating...\")\n ... return MyObject.__new__(cls)\n ... def __init__(self):\n ... print(\"initializing...\")\n\n >>> a, b = MySingleton(), MySingleton()\n instanciating...\n initializing...\n >>> id(a) == id(b)\n True\n\nNotice that both instance are the same object, so it was only\ninstanciated and initialized once.\n\nBut be warned: this is not anymore a class::\n\n >>> MySingleton\n <function MySingleton at ...>\n\n\nInstanciation based singletons\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nSlightly different, the class singleton pattern can be achieved by\ncaching ``__new__``::\n\n >>> class MySingleton(MyObject):\n ... @cache\n ... def __new__(cls):\n ... print(\"instanciating...\")\n ... return MyObject.__new__(cls)\n ... def __init__(self):\n ... print(\"initializing...\")\n\n >>> a, b = MySingleton(), MySingleton()\n instanciating...\n initializing...\n initializing...\n >>> id(a) == id(b)\n True\n\nNotice that both instance are the same object, so it was only\ninstanciated once. But the ``__init__`` was called both times.\nThis is sometimes perfectly valid, but you might want to avoid this\nalso.\n\nSo if you don't want this, you should cache also ``__init__`` method::\n\n >>> class MySingleton(MyObject):\n ... @cache\n ... def __new__(cls):\n ... print(\"instanciating...\")\n ... return MyObject.__new__(cls)\n ... @cache\n ... def __init__(self):\n ... print(\"initializing...\")\n\n >>> a, b = MySingleton(), MySingleton()\n instanciating...\n initializing...\n >>> id(a) == id(b)\n True\n\nFor both cases you'll keep your full object untouched of course::\n\n >>> MySingleton\n <class 'MySingleton'>\n\n\nSingleton with arguments\n~~~~~~~~~~~~~~~~~~~~~~~~\n\nActually, these are only singletons if you call them successively with\nthe same arguments.\n\nOr to be more precise, you can share your classes when their\ninstanciation's arguments are the same::\n\n >>> @cache\n ... class MySingleton(MyObject):\n ... def __init__(self, a):\n ... self.a = a\n ... print(\"evaluating...\")\n\n >>> a, b = MySingleton(1), MySingleton(2)\n evaluating...\n evaluating...\n >>> id(a) == id(b)\n False\n\nBut::\n\n >>> c = MySingleton(1)\n >>> id(a) == id(c)\n True\n\nIf you want a singleton that give you the same instance even if your\nsuccessive calls differs, you should check the advanced usage section\nand the ``key`` argument.\n\n\nAdvanced Usage\n==============\n\nMost of the advanced usage implies to call the ``@cache`` decorator with\narguments. Please notice that::\n\n >>> @cache\n ... def mysum1(*args):\n ... print(\"calculating...\")\n ... return sum(args)\n\nOr::\n\n >>> @cache()\n ... def mysum2(*args):\n ... print(\"calculating...\")\n ... return sum(args)\n\nis equivalent::\n\n >>> mysum1(1,1)\n calculating...\n 2\n >>> mysum1(1,1)\n 2\n\n >>> mysum2(1,1)\n calculating...\n 2\n >>> mysum2(1,1)\n 2\n\n\nProvide a key function\n----------------------\n\nProviding a key function can be extremely powerfull and will allow to\nfine tune when the cache should be recalculated.\n\n``hashing`` functions will receive exactly the same arguments than the\nmain function called. It must return an hashable structure\n(combination of ``tuples``, ``int``, ``string``... avoid list, dicts and\nsets). This will identify uniquely the result.\n\nFor example you could::\n\n >>> class WithKey(MyObject):\n ... @cache(key=lambda s: (id(s), s.a, s.b))\n ... def total(self):\n ... print(\"calculating...\")\n ... return self.a + self.b\n\n >>> xx = WithKey(2, 3)\n >>> xx.total()\n calculating...\n 5\n >>> xx.total()\n 5\n\nIt should detect changes of the given values of the instance::\n\n >>> xx.a = 5\n >>> xx.total()\n calculating...\n 8\n\nWithout bothering to recalculate when other values change::\n\n >>> xx.c = 7\n >>> xx.total()\n 8\n\nBut it should still make a difference between instances::\n\n >>> yy = WithKey(2, 3)\n >>> yy.total()\n calculating...\n 5\n\nThis last example is important as you could have wanted to share the\ncache between all instances. You could have done this easily by\navoiding returning ``id(s)`` in the ``key`` function.\n\n\nTyped key functions\n-------------------\n\nYou could ask for ``typed`` argument to NOT be treated the same::\n\n >>> @cache(typed=True)\n ... def mysum(*args):\n ... print(\"calculating...\")\n ... return sum(args)\n >>> mysum(1, 1)\n calculating...\n 2\n\n >>> mysum(1.0, 1)\n calculating...\n 2.0\n\n\ndefault key functions\n---------------------\n\nThe default key function if not provided is a bold try to make ``list``\nand ``dict``, ``set`` also keyable despite these not being hashable.\n\nThe name of the key function is called ``hippie_hashing``, and this is\nthe default value for the key argument::\n\n >>> from kids.cache import hippie_hashing\n\n >>> @cache(key=hippie_hashing)\n ... def mylength(obj):\n ... return len(obj)\n\nThis allows you to use the function with list, dict or combination of these::\n\n >>> mylength([set([3]), 2, {1: 2}])\n 3\n\nEven your objects could be used as key, as long as they are hashable::\n\n >>> class MyObj(object): ## object subclasses have a default hash\n ... length = 5\n ... def __len__(self, ):\n ... print('calculating...')\n ... return self.length\n\n >>> myobj = MyObj()\n >>> mylength(myobj)\n calculating...\n 5\n\n >>> mylength(myobj)\n 5\n\nBe assured that hash collision (they happen!) won't generate cache collisions::\n\n >>> class MyCollidingHashObj(MyObj):\n ... def __init__(self, length):\n ... self.length = length\n ... def __hash__(self):\n ... return 1\n\n >>> hash_collide1 = MyCollidingHashObj(6)\n >>> hash_collide2 = MyCollidingHashObj(7)\n\n >>> mylength(hash_collide1)\n calculating...\n 6\n >>> mylength(hash_collide2)\n calculating...\n 7\n\nBut try to avoid them for performance's sake !! And you should\nprobably be aware that if your object compare equal, then THERE WILL\nBE a cache collision (but at this point, this is probably what you\nwanted, heh ?)::\n\n >>> class MyEqCollidingHashObj(MyCollidingHashObj):\n ... def __eq__(self, value):\n ... return True\n ... def __hash__(self):\n ... return 1\n\n >>> eq_and_hash_collide1 = MyEqCollidingHashObj(8)\n >>> eq_and_hash_collide2 = MyEqCollidingHashObj(9)\n\n >>> mylength(eq_and_hash_collide1)\n calculating...\n 8\n >>> mylength(eq_and_hash_collide2)\n 8\n\nHuh oh. This is not what was probably expected in this example, but\nyou really had to work hard to make this happen. And most of the time,\nyou'll probably find this convenient and will use it at you advantage.\nIt's a little bit like an extension of the ``key`` mecanism that is\nthe objects responsability.\n\n.. note:: Please verify also that if your object compares the same, their\n hash HAS TO BE the same. For this very reason, in Python3, when you\n define the ``__eq__`` method, it'll remove the default ``__hash__``\n from objects.\n\n\nOf course, ``hippie_hashing`` will fail on special unhashable object::\n\n >>> class Unhashable(object):\n ... def __hash__(self):\n ... raise ValueError(\"unhashable!\")\n\n >>> hippie_hashing(Unhashable()) ## doctest: +ELLIPSIS\n Traceback (most recent call last):\n ...\n ValueError: <Unhashable ...> can not be hashed. Try providing a custom key function.\n\nIf you are not a hippie, you should consider using ``strict=True`` and a\nmuch more limited method will be used to make a key from your\narguments::\n\n >>> @cache(strict=True)\n ... def mylength(obj):\n ... return len(obj)\n\n >>> mylength(\"hello\")\n 5\n\nBut then, don't be surprised if it fails with dict, list, or set arguments::\n\n >>> mylength([set([3]), 2, {1: 2}])\n Traceback (most recent call last):\n ...\n TypeError: unhashable type: 'list'\n\n\nAnd ``typed=True`` can be used in combination with ``strict=True``::\n\n >>> @cache(strict=True, typed=True)\n ... def mysum(*args):\n ... print(\"calculating...\")\n ... return sum(args)\n >>> mysum(1, 1)\n calculating...\n 2\n\n >>> mysum(1.0, 1)\n calculating...\n 2.0\n\nA good key function can:\n\n- make some cache timeout (but you should then look at cache store\n section to limit the size of the cache)\n- finely select which argument are pertinent to the method to avoid\n re-evaluating the function when it is non-necessary.\n- allow you to cache callables that have very special arguments that\n can't be hashed properly.\n\n\nCleaning Cache\n--------------\n\n``kids.cache`` uses some ``lru_cache`` ideas of python 3\nimplementation, and each function cached received a ``cache_clear``\nmethod::\n\n >>> @cache\n ... def mysum(*args):\n ... print(\"calculate...\")\n ... return sum(args)\n\n >>> mysum(1,1)\n calculate...\n 2\n >>> mysum(1,1)\n 2\n\nBy calling ``cache_clear`` method, we flush all previous cached value::\n\n >>> mysum.cache_clear()\n >>> mysum(1,1)\n calculate...\n 2\n\n\nCache stats\n-----------\n\n``kids.cache`` uses some ``lru_cache`` ideas of python 3\nimplementation, and each function cached received a ``cache_info``\nmethod::\n\n >>> @cache\n ... def mysum(*args):\n ... print(\"calculate...\")\n ... return sum(args)\n\n >>> mysum(1,1)\n calculate...\n 2\n >>> mysum(1,1)\n 2\n\n >>> mysum.cache_info()\n CacheInfo(type='dict', hits=1, misses=1, maxsize=None, currsize=1)\n\n\nCache Store\n-----------\n\n``kids.cache`` can use any dict-like structure as a cache store. This\nmeans you can provide some more clever cache stores. For example, you\ncan use ``cachetools`` caches under the hood to manage the caching store.\n\nKeep in mind that the default cache store is... a dict ! which is not\na good idea if your program will run for a long time and you have\ncached function calls that will be different throughout the running\ntime: the cache store will then grow for each new call making the\nmemory usage of your process grow... perhaps out of bounds.\n\nIn these scenario, you must think about using managed cache stores that\nwill clean and remove old unused cache entries. There are many cache\nstore provided in ``cachetools`` and ``kids.cache`` supports them all.\n\nSo if you need any caching store from ``cachetools`` you can provide\nit::\n\n >>> from cachetools import LRUCache\n\nLRU stands for Least Recent Used... ::\n\n >>> @cache(use=LRUCache(maxsize=2))\n ... def mysum(*args):\n ... print(\"calculate...\")\n ... return sum(args)\n\n >>> mysum(1, 1)\n calculate...\n 2\n >>> mysum(1, 2)\n calculate...\n 3\n >>> mysum(1, 3)\n calculate...\n 4\n\nWe have exceeded the cache memory and the least recent used have been\ntossed away::\n\n >>> mysum(1, 1)\n calculate...\n 2\n\nBut we still have this one in memory::\n\n >>> mysum(1, 3)\n 4\n\n\nContributing\n============\n\nAny suggestion or issue is welcome. Push request are very welcome,\nplease check out the guidelines.\n\n\nPush Request Guidelines\n-----------------------\n\nYou can send any code. I'll look at it and will integrate it myself in\nthe code base and leave you as the author. This process can take time and\nit'll take less time if you follow the following guidelines:\n\n- check your code with PEP8 or pylint. Try to stick to 80 columns wide.\n- separate your commits per smallest concern.\n- each commit should pass the tests (to allow easy bisect)\n- each functionality/bugfix commit should contain the code, tests,\n and doc.\n- prior minor commit with typographic or code cosmetic changes are\n very welcome. These should be tagged in their commit summary with\n ``!minor``.\n- the commit message should follow gitchangelog rules (check the git\n log to get examples)\n- if the commit fixes an issue or finished the implementation of a\n feature, please mention it in the summary.\n\nIf you have some questions about guidelines which is not answered here,\nplease check the current ``git log``, you might find previous commit that\nwould show you how to deal with your issue.\n\n\nLicense\n=======\n\nCopyright (c) 2017 Valentin Lab.\n\nLicensed under the `BSD License`_.\n\n.. _BSD License: http://raw.github.com/0k/kids.cache/master/LICENSE\n\nChangelog\n=========\n\n\n0.0.7 (2017-11-16)\n------------------\n\nFix\n~~~\n- ReST inconsistency between generated changelog and ``README.rst``.\n [Valentin Lab]\n\n This prevented PyPI page to be rendered properly.\n\n\n0.0.6 (2017-11-16)\n------------------\n\nFix\n~~~\n- Fixed import time performance issue due to obsolete namespacing\n pattern. (fixes #9) [Valentin Lab]\n\n\n0.0.4 (2015-04-27)\n------------------\n\nNew\n~~~\n- Support being called before or after ``staticmethod`` decorator.\n [Valentin Lab]\n- Support being called before or after ``classmethod`` decorator.\n [Valentin Lab]\n\nChanges\n~~~~~~~\n- Documenting the singleton pattern usage when used in conjunction with\n ``class``. [Valentin Lab]\n\n\n0.0.3 (2015-02-24)\n------------------\n\nFix\n~~~\n- Nasty cache collision if two custom objects shared the same hash and\n type but where not ``equal``. [Valentin Lab]\n\n And as a matter of fact, this happens. For instance, all instance of\n ``object`` or any subclass will inherit a special ``hash`` method that\n uses ``id``, but in some version of python (the recent ones), the ``id``\n value is divided by ``16``. And hash collisions are to be expected\n anyway, and of course should not cause cache collisions.\n\n\n0.0.2 (2015-02-02)\n------------------\n\nNew\n~~~\n- Added type to cache stats, removed dependency to ``cachetools``.\n [Valentin Lab]\n\nChanges\n~~~~~~~\n- Default cache store's ``currsize`` use the ``len()`` instead of None.\n [Valentin Lab]\n\n And this makes sense for the default dict implementation.\n\nFix\n~~~\n- Wrong attribution for ``cache_clear`` and ``cache_info`` functions.\n [Valentin Lab]\n- Similar ``set`` could get different hash. [Valentin Lab]\n\n ``set`` weren't sorted prior to introspection for hashing.\n\n\n0.0.1 (2014-05-23)\n------------------\n- First import. [Valentin Lab]\n\n\n\n",
"bugtrack_url": null,
"license": "",
"summary": "Kids caching library.",
"version": "0.0.7",
"project_urls": {
"Homepage": "http://github.com/0k/kids.cache"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "86d222a34ea6a4181a764ebad7097e5b25264436eeead4030b243b5ccfd95cfe",
"md5": "124d31ea2402ba1b33e733b22e4120d8",
"sha256": "685b30a8aea1d225d0490f73a54955213b36d794e8d332a93c1db3e46e1ef11d"
},
"downloads": -1,
"filename": "kids.cache-0.0.7-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "124d31ea2402ba1b33e733b22e4120d8",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 18039,
"upload_time": "2017-11-16T14:40:31",
"upload_time_iso_8601": "2017-11-16T14:40:31.544537Z",
"url": "https://files.pythonhosted.org/packages/86/d2/22a34ea6a4181a764ebad7097e5b25264436eeead4030b243b5ccfd95cfe/kids.cache-0.0.7-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "36fe12a3ac5da6f3b5b70da5e59db908b24d0fa1797954a3210bb7146020153c",
"md5": "e8c79b089133e2ddb95ee78c4b0a5028",
"sha256": "6630bead0d43ef700a8eb4729fdc09e8fbfeba7211a2563f4bd67e5659f97853"
},
"downloads": -1,
"filename": "kids.cache-0.0.7.tar.gz",
"has_sig": false,
"md5_digest": "e8c79b089133e2ddb95ee78c4b0a5028",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 20105,
"upload_time": "2017-11-16T14:40:35",
"upload_time_iso_8601": "2017-11-16T14:40:35.123852Z",
"url": "https://files.pythonhosted.org/packages/36/fe/12a3ac5da6f3b5b70da5e59db908b24d0fa1797954a3210bb7146020153c/kids.cache-0.0.7.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2017-11-16 14:40:35",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "0k",
"github_project": "kids.cache",
"travis_ci": true,
"coveralls": false,
"github_actions": false,
"appveyor": true,
"lcname": "kids.cache"
}