
def timed(fn): def decorated(*x): start = time() result = fn(*x) print "Executing %s took %d ms" % (fn.__name__, (time()-start)*1000) return result return decorated @timed def cpuload(): load = psutil.cpu_percent() print "cpuload() returns %d" % load return load print "cpuload.__name__==" + cpuload.__name__ print "CPU load is %d%%" % cpuload() ( Source code entirely )Thecpuload .__ name __ == decorated cpuload () returns 16 Executing cpuload took 105 ms CPU load is 16%
@timed def cpuload(): ... unfolds in def cpuload(): ...; cpuload=timed(cpuload) def cpuload(): ...; cpuload=timed(cpuload) , so that as a result, the global name cpuload associated with the decorated function inside timed , closed to the original cpuload function through the variable fn . As a result, we see cpuload.__name__==decorated def repeat(times): """ times , """ def decorator(fn): def decorated2(*x): total = 0 for i in range(times): total += fn(*x) return total / times return decorated2 return decorator @repeat(5) def cpuload(): """ cpuload """ print "cpuload.__name__==" + cpuload.__name__ print "CPU load is %d%%" % cpuload() ( Source code entirely )The value of the expressioncpuload .__ name __ == decorated2 cpuload () returns 7 cpuload () returns 16 cpuload () returns 0 cpuload () returns 0 cpuload () returns 33 CPU load is 11%
repeat(5) is the decorator function, closed at times=5 . This value is used as a decorator; in fact, we have def cpuload(): ...; cpuload=repeat(5)(cpuload) def cpuload(): ...; cpuload=repeat(5)(cpuload)@timed @repeat(5) def cpuload(): - then we will getAnd if you change the order of decorators -cpuload .__ name __ == decorated cpuload () returns 28 cpuload () returns 16 cpuload () returns 0 cpuload () returns 0 cpuload () returns 0 Executing decorated2 took 503 ms CPU load is 9%
@repeat(5) @timed def cpuload(): - then we getIn the first case, the ad was expanded incpuload .__ name __ == decorated2 cpuload () returns 16 Executing cpuload took 100 ms cpuload () returns 14 Executing cpuload took 109 ms cpuload () returns 0 Executing cpuload took 101 ms cpuload () returns 0 Executing cpuload took 100 ms cpuload () returns 0 Executing cpuload took 99 ms CPU load is 6%
cpuload=timed(repeat(5)(cpuload)) , in the second case - in cpuload=repeat(5)(timed(cpuload)) . Pay attention to the printed function names: you can trace the chain of calls in both cases. def toggle(decorator): """ "" "" """ def new_decorator(fn): decorated = decorator(fn) def new_decorated(*x): if decorator.enabled: return decorated(*x) else: return fn(*x) return new_decorated decorator.enabled = True return new_decorator @toggle(timed) def cpuload(): """ cpuload """ print "cpuload.__name__==" + cpuload.__name__ print "CPU load is %d%%" % cpuload() timed.enabled = False print "CPU load is %d%%" % cpuload() ( Source code entirely )The value that controls the connection / disconnection of the decorator is stored in thecpuload .__ name __ == new_decorated cpuload () returns 28 Executing cpuload took 101 ms CPU load is 28% cpuload () returns 0 CPU load is 0%
enabled attribute of the decorated function: Python allows you to stick arbitrary attributes to any function.toggle function can also be used as a decorator for decorators : @toggle def timed(fn): """ timed """ @toggle def repeat(times): """ repeat """ @timed @repeat(5) def cpuload(): """ cpuload """ print "cpuload.__name__==" + cpuload.__name__ print "CPU load is %d%%" % cpuload() timed.enabled = False print "CPU load is %d%%" % cpuload() ( Source code entirely )Um ... no, it didn't work! But why?cpuload .__ name __ == new_decorated cpuload () returns 28 cpuload () returns 0 cpuload () returns 0 cpuload () returns 0 cpuload () returns 0 Executing decorated2 took 501 ms CPU load is 5% cpuload () returns 0 cpuload () returns 16 cpuload () returns 14 cpuload () returns 16 cpuload () returns 0 Executing decorated2 took 500 ms CPU load is 9%
timed decorator shut down on the second cpuload call?timed is associated with a decorated decorator, i.e. with new_decorated function; it means that the timed.enabled = False line timed.enabled = False changes the attribute of the new_decorated function - the common “wrapper” of both decorators. It would be possible inside new_decorated instead of if decorator.enabled: to check if new_decorator.enabled: but then the line timed.enabled = False will disable both decorators at once.enabled attribute on the “internal” decorator, as before, we new_decorated couple of methods on the new_decorated function: def toggle(decorator): """ "" "" """ def new_decorator(fn): decorated = decorator(fn) def new_decorated(*x): # if decorator.enabled: return decorated(*x) else: return fn(*x) return new_decorated def enable(): decorator.enabled = True def disable(): decorator.enabled = False new_decorator.enable = enable new_decorator.disable = disable enable() return new_decorator print "cpuload.__name__==" + cpuload.__name__ print "CPU load is %d%%" % cpuload() timed.disable() print "CPU load is %d%%" % cpuload() ( Source code entirely )timed disconnected, but repeat continued to work:This is one of the most fascinating features of Python - not only attributes, but also arbitrary function methods can be added to functions. Functions on functions sit and functions chase.cpuload .__ name __ == new_decorated cpuload () returns 14 cpuload () returns 16 cpuload () returns 0 cpuload () returns 0 cpuload () returns 0 Executing decorated2 took 503 msCPU load is 6% cpuload () returns 0 cpuload () returns 0 cpuload () returns 7 cpuload () returns 0 cpuload () returns 0 CPU load is 1%
Source: https://habr.com/ru/post/187482/
All Articles