6

Is there an idiomatic way to implement an "once method", that is: a method whose return value gets evaluated on the first call only? Something like what the following simplified code does for the return value of x:

class X: def __init__(self): self._x = None def x(self): if not self._x: self._x = some_expensive_call() return self._x 

4 Answers 4

12

As of Python 3.2, you can use the functools.lru_cache decorator (but it may be overkill for what you need):

import functools @functools.lru_cache(maxsize=1) def once(): print("calculating expensive result") return "expensive result" once() once() 

output:

calculating expensive result # <- only prints on first call 'expensive result' # returned value on first call 'expensive result' # <- just return value on second call 

Alternatively, you can write your own decorator:

def cache_result(func): def wrapper(*args, **kwds): if not wrapper.cached: wrapper.value = func(*args, **kwds) wrapper.cached = True return wrapper.value wrapper.cached = False return functools.update_wrapper(wrapper, func) 

And use it on any function you want to run only once and cache the result

@cache_result def do_once(): print('doing it once') return 'expensive result' 
Sign up to request clarification or add additional context in comments.

2 Comments

Besides functions, does functools.lru_cache work for methods, too?
A downside of this approach is that, for methods, self needs to be hashable (whereas the implementation in the question above doesn't), and this isn't always wanted/possible.
8

One way is to give the function itself an attribute which will only be True on the first call.

>>> def once(): ... if once.first_call: ... print('doing some heavy computation over here...') ... once.result = 1 + 1 ... once.first_call = False ... return once.result ... >>> once.first_call = True >>> once() doing some heavy computation over here... 2 >>> once() 2 

Another option is to (ab)use a mutable default parameter. The advantage is that you don't have to set an attribute on the function after defining it:

>>> def once(state={'first_call':True}): ... if state['first_call']: ... print('doing some heavy computation over here...') ... state['result'] = 1 + 1 ... state['first_call'] = False ... return state['result'] ... >>> once() doing some heavy computation over here... 2 >>> once() 2 

edit:

For completeness, if you have instance attributes that should only be computed once, use a property:

class Foo(object): def __init__(self): self._x = None @property def x(self): if self._x is None: self._x = self._compute_x() return self._x def _compute_x(self): print('doing some heavy computation over here...') return 1 + 1 

Demo:

>>> f = Foo() >>> f.x doing some heavy computation over here... 2 >>> f.x 2 

2 Comments

Besides functions, does this solution work for methods, too?
@Elena sure, but inside a class you could and probably should use a property decorator to achieve this result.
1

Since you tagged this as Python 3.X, you can also use a function annotation as a state flag:

>>> def f()->{"state":False}: ... if f.__annotations__['return']['state']==False: ... f.__annotations__['return']['state']=True ... >>> f.__annotations__ {'return': {'state': False}} >>> f() >>> f.__annotations__ {'return': {'state': True}} 

For a class, timgeb method works great.

Comments

1

The old-school method is to just have a global that holds the result.

_foo_result = None def foo(): global _foo_result if _foo_result is None: _foo_result = _get_me_some_foo() return _foo_result 

If None is a valid result, define an empty class and use that instead

class _FooNone: pass 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.