Python 装饰器 Class:如何正确计算 with 块中的函数调用

Python Decorator Class: How to correctly count function calls in a with block

我正在 python 中编写一个简单的装饰器 class 来计算函数调用。到目前为止,我的代码能够正确计算函数调用,即使在 with 块中也是如此。我的问题是我还想跟踪我在上下文管理器中调用修饰函数的次数(据我所知)。

这里是 class 可以是 used/tested:

@fcount2
def f(n):
    return n+2

for n in range(5):
    print f(n)   
print 'f count =',f.count

def foo(n):
    return n*n

with fcount2(foo) as g:
    print g(1)
    print g(2)
print 'g count =',g.count
print 'f count =',f.count

with fcount2(f) as g:
    print g(1)
    print g(2)
print 'g count =',g.count
print 'f count =',f.count

with f:
    print f(1)
    print g(2)
print 'g count =',g.count
print 'f count =',f.count 

这里是使用我的 class 和上面的代码的预期输出:

2
3
4
5
6
f count = 5
1
4
with block count = 2
g count = 2
f count = 5
3
4
with block count = 2
g count = 2
f count = 7
3
4
with block count = 2
g count = 3
f count = 9

这是我的代码,除了 'with block count' 语句外,它可以正确执行所有操作:

class fcount2(object):
    def __init__(self, inner_func):
        self.inner_func = inner_func
        self.count = 0
        self.block_count =0
    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.inner_func(*args, **kwargs)
    def __enter__(self):
        self.block_count += 1
        return self
    def __exit__(self, exception_type, exception_value, tb):
        print "with block count: " + str(self.block_count)
        if exception_type is not None:
            return False
        return self

那我做错了什么?你们能帮帮我吗,或者至少给我指明正确的方向,这样我就可以充分理解 with 块来使它正常工作?我尝试了一些东西,包括静态属性,但似乎没有任何效果。我是 python 的新手,所以我无法理解其中的细微差别。

编辑 - 这是当前程序的输出。

2
3
4
5
6
f count = 5
1
4
with block count: 1
g count = 2
f count = 5
3
4
with block count: 1
g count = 2
f count = 7
3
4
with block count: 1
g count = 3
f count = 9

每个 with 语句都会创建一个 fcount2 的新实例,因此每个实例只有一个 block_count - 我没有答案,但对您的代码添加一些内容可以说明正在发生的事情。

class fcount2(object):
    def __init__(self, inner_func):
        self.inner_func = inner_func
        self.count = 0
        self.block_count =0
    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.inner_func(*args, **kwargs)
    def __enter__(self):
        print 'with block entered - id(self):', id(self)
        self.block_count += 1
        return self
    def __exit__(self, exception_type, exception_value, tb):
        print "with block exit - block count: " + str(self.block_count)
        if exception_type is not None:
            return False
        return self

sep = '*************************\n'
@fcount2
def f(n):
    return n+2

for n in range(5):
    print f(n)   
print 'f count =',f.count, ' | id(f):', id(f)

def foo(n):
    return n*n

print sep
with fcount2(foo) as g:
    print g(1), ' | id(g):', id(g)
    print g(2), ' | id(g):', id(g)
print 'g count =',g.count, ' | id(g):', id(g)
print 'f count =',f.count, ' | id(f):', id(f)

print sep
with fcount2(f) as g:
    print g(1), ' | id(g):', id(g)
    print g(2), ' | id(g):', id(g)
print 'g count =',g.count, ' | id(g):', id(g)
print 'f count =',f.count, ' | id(f):', id(f)

print sep
with f:
    print f(1), ' | id(f):', id(f)
    print g(2), ' | id(g):', id(g)
print 'g count =',g.count, ' | id(g):', id(g)
print 'f count =',f.count, ' | id(f):', id(f)

>>> 
2
3
4
5
6
f count = 5  | id(f): 66567888
*************************

with block entered - id(self): 66585136
1  | id(g): 66585136
4  | id(g): 66585136
with block exit - block count: 1
g count = 2  | id(g): 66585136
f count = 5  | id(f): 66567888
*************************

with block entered - id(self): 66587152
3  | id(g): 66587152
4  | id(g): 66587152
with block exit - block count: 1
g count = 2  | id(g): 66587152
f count = 7  | id(f): 66567888
*************************

with block entered - id(self): 66567888
3  | id(f): 66567888
4  | id(g): 66587152
with block exit - block count: 1
g count = 3  | id(g): 66587152
f count = 9  | id(f): 66567888
>>> 

您的问题的解决方案可能是有一个 class 属性来跟踪 fcount2 的所有实例,类似于 PythonDecoratorLibrary[=18= 中的示例]


我试了一下并想出了一个解决方案,虽然我不确定它是否是您正在寻找的,它可能不是正确的解决方案,但它适用于您的示例范围。

class为其修饰的函数添加属性,调用在函数属性中累积,逻辑区分托管上下文内的调用,实例属性参考函数属性。

class fcount2(object):
    def __init__(self, inner_func):
        self.inner_func = inner_func
        if not hasattr(self.inner_func, 'count'):
            self.inner_func.count = 0
        if not hasattr(self.inner_func, 'block_count'):
            self.inner_func.block_count = 0
        self.context_manager = False
    def __call__(self, *args, **kwargs):
        if self.context_manager:
            self.inner_func.block_count += 1
        else:
            self.inner_func.count += 1
        return self.inner_func(*args, **kwargs)
    def __enter__(self):
        self.context_manager = True
        return self
    def __exit__(self, exception_type, exception_value, tb):
        if exception_type is not None:
            return False
        self.context_manager = False
        return self
    @property
    def count(self):
        return self.inner_func.count
    @property
    def block_count(self):
        return self.inner_func.block_count

用法:

@fcount2
def f(n):
    return n+2

for n in range(5):
    print f(n),
print 'f.count =',f.count

@fcount2
def foo(n):
    return n*n

print sep, 'with foo as g: ...'
with foo as g:
    print g(1), g(2)
print 'foo.count =',foo.count, ' | foo.block_count:', foo.block_count
print 'f.count =',f.count, ' | f.block_count:', f.block_count

print sep, 'with f as g: ...'
with f as g:
    print g(1), g(2)
print 'foo.count =',foo.count, ' | foo.block_count:', foo.block_count
print 'f.count =',f.count, ' | f.block_count:', f.block_count


>>> 
2 3 4 5 6 f.count = 5
*************************
with foo as g: ...
1 4
foo.count = 0  | foo.block_count: 2
f.count = 5  | f.block_count: 0
*************************
with f as g: ...
3 4
foo.count = 0  | foo.block_count: 2
f.count = 5  | f.block_count: 2
>>> 

在托管上下文中访问 计数

>>> with foo as g:
        for n in [1,2,3,4,5]:
            print 'g(n): {} | g.block_count: {} | foo.block_count: {}'.format(g(n), g.block_count, foo.block_count)


g(n): 1 | g.block_count: 3 | foo.block_count: 3
g(n): 4 | g.block_count: 4 | foo.block_count: 4
g(n): 9 | g.block_count: 5 | foo.block_count: 5
g(n): 16 | g.block_count: 6 | foo.block_count: 6
g(n): 25 | g.block_count: 7 | foo.block_count: 7
>>>