Pages

Friday, September 29, 2017

Python decorators

class Car:
    def __init__(self):
        print("Car object created.")
    def drive():
        print("driving a car..")
       
c = Car()
c.drive() ## this is equivalent to Car.drive(c) but this will work
Car.drive()

TypeError: drive() takes 0 positional arguments but 1 was given


Solution is @staticmethod. With staticmethod we don't have to initialize object. staticmethod doesn't need any object we can call method directly from class and staticmethod cannot access class attributes.

Car.drive()

class Car:
    def __init__(self):
        print("Car object created.")
   
    @staticmethod
    def drive():
        print("driving a car..")
       
c = Car()
c.drive()

output:
Car object created.
driving a car..

======

class Car:
    def __init__(self):
        print("Car object created.")
   
    @staticmethod
    def drive():
        print("driving a car..")
       
    #@classmethod
    def sell(c):
        print("c =", c)
       
c = Car()
c.drive()
c.sell()

Car object created.
driving a car..
c = <__main__ .car="" 0x7fda7b572b00="" at="" object="">

to make it work, use classmethod

class Car:
    def __init__(self):
        print("Car object created.")
   
    @staticmethod ## we don’t pass anything
    def drive():
        print("driving a car..")
       
    @classmethod ## always take class as first argument
    def sell(c):
        print("c =", c)
       
c = Car()
c.drive()
c.sell() or Car.sell()

output:
Car object created.
driving a car..
c =

staticmethod pass nothing whether u call from class or instance. It will call function as it is. Staticmethod cannot access class or instance attribute.

classmethod: whether you call for class perspective or instance perspective always it will call class as first argument

decorator is a pattern which allows one function or object to decorate another object

def greet():
    return "Hello world"
   
def convert_to_upper(fn):
    def wrapper():
        return fn().upper()
    return wrapper
   
print("greet =", greet)
print(greet())

greet = convert_to_upper(greet)

print("greet =", greet)
print(greet())

output:
greet =
Hello world
greet = .wrapper at 0x7f790d200e18>
HELLO WORLD

Actual code should be:

def convert_to_upper(fn):
    def wrapper():
        return fn().upper()
    return wrapper
   

@convert_to_upper
def greet():
    return "Hello world"   
   
print("greet =", greet)
print(greet())

##output
greet = .wrapper at 0x7f692a474e18>
-->
HELLO WORLD


def outer_function():
message = 'Hi'

def inner_function():
print(message)
return inner_function()

outer_function()

def outer_function():
        message = 'Hi'

        def inner_function():
                print(message)
        return inner_function

my_func = outer_function()
my_func()
my_func()
my_func()

def outer_function(msg):
        message = msg

        def inner_function():
                print(message)
        return inner_function

hi_func = outer_function('Hi')
bye_func = outer_function('Bye')
hi_func()
bye_func()

def outer_function(msg):
        def inner_function():
                print(msg)
        return inner_function

hi_func = outer_function('Hi')
bye_func = outer_function('Bye')
hi_func()
bye_func()

## all of above code is on closure. Now let's talk about decorator. It is similar to closure.
## a decorator is a function that takes another function as argument, adds some kind of functionality and return another function all of this without altering the source code of original function passed in

## let's replace outer function with decorator and inner function with wrapper. Instead of message we are going to accept function as argument

def decorator_function(original_function):
def wrapper_function():
return original_function()
return wrapper_function

def display():
print('display function ran')

decorated_display = decorator_function(display)
decorated_display()

## here decorated_display is equal to wrapper function

## decorator function allows us to easily add functionality to our existing function by adding that functionality inside our wrapper function

def decorator_function(original_function):
        def wrapper_function():
                print('wrapper executed this before {}'.format(original_function.__name__))
                return original_function()
        return wrapper_function

def display():
        print('display function ran')

decorated_display = decorator_function(display)
decorated_display()

## or

def decorator_function(original_function):
        def wrapper_function():
                print('wrapper executed this before {}'.format(original_function.__name__))
                return original_function()
        return wrapper_function

@decorator_function
def display():
        print('display function ran')

display()

#@decorator_function is equal to display = decorator_function(display)
## Now let's decorate two functions with same decorator

def decorator_function(original_function):
        def wrapper_function(*args, **kwargs):
                print('wrapper executed this before {}'.format(original_function.__name__))
                return original_function(*args, **kwargs)
        return wrapper_function

@decorator_function
def display():
        print('display function ran')

@decorator_function
def display_info(name,age):
print('display_info ran with arguments ({}, {})'.format(name, age))

display_info('Nawraj', 32)

display()

## We can also use class as decorator instead of function

class decorator_class(object):
def __init__(self, original_function):
self.original_function = original_function ## tie our function with the instance of this class

## instead of wrpper function we need to use call method here
def __call__(self, *args, **kwargs):
print('call method executed this before {}'. format(self.original_function.__name__))
return self.original_function(*args, **kwargs)

@decorator_class
def display():
        print('display function ran')

@decorator_class
def display_info(name,age):
        print('display_info ran with arguments ({}, {})'.format(name, age))

display_info('Nawraj', 32)
display()

## some practical examples
def my_logger(orig_func):
import logging
logging.basicConfig(filename='{}.log'.format(orig_func.__name__), level=logging.INFO)

def wrapper(*args, **kwargs):
logging.info(
'Ran with args: {}, and kwargs: {}'.format(args, kwargs))
return orig_func(*args, **kwargs)
return wrapper

def my_timer(orig_func):
import time

def wrapper(*args, **kwargs):
t1 = time.time()
result = orig_func(*args, **kwargs)
t2 = time.time() - t1
print('{} ran in: {}'.format(orig_func.__name__, t2))
return result
return wrapper

#@decorator_function
#def display():
#        print('display function ran')

@my_logger
def display_info(name,age):
        print('display_info ran with arguments ({}, {})'.format(name, age))

#display_info('Nawraj', 32)
## this will create a display_info.log file

## we can add this logging functionaltiy to any new function using this decorator. We don't have to add logging functionality to all new functions

## Now lets use timer decorator
import time
@my_timer
def display_info(name,age):
        time.sleep(1)
        print('display_info ran with arguments ({}, {})'.format(name, age))
display_info('Nawraj', 32)

## we can also use two decorator to decorate same function
def my_logger(orig_func):
        import logging
        logging.basicConfig(filename='{}.log'.format(orig_func.__name__), level=logging.INFO)

        def wrapper(*args, **kwargs):
                logging.info(
                        'Ran with args: {}, and kwargs: {}'.format(args, kwargs))
                return orig_func(*args, **kwargs)
        return wrapper

def my_timer(orig_func):
        import time

        def wrapper(*args, **kwargs):
                t1 = time.time()
                result = orig_func(*args, **kwargs)
                t2 = time.time() - t1
                print('{} ran in: {}'.format(orig_func.__name__, t2))
                return result
        return wrapper

import time

@my_logger
@my_timer
def display_info(name,age):
        time.sleep(1)
        print('display_info ran with arguments ({}, {})'.format(name, age))

display_info('Nawraj', 32)

# note:
@my_logger
@my_timer
is equivalent to display_info = my_logger(my_timer(display_info))
first my_timer will execute and then my_logger

## to preserve the identity of original function in case of nested decorator we use wraps to decorate all our wrapper
from functools import wraps

def my_logger(orig_func):
        import logging
        logging.basicConfig(filename='{}.log'.format(orig_func.__name__), level=logging.INFO)

        @wraps(orig_func)
        def wrapper(*args, **kwargs):
                logging.info(
                        'Ran with args: {}, and kwargs: {}'.format(args, kwargs))
                return orig_func(*args, **kwargs)
        return wrapper

def my_timer(orig_func):
        import time

        @wraps(orig_func)
        def wrapper(*args, **kwargs):
                t1 = time.time()
                result = orig_func(*args, **kwargs)
                t2 = time.time() - t1
                print('{} ran in: {}'.format(orig_func.__name__, t2))
                return result
        return wrapper

import time

@my_logger
@my_timer
def display_info(name,age):
        time.sleep(1)
        print('display_info ran with arguments ({}, {})'.format(name, age))

display_info('Nawraj', 32)

No comments:

Post a Comment