Share @ LinkedIn Facebook  functools, decorators, wrapperfunctions
functools (functions) - Generate Wrapper Functions in Python

Overview

functools module provides functions that act on other functions and returns modified functions. It lets us add more functionality by providing a wrapper around existing functions.

We'll be covering this tutorial in 2 parts. In this part, we'll be covering functions of this module and in 2nd part, we'll be covering annotations available for the same functionalities.

In [1]:
import functools
import datetime
import pytz
import time

functools functions:

  • functools.cmp_to_key(func) - It converts old-style comparison function to key function.

Comparison function takes 2 arguments and returns negative, zero and positive numbers based on less than, equal and greater than the relationship between 2 elements. The key function takes single element and returns a value that will then be used for sorting.

In [2]:
class Employee(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return self.name + ' : , Age : '+str(self.age)

e1 = Employee('Sunny',27)
e2 = Employee('Sumit',27)
e3 = Employee('Anup',35)
e4 = Employee('Ashwin',35)
e5 = Employee('Rohit',38)
e6 = Employee('Irfan',38)
e7 = Employee('Ethesh',22)
e8 = Employee('Kaushal',45)

def emp_cmp(emp1,emp2):
    return 1 if emp1.age > emp2.age else -1 if emp1.age < emp2.age else 0

sorted_employees = sorted([e8,e7,e6,e5,e4,e3,e2,e1], key = functools.cmp_to_key(emp_cmp))
print([str(emp) for emp in sorted_employees]) ## Please make a not that it's stable sort as it maintained same order for employees with same age.
['Ethesh : , Age : 22', 'Sumit : , Age : 27', 'Sunny : , Age : 27', 'Ashwin : , Age : 35', 'Anup : , Age : 35', 'Irfan : , Age : 38', 'Rohit : , Age : 38', 'Kaushal : , Age : 45']
  • functools.partial(func,*args,**kwargs) - It wraps function provided to it with arguments provided as args & kwargs set and returns partial object. It's generally used to freeze few arguments of some function with too many arguments so that one can avoid giving the same arguments again and again.
  • functools.partialmethod(func,*args,**kwargs) - It returns a new partialmethod descriptor which is the same as partial object. It's designed to be method definition in class than directly being called partial. It freezes all parameters.
In [3]:
freezed_params = {'hour':5,'minute':0,'second':59,'microsecond':999}
partial_datetime = functools.partial(datetime.datetime, **freezed_params)
partial_datetime(2015,1,1), partial_datetime(2018,2,21), partial_datetime(2017,1,31)
Out[3]:
(datetime.datetime(2015, 1, 1, 5, 0, 59, 999),
 datetime.datetime(2018, 2, 21, 5, 0, 59, 999),
 datetime.datetime(2017, 1, 31, 5, 0, 59, 999))
In [4]:
partial_datetime.func, partial_datetime.args, partial_datetime.keywords
Out[4]:
(datetime.datetime,
 (),
 {'hour': 5, 'minute': 0, 'second': 59, 'microsecond': 999})
In [5]:
class Employee(object):
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def decrease_salary(self, percentage):
        self.salary -= (self.salary*(percentage/100))
    def increase_salary(self, percentage):
        self.salary += (self.salary*(percentage/100))

    highest_performar_increase = functools.partialmethod(increase_salary, 30)
    average_performar_increase = functools.partialmethod(increase_salary, 15)
    bad_performar_decrease = functools.partialmethod(decrease_salary, 5)

e1 = Employee('Sumit', 25000)
e2 = Employee('Jayesh', 35000)
e3 = Employee('Rohit', 35000)
e1.highest_performar_increase(), e2.average_performar_increase(), e3.bad_performar_decrease()
e1.salary, e2.salary, e3.salary
Out[5]:
(32500.0, 40250.0, 33250.0)
  • functools.reduce(function,iterable[,initializer]) - It applies function of 2 arguments cummunlatively to all elements of iterable from left to right to return single value.If initializer is provided then function is performed first on initializer and first element of iterable.
In [6]:
print(functools.reduce(lambda x,y : x - y, range(1,5))) # (((1-2)-3)-4)
print(functools.reduce(lambda x,y : x - y, range(1,5), 100 )) # ((((100-1)-2)-3)-4)
-8
90
  • functools.update_wrapper(wrapper, wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES) - It assigns/updates atrributes of wrapped(original function) to wrapper function.
In [7]:
functools.WRAPPER_ASSIGNMENTS, functools.WRAPPER_UPDATES
Out[7]:
(('__module__', '__name__', '__qualname__', '__doc__', '__annotations__'),
 ('__dict__',))
In [8]:
def outer(func):
    def inner(*args):
        print('Before execution')
        func(*args)
        print('After execution')
    return inner

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

wrapper = outer(print_title)
print('Attributes before update : '+ str(dict(zip(['__module__', '__name__', '__qualname__', '__doc__', '__annotations__','__dict__'],
                                                  [wrapper.__module__,wrapper.__name__, wrapper.__qualname__, wrapper.__doc__, wrapper.__annotations__, wrapper.__dict__]))))
functools.update_wrapper(wrapper, print_title)
print('\nAttributes after update : '+ str(dict(zip(['__module__', '__name__', '__qualname__', '__doc__', '__annotations__','__dict__'],
                                                  [wrapper.__module__,wrapper.__name__,wrapper.__qualname__, wrapper.__doc__, wrapper.__annotations__, wrapper.__dict__]))))
Attributes before update : {'__module__': '__main__', '__name__': 'inner', '__qualname__': 'outer.<locals>.inner', '__doc__': None, '__annotations__': {}, '__dict__': {}}

Attributes after update : {'__module__': '__main__', '__name__': 'print_title', '__qualname__': 'print_title', '__doc__': '\n    Simple print functionality\n    ', '__annotations__': {}, '__dict__': {'__wrapped__': <function print_title at 0x7fbb319fed90>}}


Sunny Solanki  Sunny Solanki