Decorators
@Add_This_Functionality_To_Any_Function!
Simple Example:
def my_decorator(original_function):
def new_function(*args,**kwargs):
print("you did it!")
of = original_function(*args,**kwargs)
return of
return new_function
@my_decorator
def my_func1(stuff):
print("do things")
@my_decorator
def my_func2(stuff):
print("do things")
@my_decorator
def my_func3(stuff):
print("do things")
my_func1(1);my_func2(1);my_func3(1);
Tutorial:
We need to discuss global variables and function closure
[1] functions:
def foo():
return 1;
[2] scope
a_string = 'this is a string'
def foo():
print(locals())
foo() # {}
print(globals()) # {..., 'a_string': 'this is a string'}
[3] varible resolution rules
Local variables with the same name as global ones don't modify the global one.
[4] variable lifetime
def foo():
x = 1;
foo() # NameError: name 'x' is not defined
[5] function arguments and parameters
def foo(x):
print(locals())
foo(1) # {'x': 1}
def foo(x, y=0): # remember if no default it's mandatory
return x - y
foo()
# Traceback (most recent call last):
# File "", line 1, in
# TypeError: foo() missing 1 required positional argument: 'x'
[6] Nested Functions
def outer():
x = 1
def inner():
print(x)
inner()
outer() # 1
[7] Functions are first class objectsin python
# all objects in Python inherit from a common baseclass
issubclass(int, object) # True
def foo():
pass
foo.__class__ # <type 'function'>
issubclass(foo.__class__, object) # True
#..so what?
def add(x,y):
return x + y
def sub(x,y):
return x - y
def apply(func, x, y):
return func(x, y)
apply(add, 2, 1) # 3
apply(sub, 2, 1) # 1
#.. closure lead in
def outer():
def inner():
print('this is inner')
return inner # the function not what it returns
foo = outer()
foo # .inner at 0x10be011e0>
foo() # 'this is inner'
[8] Closures
def outer():
x = 1
def inner():
print(x)
return inner # the function not what it returns
foo = outer()
foo.__closure__ # (,)
# aside: cell objects are used to store the free variables of a closure
# without closures x would have not existed as after the call to outer x is gone based on variable life time.
# inner functions defined in non-global scope remember what their enclosing namespaces looked like at definition time.
foo() # 1
# let's tweak it
def outer(x):
def inner():
print(x)
return inner
print1 = outer(1)
print2 = outer(2)
print1() # 1
print2() # 2
[9] Decorators
def outer(some_func):
def inner():
print('before some_func')
ret = some_func() # 1
return ret + 1
return inner
def foo():
return 1
decorated = outer(foo)
decorated() # we've added functionality to foo()!
# what if we did this
foo = outer(foo)
foo()
# before some_func
# 2
# Let's write a more useful decorator
class Coordinate(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return "Coord: " + str(self.__dict__)
def add(a, b):
return Coordinate(a.x + b.x, a.y + b.y)
def sub(a, b):
return Coordinate(a.x - b.x, a.y - b.y)
one = Coordinate(100, 200)
two = Coordinate(300, 200)
add(one, two) # Coord: {'y': 400, 'x': 400}
# add this instance
three = Coordinate(-100, -100)
def wrapper(func):
def checker(a, b): # 1
if a.x < 0 or a.y < 0:
a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
if b.x < 0 or b.y < 0:
b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
ret = func(a, b)
if ret.x < 0 or ret.y < 0:
ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
return ret
return checker
add = wrapper(add)
sub = wrapper(sub)
sub(one, two)
add(one, three)
[10] the @ symbol
# so instead of wrapper(add)\wrapper(sub), use @wrapper
@wrapper
def add(a, b):
return Coordinate(a.x + b.x, a.y + b.y)
@wrapper
def sub(a, b):
return Coordinate(a.x - b.x, a.y - b.y)
[11] *args and **kwargs
def one(*args):
print(args)
one() # ()
one(1,2,3) # (1, 2, 3)
def two(x, y, *args):
print(x, y, args)
two('a','b','c')
# Reminder
# l = (1,2,3)
# one(l[0], l[1], l[2]) # (1, 2, 3)
# one(*l) # (1, 2, 3)
def foo(**kwargs):
print(kwargs)
foo()
foo(x=1, y=2)
[12] More generic decorators
def logger(func):
def inner(*args, **kwargs):
print('Arguments were: {}, {}'.format(args,kwargs))
return func(*args, **kwargs)
return inner
@logger
def foo1(x,y=1):
return x * y
@logger
def foo2():
return 2
foo1(5,4)
# Arguments were: (5, 4), {}
# 20
foo2()
# Arguments were: (), {}
# 2