Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Writing Stateful Callables With .__call__()

00:00 By default, functions in Python are stateless, meaning that multiple calls of the same function can’t affect each other. This is usually the desired behavior, but there are times when you might want to have a function that remembers the results of previous calls.

00:13 This would be a kind of stateful callable. There is a way to do this purely with functions, taking advantage of something called a closure, but you can also use callable instances to make classes whose instances have this function-like behavior.

00:28 The use cases of stateful callables include keeping a count. So like you’ve already seen, you can create a counter that maintains an internal state: that’s statefulness.

00:38 Another popular use would be caching computed values. This is a common optimization trick, also called memoization. If a function is particularly expensive to run and tends to be run repeatedly with the same arguments, it’s great to create a callable that has an internal state, a cache, in this case, of results.

00:56 And when called, will return a cached value if it exists to prevent redundant computation. Yet another use of stateful callables is tracking a running average.

01:07 For instance, creating a callable that takes a series of values from a stream of data and computes the cumulative average, maintaining the values between calls as internal state.

01:17 Actually, that one sounds like fun. Fire up your code editor and get ready to implement it. Here in a Python file named cumulative_average.py, create a class called CumulativeAverager.

01:29 Class CumulativeAverager

01:33 def __init__(self ): Set self.data to an empty list.

01:40 And define a .__call__() that takes self and new_value as arguments: def __call__(self, new_value

01:49 ): And the functionality is straightforward. Add the new_value to the list of values.

01:54 self.data.append(new_value) and return the arithmetic mean: the sum of the values in the list divided by its length. return sum(self.data) / len( self.data). Save the file and open up the REPL.

02:15 From cumulative_average import CumulativeAverager. And remember, the functionality for this lies in the instances of CumulativeAverager.

02:27 So first, create an instance.

02:29 stream_average is the result of calling CumulativeAverager().

02:35 And start adding values: stream_average(12) returns 12, since that’s the only item in the list. stream_average(13) returns 12.5, which is the average of 12 and 13.

02:50 stream_average(11) now returns 12, the average of 11, 12, and 13, and try stream_average(20), which returns 14, and I have to believe that’s the average of 12, 13, 11, and 20.

03:06 Feel free to go ahead and play around with it some more. And once you’re done, since this is still a class instance, not a regular function, you can actually examine the list of values via its .data attribute. stream_ average.data contains the list 12, 13, 11, and 20.

03:24 And in this way, you can get the best of two worlds: stateful functions and object-oriented design, all in a straightforward and readable package.

03:34 And speaking of object-oriented design, next lesson, you’ll use .__call__()to implement a classic design pattern, the strategy pattern.

Become a Member to join the conversation.