Updated On : Nov-29,2019 Tags decorators, functools
functools (annotations) - Generate Wrapper Functions in Python

functools - annotations

Note: Please refer to the first part of the module to get an idea about functions of module functools. This part will cover annotations.

In [1]:
import functools
  • @functools.lru_cache(maxsize=128,typed=False) - It's a decoractor which wraps a function to provide memoizing functionality. It saves upto maxsize function calls. It uses dictionary behind the scene to cache calls.

If maxsizeis None, then Least Recent Used Cache(LRU Cache) is functionality is disabled and cache can grow without limit. Cache performs best when LRU is of size power of 2.LRU cache performs best when most recent calls are best estimates of upcoming calls.

If typed is set to True then function argument of different types will be stored seprately.Ex: f(3) & f(3.0) will be stored differently.

Decorator adds cache_info() method to wrapped function which provides named_tuple of hits,misses, maxsize and currsize. It also provides cache_clear() function for clearing/invalidating cache.

In [2]:
def factorial(n):
    if n == 1:
        return 1
        return n * factorial(n-1)

def factorial_without_cache(n):
    if n == 1:
        return 1
        return n * factorial(n-1)
In [3]:
In [4]:
%time print(factorial(6))
%time print(factorial_without_cache(6))
%time print(factorial(10))
%time print(factorial_without_cache(10))
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 223 µs
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 114 µs
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 55.3 µs
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 49.6 µs
In [5]:
CacheInfo(hits=5, misses=10, maxsize=64, currsize=10)
In [6]:
CacheInfo(hits=0, misses=0, maxsize=64, currsize=0)
  • @functools.total_ordering : It's a class decorator. If class has defined one or more comparison methods then it adds rest of them. Class must define one of (__lt__(), __gt__(), __le__(),__ge__()) and __eq__() compulsarily.
In [7]:
class Employee(object):
    def __init__(self,name,age):
        self.name = name
        self.age =age

    def __eq__(self, other):
        return (self.name == other.name) and (self.age == other.age)

    def __lt__(self, other):
        return self.age < other.age
In [8]:
print(dir(Employee)) ## Notice __le__(), __ge__() and __gt__().
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
  • @functools.singledispatch: This decorator will convert the normal function into single dispatch generic function. By single dispatch it means that based on the type of the first argument of a function, it’ll determine which generic function to execute.Generic function refers to a function that performs the same operation on different types of argument based on a type of argument.

To overload function for a particular data type register attribute of function is used. We can pass it datatype as an attribute for data type it needs to be invoked.

In [9]:
def add(x, y):
    print('Inside Main Function')
    return x + y

def dict_add(x, y):
    print('Inside dict version of  Function')
    return x

def set_add(x, y):
    print('Inside set version of  Function')
    return x.union(y)

def str_add(x, y):
    print('Inside string version of  Function')
    return x + y

def complex_add(x, y):
    print('Inside complex version of  Function')
    return complex(x.real + y.real, x.imag + y.imag)
In [10]:
Inside Main Function
[1, 2, 3, 4, 5, 6]
In [11]:
Inside string version of  Function
In [12]:
Inside dict version of  Function
{'a': 'b', 'c': 'd'}
In [13]:
add(set([1,2,3]), set([3,4,5]))
Inside set version of  Function
{1, 2, 3, 4, 5}
In [14]:
add(5+6j, 6+5j)
Inside complex version of  Function

When we don't find a particular version for data type then it's method resolution order is used to find appropriate function. Original function decorated with @singledispatch is registered for object type which will be executed if no matching function found for a particular data type.

One can find which function will be called for which data type based on dispatch(datatype) method of function.

In [15]:
print(add.dispatch(dict), add.dispatch(set), add.dispatch(complex), add.dispatch(bool))
<function dict_add at 0x7fb7db2db730> <function set_add at 0x7fb7db2db7b8> <function complex_add at 0x7fb7db2db8c8> <function add at 0x7fb7db2db158>
In [16]:
dict_keys([<class 'object'>, <class 'dict'>, <class 'set'>, <class 'str'>, <class 'complex'>])
In [17]:
print(add.registry[object], add.registry[dict], add.registry[set])
<function add at 0x7fb7db2db158> <function dict_add at 0x7fb7db2db730> <function set_add at 0x7fb7db2db7b8>
  • functools.wraps(wrapped,assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES) - It's convenience decorator for functools.update_wrapper()(Functools - functions).
In [18]:
def outer2(func):
    def wrapper(*args):
        print('Before execution')
        print('After execution')
    return wrapper

def print_title_2(title):
    Simple print functionality

print_title_wrapped = outer2(print_title_2)
print_title_wrapped('Welcome to CoderzColumn')
print('Attributes not updated even after applying decorator : '+ str(dict(zip(['__module__', '__name__', '__qualname__', '__doc__', '__annotations__','__dict__'],
                                                  [print_title_wrapped.__module__,print_title_wrapped.__name__, print_title_wrapped.__qualname__, print_title_wrapped.__doc__, print_title_wrapped.__annotations__, print_title_wrapped.__dict__]))))

def outer(func):
    def wrapper(*args):
        print('Before execution')
        print('After execution')
    return wrapper

def print_title(title):
    Simple print functionality

print_title('Welcome to CoderzColumn')
 ## Please make a not that document string also got updated otherwise it would be None withuot decorator and function name also got updated from wrapper to print_function.
print('Attributes updated due to decorator : '+ str(dict(zip(['__module__', '__name__', '__qualname__', '__doc__', '__annotations__','__dict__'],
                                                  [print_title.__module__,print_title.__name__, print_title.__qualname__, print_title.__doc__, print_title.__annotations__, print_title.__dict__]))))
Before execution
Welcome to CoderzColumn
After execution
Attributes not updated even after applying decorator : {'__module__': '__main__', '__name__': 'wrapper', '__qualname__': 'outer2.<locals>.wrapper', '__doc__': None, '__annotations__': {}, '__dict__': {}}

Before execution
Welcome to CoderzColumn
After execution
Attributes updated due to decorator : {'__module__': '__main__', '__name__': 'print_title', '__qualname__': 'print_title', '__doc__': '\n    Simple print functionality\n    ', '__annotations__': {}, '__dict__': {'__wrapped__': <function print_title at 0x7fb7db27c488>}}
Sunny Solanki  Sunny Solanki

  Support Us

Thank You for visiting our website. If you like our work, please support us so that we can keep on creating new tutorials/blogs on interesting topics (like AI, ML, Data Science, Python, Digital Marketing, SEO, etc.) that can help people learn new things faster. You can support us by clicking on the Coffee button at the bottom right corner. We would appreciate even if you can give a thumbs-up to our article in the comments section below.

 Want to Share Your Views? Have Any Suggestions?

If you want to

  • provide some suggestions on topic
  • share your views
  • include some details in tutorial
  • suggest some new topics on which we should create tutorials/blogs
Please feel free to let us know in the comments section below (Guest Comments are allowed). We appreciate and value your feedbacks.