这是一个关于Python中scope和闭包的问题,其动机是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
是留给读者的练习,但相当琐碎。