File: decorator0.py

"""
tracer function decorator examples
run me under 3.0 to make the 3rd example execute
"""

# add return, **, 3.0 print()


class tracer:
    def __init__(self, func):                # on @ decorator
        self.calls = 0                       # save func for later call
        self.func  = func
    def __call__(self, *args, **kwargs):     # on call to original function
        self.calls += 1
        print('call %s to %s' % (self.calls, self.func.__name__))
        return self.func(*args, **kwargs)


if __name__ == '__main__':
    @tracer
    def spam(a, b, c):        # same as: spam = tracer(spam)
        print(a + b + c)      # triggers tracer.__init__

    @tracer
    def eggs(x, y):           # same as: eggs = tracer(eggs)
        print(x ** y)         # wraps eggs in a tracer object

    spam(1, 2, 3)             # really calls tracer instance: runs tracer.__call__        
    spam(a=4, b=5, c=6)       # spam is an instance attribute

    eggs(2, 16)               # really calls tracer instance, self.func is eggs
    eggs(4, y=4)              # self.calls is per-function here (need 3.0 nonlocal)
    print('')

    
###################################################################


# enclosing scopes, global shared by all wrapped functions

calls = 0
def tracer(func):                         # state via nested scope and global
    def wrapper(*args, **kwargs):         # instead of class attributes
        global calls                      # calls is global, not per-function
        calls += 1
        print('call %s to %s' % (calls, func.__name__))
        return func(*args, **kwargs)
    return wrapper


if __name__ == '__main__':
    @tracer
    def spam(a, b, c):        # same as: spam = tracer(spam)
        print(a + b + c)

    @tracer
    def eggs(x, y):           # same as: eggs = tracer(eggs)
        print(x ** y)

    spam(1, 2, 3)             # really calls wrapper, bound to func        
    spam(a=4, b=5, c=6)       # wrapper calls spam

    eggs(2, 16)               # really calls wrapper, bound to eggs
    eggs(4, y=4)              # global calls is not per-function here
    print('')


###################################################################

# enclosing scopes, 3.0 nonlocal is per-function counter 

#
# caveat: need to exec as string, else 'nolocal' is a 
# syntax error in 2.6
#

import sys
if sys.version_info[0] == 3:
    code = """

def tracer(func):                         # state via nested scope and nonlocal
    calls = 0                             # instead of class attrs or global
    def wrapper(*args, **kwargs):         # calls is per-function, not global
        nonlocal calls
        calls += 1
        print('call %s to %s' % (calls, func.__name__))
        return func(*args, **kwargs)
    return wrapper


if __name__ == '__main__':
    @tracer
    def spam(a, b, c):        # same as: spam = tracer(spam)
        print(a + b + c)

    @tracer
    def eggs(x, y):           # same as: eggs = tracer(eggs)
        print(x ** y)

    spam(1, 2, 3)             # really calls wrapper, bound to func        
    spam(a=4, b=5, c=6)       # wrapper calls spam

    eggs(2, 16)               # really calls wrapper, bound to eggs
    eggs(4, y=4)              # nonlocal calls is per-function here
"""

    exec(code)
    print('')



[Home page] Books Code Blog Python Author Train Find ©M.Lutz