Share @ Google LinkedIn Facebook  functools, decorators, wrapperfunctions

Overview

functools module provides functions which acts on other functions and returns modified functions.

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 return negative,zero and positive number based on less than, equal and greater than relationship between 2 elements. Key function takes single element and returns a value which 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 same arguments again and again.
  • functools.partialmethod(func,*args,**kwargs) - It returns new partialmethod descriptor which is same as partial object. It's designed to be method definition in class than directly being called like 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>}}

Let other learners know about this article @ Google LinkedIn Facebook
Sunny Solanki  Sunny Solanki