2

I am writing a simple decorator class in python that counts function calls. So far my code is able to count function calls correctly, even in a with block. My issue is that I also want to keep track of how many times I call decorated functions inside of a context manager (to the best of my understanding).

Here is how the class can be 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 

And here is the expected output using my class and the above code:

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

Here is my code, which does everything correctly except for the 'with block count' statements:

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

So what am I doing wrong? Can you guys help out or at least point in me in the right direction so I can understand with blocks enough to get this working? I have tried a handful of things including static properties but nothing seems to work. I am relatively new to python so the nuances escape me.

Edit- this is the output from the current program.

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
Illusionist
  • 5,204
  • 11
  • 46
  • 76
  • What output do you get ? – Illusionist Mar 10 '15 at 01:38
  • I get everything in the second code snippet (the 'expected output' section) but my block count is always 1. This leads me to believe that I am not handling the counter the way I should be when in a with block. – colloquialism Mar 10 '15 at 01:43
  • Thanks for the suggested edit. That is more clear. – colloquialism Mar 10 '15 at 01:52
  • Do you understand why you get those results? – wwii Mar 10 '15 at 02:02
  • its being passed by value @wwii, is that right? everytime the count is 1 because of passing by value – Illusionist Mar 10 '15 at 02:05
  • @wwii My guess is because I only increment the counter when I enter in the with block, so when I print the value when I exit I have only incremented for as many 'with' blocks in which I entered. However, I am not sure how to apply the counter for each function in the with block. – colloquialism Mar 10 '15 at 02:10
  • Python doesn't really pass *by value* or *by reference* - a search for "Python pass by reference" should provide something instructive like this SO Q&A - [How do I pass a variable by reference?](http://stackoverflow.com/q/986006/2823755) – wwii Mar 10 '15 at 02:25
  • What problem are you trying to solve by creating a class that counts function calls in and differentiates calls made in a managed context? - You may want to add that explanation to your question. – wwii Mar 10 '15 at 17:56
  • No problem specifically, this is just as an exercise to broaden my understanding of python. This should be a simple problem to solve but I still cannot see a clear solution. Thanks for the input. – colloquialism Mar 11 '15 at 15:11

1 Answers1

2

Each with statement creates a new instance of fcount2 so each instance only has one block_count - I don't have an answer but some additions to your code will illustrate what is happening.

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

The solution to your problem may be to have a class attribute that keeps track of all the instances of fcount2, similar to the example in the PythonDecoratorLibrary


I played around a bit and came up with a solution, although I'm not sure it is what you are looking for, and it may not be the correct solution but it works for the scope of your examples.

The class adds attributes to the function it decorates, calls are accumulated in the function attributes, logic differentiates calls within a managed context, and instance properties refer to the function attributes.

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

Usage:

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

Accessing the counts while in a managed context:

>>> 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
>>>
wwii
  • 23,232
  • 7
  • 37
  • 77
  • This is helpful, definitely allows me to see what is going on. Let me play with this and see if it helps. – colloquialism Mar 10 '15 at 02:24
  • Maybe add the *count* attributes to the functions themselves, ```.inner_func``` in this case. Or maybe use a descriptor [Python Descriptors Demystified](http://nbviewer.ipython.org/urls/gist.github.com/ChrisBeaumont/5758381/raw/descriptor_writeup.ipynb) – wwii Mar 10 '15 at 02:39
  • Reviewing your proposed solution right now. Sorry for the late reply. – colloquialism Mar 11 '15 at 15:11
  • What version of python are you using? I am in 2.7.9 and it appears that I cannot set attributes on self.inner_func, is there a reason why? – colloquialism Mar 11 '15 at 19:14
  • Follow up: The program dies when I try to access the block_count attribute on the self.inner_func property when it is within the context manager. Debugging now. – colloquialism Mar 11 '15 at 19:21
  • @colloquialism - I am using 2.7.5. Added a usage example within a managed context. – wwii Mar 11 '15 at 22:50