2014-01-08 105 views
2

我想計算給定函數被調用的次數。訪問在裝飾器之外的裝飾器中創建的函數屬性

所以,我做了一個countcalls修飾器給我的功能__callcount屬性,每增加一個電話。夠簡單。

我的問題是得到__callcount值後面退出。

這裏是我的代碼:

import functools 

def countcalls(f): 
    f.__callcount = 0 

    @functools.wraps(f) 
    def _countcalls(*args, **kwds): 
     f.__callcount += 1 
     print(' Called {0} time(s).'.format(f.__callcount)) 
     return f(*args, **kwds) 
    return _countcalls 

@countcalls 
def fib(n): 
    if n < 0: 
     raise ValueError('n must be > 0') 
    if n == 0 or n == 1: 
     return 1 

    return fib(n-1) + fib(n-2) 

if __name__ == '__main__': 
    print('Calling fib(3)...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 

    print('Calling fib(3) again...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 

    print('fib was called a total of {0} time(s).'.format(fib.__callcount)) 

產生以下輸出(Python的3.3.0):

Calling fib(3)... 
    Called 1 time(s). 
    Called 2 time(s). 
    Called 3 time(s). 
    Called 4 time(s). 
    Called 5 time(s). 
fib(3) = 3 
Calling fib(3) again... 
    Called 6 time(s). 
    Called 7 time(s). 
    Called 8 time(s). 
    Called 9 time(s). 
    Called 10 time(s). 
fib(3) = 3 
fib was called a total of 0 time(s). 

爲什麼在最後一行fib.__callcount等於0?如輸出所示,__callcount得到遞增,並在fib的調用之間持續存在。

我錯過了什麼?

+0

我認爲這是一個範圍/上下文問題。 http://stackoverflow.com/a/1753475/2823755可能會解釋此行爲。 – wwii

+0

好找。我只是嘗試使用'setattr(f,'__callcount',0)'和'getattr(f,'__callcount')',但它們表現出與原始行爲完全相同的行爲。 'f .__ dict __ ['__ callcount']'也是同樣的行爲。 – Keeler

回答

0

好吧,這是原因,在一些幫助後。多謝你們!

問題是函數是不可變的。例如

>>> def f(func): 
...  return func() 
... 
>>> def g(): 
...  return 'sunflower seeds' 
... 
>>> id(g) 
139636515497336 
>>> g = f(g) 
>>> id(g) 
139636515515112 

因此,要獲得功能f的唯一途徑,我們分配__callcount屬性中的countcalls的定義是從callcount返回功能。但是我們已經返回了內部函數_countcalls。我們可以返回f_countcalls,但是這會弄亂@countcalls裝飾器語法。

你仍然可以這樣做,它只是不那麼漂亮。

import functools 

def countcalls(f): 
    f.__callcount = 0 

    @functools.wraps(f) 
    def _countcalls(*args, **kwds): 
     f.__callcount += 1 
     print(' Called {0} time(s).'.format(f.__callcount)) 
     return f(*args, **kwds) 
    return f, _countcalls 

def fib(n): 
    if n < 0: 
     raise ValueError('n must be > 0') 
    if n == 0 or n == 1: 
     return 1 

    return fib(n-1) + fib(n-2) 

if __name__ == '__main__': 
    counter, fib = countcalls(fib) 

    print('Calling fib(3)...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 

    print('Calling fib(3) again...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 

    print('fib was called a total of {0} time(s).'.format(counter.__callcount)) 

長話短說,只是使用class from the Python Decorator Library。 :D

0
f.__callcount = [0] 

.......... 


f.__callcount[0] = f.__callcount[0] + 1 

...... 


print('fib was called a total of {0} time(s).'.format(fib.__callcount[0])) 

它的工作原理。
也許它的東西更pythonic

+0

這絕對有效,它基本上就是我想要的。但這很奇怪......如果將'f .__ callcount = [0]'改爲'f .__ callcount = {0:0}',它也可以工作。爲什麼這與列表和字典一起工作,但不是一個整數?我要編輯我的問題來澄清。 – Keeler

+0

可變性可能與它有關 - 列表和字典是可變的。該列表是在函數定義(修飾)處創建的,並且類似於使用可變對象作爲函數參數的默認值,它將進入全局範圍。 – wwii

0

這就是你想要的。我知道了這裏 - https://wiki.python.org/moin/PythonDecoratorLibrary#Alternate_Counting_function_calls

class countcalls(object): 
    "Decorator that keeps track of the number of times a function is called." 

    __instances = {} 

    def __init__(self, f): 
     self.__f = f 
     self.__numcalls = 0 
     countcalls.__instances[f] = self 
     self.__doc__ = f.func_doc 
     self.__name__ = f.func.func_name 

    def __call__(self, *args, **kwargs): 
     self.__numcalls += 1 
     return self.__f(*args, **kwargs) 

    def count(self): 
     "Return the number of times the function f was called." 
     return countcalls.__instances[self.__f].__numcalls 

    @staticmethod 
    def counts(): 
     "Return a dict of {function: # of calls} for all registered functions." 
     return dict([(f.__name__, countcalls.__instances[f].__numcalls) for f in countcalls.__instances]) 

@countcalls 
def fib(n): 
    if n < 0: 
     raise ValueError('n must be > 0') 
    if n == 0 or n == 1: 
     return 1 

    return fib(n-1) + fib(n-2) 

if __name__ == '__main__': 
    print('Calling fib(3)...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 
    print('fib was called a total of {0} time(s).'.format(fib.count())) 

    print('Calling fib(3) again...') 
    x = fib(3) 
    print('fib(3) = {0}'.format(x)) 

    print('fib was called a total of {0} time(s).'.format(fib.count())) 
+0

是的,我從這段確切的代碼中得到了靈感。 :) 我想使用functools.wraps和一個單一的屬性來保持簡單,當我試圖添加另一個裝飾器到相同的功能由於某種原因,countcalls裝飾器給我麻煩。 – Keeler

+0

在您的幫助下,我將此問題的解釋發佈爲[此答案](http://stackoverflow.com/a/20987591/3171324)。這讓我瘋狂,謝謝你的幫助。將你的標記標記爲答案,因爲無論如何我都鏈接到這個例子。 – Keeler

0

的函數對象要添加的屬性是不同的對象比「原始」功能。試試這個:

import functools 

def countcalls(f): 
    f.__callcount = 0 

    @functools.wraps(f) 
    def _countcalls(*args, **kwds): 
     f.__callcount += 1 
     print 'id(f):', id(f) 
     print(' Called {0} time(s).'.format(f.__callcount)) 
     return f(*args, **kwds) 
    return _countcalls 


@countcalls 
def fib(n): 
    """fibinacci""" 
    if n < 0: 
     raise ValueError('n must be > 0') 
    if n == 0 or n == 1: 
     return 1 

    return fib(n-1) + fib(n-2) 


if __name__ == '__main__': 
    print('Calling fib(3)...') 
    x = fib(3) 
    print 'id(fib):', id(fib) 

""" 
>>> 
Calling fib(3)... 
id(f): 45611952 
    Called 1 time(s). 
id(f): 45611952 
    Called 2 time(s). 
id(f): 45611952 
    Called 3 time(s). 
id(f): 45611952 
    Called 4 time(s). 
id(f): 45611952 
    Called 5 time(s). 
id(fib): 45612016 
>>> 
"""