跟踪Python

时间:2016-05-12 20:13:38

标签: python functional-programming scheme closures

这是关于 Python 中的范围关闭的问题,由SICP中的练习激发。非常感谢你的时间,如果你读到这个!

SICP中的一个问题(3.2)要求一个人创建一个程序“make-monitored”,它接受一个函数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))
然而,我的问题是如何以“pythonic”的方式写这个。我下面的尝试显然是我的Scheme代码的直接翻译,我意识到虽然它对于不纯的函数式语言(如Scheme)来说很好,但它可能不是用Python做的最干净或最好的方法。如何在Python中解决像这样的一般问题,你需要一个更高阶的程序来调度类型并记住本地状态?

下面是我的noobish尝试有效(早些时候我说它没有,但问题是该程序的早期版本仍然在终端的内存中)(在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:这个问题与this same set of exercises in SICP有关,但我的问题是关于Python最佳实践,而不是闭包或Scheme的概念......

1 个答案:

答案 0 :(得分:2)

我认为在类中编写一个包装函数的装饰器会更加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))

这样做的好处是它尽可能地模仿原始函数,只需向其添加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是留给读者的练习,但非常简单。