Updated On : Nov-29,2019 Time Investment : ~20 mins

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.

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.

@functools.lru_cache(maxsize=64)
def factorial(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n-1)

def factorial_without_cache(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n-1)
print(factorial_without_cache(4))
print(factorial(4))
24
24
%time print(factorial(6))
%time print(factorial_without_cache(6))
%time print(factorial(10))
%time print(factorial_without_cache(10))
720
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 223 µs
720
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 114 µs
3628800
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 55.3 µs
3628800
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 49.6 µs
factorial.cache_info()
CacheInfo(hits=5, misses=10, maxsize=64, currsize=10)
factorial.cache_clear()
factorial.cache_info()
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.
@functools.total_ordering
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
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.

@functools.singledispatch
def add(x, y):
    print('Inside Main Function')
    return x + y

@add.register(dict)
def dict_add(x, y):
    print('Inside dict version of  Function')
    x.update(y)
    return x

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

@add.register(str)
def str_add(x, y):
    print('Inside string version of  Function')
    return x + y

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

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.

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>
add.registry.keys()
dict_keys([<class 'object'>, <class 'dict'>, <class 'set'>, <class 'str'>, <class 'complex'>])
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).
def outer2(func):
    def wrapper(*args):
        print('Before execution')
        func(*args)
        print('After execution')
    return wrapper

def print_title_2(title):
    """
    Simple print functionality
    """
    print(title)

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):
    @functools.wraps(func)
    def wrapper(*args):
        print('Before execution')
        func(*args)
        print('After execution')
    return wrapper

@outer
def print_title(title):
    """
    Simple print functionality
    """
    print(title)

print()
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

YouTube Subscribe Comfortable Learning through Video Tutorials?

If you are more comfortable learning through video tutorials then we would recommend that you subscribe to our YouTube channel.

Need Help Stuck Somewhere? Need Help with Coding? Have Doubts About the Topic/Code?

When going through coding examples, it's quite common to have doubts and errors.

If you have doubts about some code examples or are stuck somewhere when trying our code, send us an email at coderzcolumn07@gmail.com. We'll help you or point you in the direction where you can find a solution to your problem.

You can even send us a mail if you are trying something new and need guidance regarding coding. We'll try to respond as soon as possible.

Share Views 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 contact us at coderzcolumn07@gmail.com. We appreciate and value your feedbacks. You can also support us with a small contribution by clicking DONATE.


Subscribe to Our YouTube Channel

YouTube SubScribe

Newsletter Subscription