Learn about decorators, kwargs and args.
Python Decorators, **args and **kwargs

Decorators

First Class Objects

In [26]:
def outer():
    def inner():
        print "Inside inner() function."
    return inner  # This returns a function.
In [27]:
# Here, we are assigning `inner` to the object `call_outer`.
call_outer = outer()
In [28]:
# We can call `call_outer`. This works only because we have an object `call_outer` that contains `inner`.
call_outer()
Inside inner() function.

If we do not return inner, we would get a NoneType error. This is because the inner function is redefined at every outer() call. When we do not return inner, it would not exist when it goes out of scope.

In [29]:
def outer():
    def inner():
        print "Inside inner() function."
In [30]:
call_outer = outer()
In [31]:
call_outer()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-31-5924316b2ca9> in <module>()
----> 1 call_outer()

TypeError: 'NoneType' object is not callable

*args

*args is used to indicate that positional arguments should be stored in the variable args. <br > * is for iterables and positional parameters.

In [33]:
def dummy_func(*args):
    print args
In [37]:
# * allows us to extract positional variables from an iterable when we are calling a function
dummy_func(*range(10))
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
In [38]:
# If we do not use *, this would happen
dummy_func(range(10))
([0, 1, 2, 3, 4, 5, 6, 7, 8, 9],)
In [39]:
# See how we can have varying arguments?
dummy_func(*range(2))
(0, 1)

**kwargs

** is for dictionaries & key/value pairs.

In [40]:
def dummy_func_new(**kwargs):
    print kwargs
In [42]:
dummy_func_new(a=0, b=1)
{'a': 0, 'b': 1}
In [44]:
# Again, there's no limit to the number of arguments.
dummy_func_new(a=0, b=1, c=2)
{'a': 0, 'c': 2, 'b': 1}
In [46]:
# You can even leave out any arguments.
dummy_func_new()
{}
In [51]:
# Similar to *args, you can use ** for definition.
new_dict = {'a': '10', 'b': '20'}
new_dict
Out[51]:
{'a': '10', 'b': '20'}
In [53]:
dummy_func_new(**new_dict)
{'a': '10', 'b': '20'}
In [54]:
# If we do not use **, we would get an error.
dummy_func_new(new_dict)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-54-586312740b6a> in <module>()
      1 # If we do not use **, we would get an error.
----> 2 dummy_func_new(new_dict)

TypeError: dummy_func_new() takes exactly 0 arguments (1 given)

Decorators

Now we have a decent understanding of first class objects, *args and **kwargs, we can show how decorators work. It basically allows us to modify our original function and even replace it without changing the function's code.

In [70]:
def printall(func):
    def inner(*args, **kwargs):
        print 'Arguments for args: {}'.format(args)
        print 'Arguments for kwargs: {}'.format(kwargs)
        return func(*args, **kwargs)
    return inner
In [71]:
@printall
def random_func(a, b):
    return a * b
In [72]:
a = random_func(2, 2)
Arguments for args: (2, 2)
Arguments for kwargs: {}
In [73]:
a
Out[73]:
4
In [74]:
@printall
def random_func_new():
    return 10
In [75]:
random_func_new()
Arguments for args: ()
Arguments for kwargs: {}
Out[75]:
10
Tags: python