Functions are beautiful when they are small!
They are small when they do only one thing. A sort of single responsibility principle for functions, if you will.
One way to achieve this is by using decorators.
A decorator can be used to add some extra functionality to a function without modifying the actual function definition.
For example if we want to log the calling of a function, adding that functionality to a function definition violates the single responsibility principle. Besides, adding it to each and every function is ugly, and not very DRY.
Instead, we can define a decorator which does this and decorate all the functions with it.
Log calling of a function with a decorator
def log_call(func):
def inner(*args, **kwargs):
print "calling {}".format(func.__name__)
if args:
print "with arguments: {}".format(str(args))
if kwargs:
print "with keyword arguments: {}".format(str(kwargs))
return func(*args, **kwargs)
return inner
@log_call
def add(a, b=5):
return a + b
@log_call
def sub(a, b):
return a - b
@log_call
def printer(string):
print string
print add(2, 3)
print sub(2, 3)
printer("Hello world!")
print add(2, b=3)
OUTPUT
calling add
with arguments: (2, 3)
5
calling sub
with arguments: (2, 3)
-1
calling printer
with arguments: ('Hello world!',)
Hello world!
calling add
with arguments: (2,)
with keyword arguments: {'b': 3}
5
Calling any function decorated with log_call
will log the call and execute the called function. This decorator might be useful while debugging your code.
How does it work?
Whenever a function is decorated with log_call
we are baking a new function with the additional
functionality and assigning the decorated function’s name to it.
When we call add
, we are actually calling the decorated add
.
@log_call
def add(a, b=5)
is a syntactig sugar for:
add = log_call(add)
print add(2, 3) # calling decorated 'add'