跟踪 Python 中函数调用 + 闭包的数量 (à la SICP)



这是一个关于Pythonscope闭包的问题,其动机是SICP中的一个练习。如果你读到这篇文章,非常感谢你抽出时间!

SICP中的一个问题(3.2)要求创建一个"使被监控"的过程,该过程接受(一个参数的)函数f作为输入,并返回一个跟踪f被调用次数的过程。(如果这个新过程的输入是"num calls",它将返回f被调用的次数,如果它是"reset",它会将计数器重置为0和其他任何东西,它将f应用于输入并返回结果(在适当地增加计数器之后)。

这是我写的Scheme中的代码,它有效:

(define (make-monitored f)
  (let ((counter 0))
     (define (number-calls) counter)
     (define (reset-count)
       (set! counter 0))
     (define (call-f input)
       (begin (set! counter (+ 1 counter))
              (f input)))
    (define (dispatch message)
      (cond ((eq? message 'num-calls) (number-calls))
            ((eq? message 'reset) (reset-count))
            (else (call-f message))))
    dispatch))

然而,我的问题是关于如何以"蟒蛇"的方式写这篇文章。我在下面的尝试显然是对Scheme代码的直接翻译,我意识到尽管这对于一种不纯净的函数语言(如Scheme)来说是可以的,但在Python中可能不是最干净或最好的方法。在Python中,如何解决像这样的一般问题?在Python中需要一个更高阶的过程来调度类型并记住本地状态?

下面是我的一个很好的尝试(早些时候我说过没有,但问题是程序的早期版本仍然在终端的内存中)(在2中,似乎很难进行非局部变量绑定)

def make_monitored(func):
    counter = 0                      
    def dispatch(message):
        if message == "num-calls":
            return num_calls()
        elif message == "reset":
            reset()
        else:
            nonlocal counter
            counter += 1
            return func(message)
    def num_calls():
        nonlocal counter
        return counter
    def reset():
        nonlocal counter
        counter = 0
    return dispatch

PS:这个问题与SICP中的同一组练习有关,但我的问题实际上是关于Python的最佳实践,而不是闭包或Scheme的概念。。。

我认为编写一个装饰器将函数包装在类中会更像蟒蛇:

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

这样做的优点是,它尽可能接近原始函数,只需添加一个counter字段:

In [25]: msqrt = make_monitored(math.sqrt)
In [26]: msqrt(2)
Out[26]: 1.4142135623730951
In [29]: msqrt.counter
Out[29]: 1
In [30]: msqrt(235)
Out[30]: 15.329709716755891
In [31]: msqrt.counter
Out[31]: 2
In [32]: @make_monitored
    ...: def f(a):
    ...:     """Adding the answer"""
    ...:     return a + 42
In [33]: f(0)
Out[33]: 42
In [34]: f(1)
Out[34]: 43
In [35]: f.counter
Out[35]: 2
In [36]: f.__name__
Out[36]: 'f'
In [37]: f.__doc__
Out[37]: 'Adding the answer'

对于f,您还可以看到作为装饰器的用法,以及包装器如何保留原始名称和文档字符串(如果没有functools.wraps,情况就不会如此)。

定义reset是留给读者的练习,但相当琐碎。

相关内容

最新更新