5

Is there a way that a function can remember its previous output and use that value during the next call to the function? For instance, assume there is a function, runningTotal with a single argument x that returns x on the first call to runningTotal but x + prevOutput for every call after that. Is there a way to write such a function in python?

I am aware that this could be easily achieved by using a global variable in the function or by saving the previous value to a new variable, but I would like to avoid these solutions if possible. The reason I'm looking for an alternate solution is because this is one function in a program I'm working on with other people and I would like to avoid having to create more global variables than already established.

Robotex
  • 159
  • 1
  • 3
  • 4
    Yes: [generators](https://jeffknupp.com/blog/2013/04/07/improve-your-python-yield-and-generators-explained/) – roganjosh Jan 10 '20 at 01:15
  • 1
    Technically speaking, generators are more than simply remembering the previous output. They keep the whole function state; so you will have access to the values of all local variables on subsequent calls. – Selcuk Jan 10 '20 at 01:17
  • This is a very vague question, please be more specific, [guidance](https://stackoverflow.com/help/minimal-reproducible-example) – Kenan Jan 10 '20 at 01:17
  • 2
    You can use a function closures. See [Simulate static variables in python with closures](https://stackoverflow.com/q/8175323/4996248). – John Coleman Jan 10 '20 at 01:18
  • [This question](https://stackoverflow.com/q/279561/4996248) contains other ways (beyond closures) to simulate static variables (variables attached to a function which keep their state between function calls). – John Coleman Jan 10 '20 at 01:28
  • [`functools.lru_cache`](https://docs.python.org/3/library/functools.html#functools.lru_cache) might help. – martineau Jan 10 '20 at 03:12

2 Answers2

2

Yes, but to avoid too much hackery or GLOBAL variables we'll probably want to use a class. In python a class can be treated as function with a magic function (method) inside the class named __call__.

Your question might be better written: what's the best way to have a function in python that has internal state?

Let's say we have the runningTotal function defined using a global variable as:

TOTAL = 0
def runningTotal(inc):
    global TOTAL
    TOTAL += inc
    return TOTAL

Answer Ok so lets define a class that will behave the same way as the above function but without a global variable:


class StatefulFunction:
    running_total = 0

    def __call__(self, inc):
        self.running_total += inc
        return self.running_total


# create the stateful function variable
runningTotal = StatefulFunction()

# Use the stateful function
runningTotal(1)
# outputs: 1
runningTotal(5)
# outputs: 6

Another way to accomplish the same thing is with a Counter Dictionary

from collections import Counter
counter = Counter()
counter['runningTotal'] += 1
# in another part of the program
counter['runningTotal'] += 5

The output will be:

print(counter)
Counter({'runningTotal': 6})
Ben DeMott
  • 3,362
  • 1
  • 25
  • 35
2

Although there are ways of doing what you ask, it's not a good idea. As @JohnColeman pointed out, Simulate static variables in python with closures

But why not create a class?

class Accumulator:
    total = 0

    @classmethod
    def add(cls, x):
        cls.total += x
        return cls.total


print(Accumulator.add(1))
print(Accumulator.add(2))
print(Accumulator.add(3))

Result:

1
3
6

You can set up a generator to maintain state and send values to it as well, as suggested by @HeapOverflow:

def get_running_total():
    def _running_total():
        value = 0
        while True:
            value += yield value
    # get a generator instance
    generator = _running_total()
    # set it up to wait for input
    next(generator)
    # return the send method on the generator
    return generator.send


# you can get a generator that functions similar to the Accumulator method
running_total = get_running_total()
print(running_total(1))   # prints 1
print(running_total(2))   # prints 3
print(running_total(3))   # prints 6
Grismar
  • 27,561
  • 4
  • 31
  • 54
  • You're right - my remark is too sharp, although I think that solution is still a bit hacky, it certainly is closer to what OP asked. I'll update my answer. – Grismar Jan 10 '20 at 02:45
  • Actually made another update, you can encapsulate the setup in the function and I feel this is a cleaner solution (and reusable). – Grismar Jan 10 '20 at 03:30
  • I think I had such a version as well, but dismissed it as inferior to the more normal [this](https://repl.it/repls/BraveForkedMethods). I'd say I mostly wrote a generator solution to practice it (especially the sending) and because you said it doesn't work :-P – Kelly Bundy Jan 10 '20 at 03:39