这是关于 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的概念......
答案 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
是留给读者的练习,但非常简单。