Source code for PythonCK.decorators.lazy

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""

Collection of lazy (caching) decorators

"""

import functools
import types

## Local
from .. import logger

#===============================================================================
# MEMORIZED
#===============================================================================

[docs]def memorized(func): """ Memorize the computed result of this function until the end of session:: ## Define >>> @memorized ... def f_mem(arg1, arg2): ... return arg1+arg2 ## Check consistency >>> x1 = f_mem(10, arg2=20) >>> x2 = f_mem(10, arg2=20) >>> x1 == x2 True """ ## record the path to defined location first. # may not be the same as calling location defpath = func.__globals__['__file__'] @functools.wraps(func) def wrap(*args, **kwargs): """ Make args+kwargs into immutable key, save in globals """ rawkey = ['__memorized_', defpath, func.__name__] if args: rawkey.append(args) if kwargs: rawkey.append(frozenset(sorted(kwargs.items()))) key = str(rawkey) if key not in globals(): logger.debug('memorized: write new key: '+key, extra=logger.extra(func)) val = func(*args, **kwargs) globals()[key] = val return globals().get(key) ## Conflict with pytest, find another way # wrap.func_globals['__file__'] = func.func_globals['__file__'] return wrap
#=============================================================================== # LAZY-INSTANCE #===============================================================================
[docs]def lazy_instance(func): """ To be used on the function that return some instance. The function will delay its call until its resultant instance is used (via __getattr__). A prime example is PyrootCK.import_tree. Usage:: >>> IS_INIT = False >>> class Heavy(object): ... def __init__(self): ... global IS_INIT ... IS_INIT = True ... def foo(self): ... return 42 >>> @lazy_instance ... def get_heavy(): ... return Heavy() >>> x = get_heavy() >>> IS_INIT False >>> x <PythonCK.decorators.lazy...> >>> x.foo() 42 >>> IS_INIT True ## Triggered by setter >>> IS_INIT = False >>> y = get_heavy() >>> IS_INIT False >>> y.attr = 'val' >>> IS_INIT True >>> y.attr 'val' """ # @functools.wraps(func) class Wrap(object): """ Actual wrap logic """ __slots__ = 'obj', 'args', 'kwargs' def __init__(self, *args, **kwargs): self.obj = None self.args = args self.kwargs = kwargs def __getattr__(self, key): if self.obj is None: self.obj = func(*self.args, **self.kwargs) return getattr(self.obj, key) def __setattr__(self, key, val): if key in self.__slots__: super(Wrap, self).__setattr__(key, val) else: if self.obj is None: self.obj = func(*self.args, **self.kwargs) return setattr(self.obj, key, val) return Wrap
#=============================================================================== # LAZY PROPERTY #===============================================================================
[docs]def lazy_property(func): """ Lazy function, only for *getter* instance-method! Usage: >>> class Foo(object): ... slow_count = 0 ... @lazy_property ... def slow(self): ... self.slow_count += 1 ... return 'some slow result' >>> foo = Foo() >>> foo.slow_count 0 >>> foo.slow 'some slow result' >>> foo.slow_count # increase 1 >>> foo.slow 'some slow result' >>> foo.slow_count # not increase anymore! 1 """ attr_name = '_lazy_' + func.__name__ @property @functools.wraps(func) def _lazyprop(self): if not hasattr(self, attr_name): val = func(self) assert not isinstance(val, types.GeneratorType), "Don't use this with iterator!!" setattr(self, attr_name, val) return getattr(self, attr_name) return _lazyprop