This is a question about scope and closures in Python, motivated by an exercise in SICP. Much thanks for your time if you read this!
A question (3.2) in SICP asks one to create a procedure "make-monitored", that takes in a function f (of one parameter) as input and returns a procedure that keeps track of how many times f has been called. (If the input to this new procedure is "num-calls" it returns the number of times f has been called, if it is "reset" it resets counter to 0 and anything else, it applies f to the input and returns the result (after appropriately incrementing the counter).
Here is code in Scheme that I wrote that works:
(define (make-monitored f)
(let ((counter 0))
(define (number-calls) counter)
(set! counter 0))
(define (call-f input)
(begin (set! counter (+ 1 counter))
(define (dispatch message)
(cond ((eq? message 'num-calls) (number-calls))
((eq? message 'reset) (reset-count))
(else (call-f message))))
counter = 0
if message == "num-calls":
elif message == "reset":
counter += 1
counter = 0
Besides the question whether the above code works, I'll answer the question "How would one go about solving this type of problem [...] in Python??". I think writing a decorator wrapping the function in a class would be more pythonic:
from functools import wraps def make_monitored(func): class wrapper: def __init__(self, f): self.func = f self.counter = 0 def __call__(self, *args, **kwargs): self.counter += 1 return self.func(*args, **kwargs) return wraps(func)(wrapper(func))
This has the advantage that it mimics the original function as close as possible, and just adds a
counter field to it:
In : msqrt = make_monitored(math.sqrt) In : msqrt(2) Out: 1.4142135623730951 In : msqrt.counter Out: 1 In : msqrt(235) Out: 15.329709716755891 In : msqrt.counter Out: 2 In : @make_monitored ...: def f(a): ...: """Adding the answer""" ...: return a + 42 In : f(0) Out: 42 In : f(1) Out: 43 In : f.counter Out: 2 In : f.__name__ Out: 'f' In : f.__doc__ Out: 'Adding the answer'
f, you also see the usage as a decorator, and how the wrapper keeps the original name and docstring (which would not be the case without
reset is left as an exercise to the reader, but quite trivial.