简体   繁体   中英

Is there a way to compute something only the first time you call a function without creating a global in python3?

Currently I use the following technique:

def Myfb(param1, param2, firstTime):
    if firstTime:
        global a = compute()
        global b = compute2()
    global a
    global b
    c = doNormalExecution(param1, param2, a, b)

Is there a more elegant way? I don't like creating globals

The technique is called memoization. The functools module has an lru_cache function that does what you want.

from functools import lru_cache

@lru_cache(maxsize=None)
def Myfb(param1, param2):
    b = doNormalExecution(a)

The python docs have more information like what maxsize is and how lru_cache works so that you can implement it suitably.

You can use a generator:

def Myfb():
    a = compute()
    while True:
      param1, param2 = yield
      b = doNormalExecution(a, param1, param2)
      yield b

Here you have a live example

Example code:

def compute():
  return 10

def doNormalExecution(a, b, c):
  return a + b + c

def Myfb():
    a = compute()
    while True:
      param1, param2 = yield
      b = doNormalExecution(a, param1, param2)
      yield b

f = Myfb()
next(f)
for a, b in zip(range(10), range(10)):
  print(f.send((a, b)))
  next(f)

You can create a custom callable that will maintain it's own state:

class MyFB(object):
    _sentinel = object()

    def __init__(self):
        self._a = self._sentinel
        self._b = self._sentinel


    def __call__(self, param1, param2, reinit=False):
        if reinit or self._a is self._sentinel or self._b is self._sentinel:
            self._a = compute_a()
            self._b = compute_b()
        return doNormalExecution(param1, param2, self._a, self._b)


myfb = MyFB()

# now use `myfb` like an ordinary function

Seeing as compute1() and compute2() don't take arguments, you could use functools to cache their results. (Unless they have side effects.)

from functools import cache

@cache
def compute():
    #do complicated stuff first time called
    return result

@cache
def compute2():
    #do complicated stuff first time called
    return result

def Myfb(param1, param2):
    a = compute()
    b = compute2()
    c = doNormalExecution(param1, param2, a, b)

If you don't pass any parameters to a function, use this decorator (I had it lying around):

import functools

def lazy(func):
    """ Decorator which only actually runs a function the first time it is
    called and stores the result to be returned on later calls.

    Usage:
        @lazy
        def func_to_be_only_run_once():
            ...
    """
    FLAG = object()
    result = FLAG
    @functools.wraps(func)
    def inner():
        nonlocal result
        if result is FLAG:
            result = func()
        return result
    return inner

If you have one or more arguments that change (including self ) use functools.lru_cache

Here's a cool way to do it using closures.

def closure(firstTime=True):
  def Myfb():
    nonlocal firstTime
    if firstTime:
        print("First time.")
        firstTime = False
  return Myfb

myfb = closure()
myfb()
myfb()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM