Python decorator leaving NoneType error

2019-03-04 06:59发布

I have made two functions doing the same thing but differently. I want to compare the time each function takes to run so I have added a decorator @calcul_time. The script does work but i get the following error message:

{1: 1, 2: 3, 3: 2, 4: 4, 5: 4, 6: 3, 7: 3, 8: 2, 9: 2, 10: 2, 11: 2, 12: 2, 13: 2, 14: 2, 15: 2, 16: 2, 17: 2, 18: 2, 19: 2, 20: 2, 21: 2, 22: 2, 23: 0, 24: 0}
0.0021219253540039062
{1: 1, 2: 3, 3: 2, 4: 4, 5: 4, 6: 3, 7: 3, 8: 2, 9: 2, 10: 2, 11: 2, 12: 2, 13: 2, 14: 2, 15: 2, 16: 2, 17: 2, 18: 2, 19: 2, 20: 2, 21: 2, 22: 2, 23: 0, 24: 0}
8.702278137207031e-05
Traceback (most recent call last):
  File "./03_PeakHours.py", line 51, in <module>
    horaires1()
TypeError: 'NoneType' object is not callable

I don't understand why I have this NoneType error message. If I comment my decorator, I don't have any errors. Hereunder my script. Does anyone know why I get this 'NoneType' error?

#!/usr/local/bin/python3.5

import time

input='''5
1 8
2 3
4 23
4 6
2 23'''


def calcul_time(fonction):
    avant = time.time()
    fonction()
    apres = time.time()
    print(apres - avant)    


#@calcul_time
def horaires1():
    hours = {}
    for time in range(1,25):
        hours[time] = 0

    def inBetween(line):
        current = int(line.split(" ")[0])
        while current < int(line.split(" ")[1]):
            hours[current] +=1
            current += 1
    list(map(inBetween, input.split("\n")[1:]))
    print(hours)
    return 0


#@calcul_time
def horaires2():
    lines = input.split("\n")
    hours={}
    for time in range(1,25):
        hours[time] = 0

    for i in range(1, int(lines[0])+1):
        start, stop = lines[i].split(" ")
        for heure in range(int(start), int(stop)):
            hours[heure] += 1
    print(hours)
    return 0


horaires1()
horaires2()

1条回答
男人必须洒脱
2楼-- · 2019-03-04 07:17

You haven't really built a decorator. A decorator has to return a the original function or a suitable replacement.

Your decorator returns nothing:

def calcul_time(fonction):
    avant = time.time()
    fonction()
    apres = time.time()
    print(apres - avant)    

A decorator typically returns a wrapper function. You created the wrapper function, but not the decorator that returns it.

This would be a proper decorator:

def calcul_time(fonction):
    def wrapper():
        avant = time.time()
        fonction()
        apres = time.time()
        print(apres - avant)    
    return wrapper

I renamed your calcul_time wrapper to wrapper, removed the fonction argument (that would be passed to the decorator, you can rely on it as a closure), and returned the wrapper. Now the decorator returns a replacement.

You probably want to make it a little more generic, and pass through arbitrary arguments with *args and **kwargs, and handle both the return value (pass it through to the caller of wrapper()) and exceptions correctly.

You also want to use the @functools.wraps() decorator to copy things like the name and attributes from the wrapped function to the wrapper:

from functools import wraps

def calcul_time(fonction):
    @wraps(fonction)
    def wrapper(*args, **kwargs):
        avant = time.time()
        try:
            return fonction(*args, **kwargs)
        finally:
            apres = time.time()
            print(apres - avant)
    return wrapper

The try..finally ensures that print() is executed regardless of what happens in fonction.

查看更多
登录 后发表回答