1

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

Fractale
  • 1,503
  • 3
  • 19
  • 34
  • You could hack the mutable default argument or something, but I feel this would best to do *outside* the function – Chris_Rands Nov 05 '18 at 09:34
  • You never use `param1` and `param2` in your function. Does it actually take parameters? If so, what happens if I pass new paramaters the second time? – FHTMitchell Nov 05 '18 at 09:34
  • `b = doNormalExecution(a)` will raise name error in later executions then. Please add your real function and besides tell us what's the connection between global variable and first-time using the function. – Mazdak Nov 05 '18 at 09:34
  • There are a couple ways of doing this I guess. You could use a closure, or you can add a property on the function object. – Silver Nov 05 '18 at 09:35
  • @Chris_Rands `a` and `b` are use only in that function so I feel doing it outside will add complexity, don't you think? – Fractale Nov 05 '18 at 09:44
  • @FHTMitchell `param1` and `param2` are use after in the function, I edit the question to make it clear – Fractale Nov 05 '18 at 09:45
  • @SilverSlash I understand closure but I didn't really yet use them in practive. I should return the function doNormalExecution witch is in another function witch countain a and b. does I'm right? so the fist time I call a "closure init" then after just the function return. does I'm right? – Fractale Nov 05 '18 at 09:50
  • @Fractale Yeah just check out the solution below I show how you can call your function. – Silver Nov 05 '18 at 09:52

6 Answers6

2

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.

Ananth
  • 848
  • 11
  • 26
  • I don't know what you are talking about. Please read the documentation thoroughly. It most certainly supports an arbitrary number of parameters. The only condition it lays on parameters is that they be hashable as they are cached in a dict. – Ananth Nov 05 '18 at 09:41
  • This answer is wrong, or at least doesn't address the problem correctly. Where are you computing those global variables? And what does `lru_cache` has to do with that? – Mazdak Nov 05 '18 at 09:46
  • It doesn't address the question fully because it was edited after I had my answer down. The question is completely different now. – Ananth Nov 05 '18 at 09:47
0

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)
Netwave
  • 40,134
  • 6
  • 50
  • 93
0

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
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
0

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)
-2

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

FHTMitchell
  • 11,793
  • 2
  • 35
  • 47
-2

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()
Silver
  • 1,327
  • 12
  • 24
  • 2
    I didn't even read your answer when I wrote mine. Also I don't know how it's literally a copy of yours since you're using `functools` and unnecessary convoluted code while mine is a clean solution. – Silver Nov 05 '18 at 09:41
  • 1
    Maybe read my answer more carefully before claiming it's a copy of yours? – Silver Nov 05 '18 at 09:43
  • Haven't you got `True` and `False` backwards with regards to the `firstTime` parameter? This code as it stands will not print anything the first time, then `First time.` after that. Other than this though, I like this solution and don't understand the criticism. – Robin Zigmond Nov 05 '18 at 09:46