Monday, April 10, 2023

Small Programs using Python

 1. How to find the minimum value in a list?

Primitive

lst = [3, 6, 7, 2, 1, 5]

print(min(lst))

Object:

lst = [Car('Nano', 150), Car('Ferrari', 500), Car('Maruti', 250)]

new_lst = min(lst, key=lambda car: car.max_speed)



2. How to get a list sorted?

Primitive

lst = [3, 6, 7, 2, 1, 5]

sort(lst)

Object:

lst = [Car('Nano', 150), Car('Ferrari', 550), Car('Maruti', 250)]

new_lst = sorted(lst, key=lambda car: car.max_speed)

Reverse Order:

new_lst = sorted(lst, key=lambda car: car.max_speed, reverse=True)



3. How to reverse a String or a List?

List of Primitives

lst.reverse()

list(reversed(lst))

lst[::-1] 

String:

list(string).reverse()

string = "".join(reversed(string))

str[::-1]

List of Objects:

sort in reverse order.



4. How to search an element?

List of Primitives

lst = [3, 6, 7, 2, 1, 5]

print(6 in lst)

print(lst.index(6) > -1)

String

str = 'Eleven boys have a good football court.'

result = str.find('ball')

List of Objects:

lst = [Car('Nano', 150), Car('Ferrari', 500), Car('Maruti', 250)]

print(any(filter(lambda car: car.max_speed > 400, lst)))

# filter returns iterator, list(filter()) returns list

print(any(car for car in lst if car.max_speed > 400))



5. How to count the occurrences?

List of Primitives

lst = [3, 6, 7, 2, 1, 5]

print(lst.count(2))

String

str = 'Eleven boys have a good football court.'

print(str.count('o'))

List of Objects:

lst = [Car('Nano', 150), Car('Ferrari', 500), Car('Maruti', 250)]

print(len(filter(lambda car: car.max_speed > 400, lst)) )    

print(len([car for car in lst if car.max_speed > 400])) # square bracket same as list( ).



6. How to replace an element?

List of Primitives

lst = [3, 6, 7, 2, 1, 5]

lst[2] = 17

list(map(lambda x: x+10, lst))           

String

str = 'Eleven boys have a good football court.'

str = str.replace('boys', 'girls')

List of Objects:

lst = [Car('Nano', 150), Car('Ferrari', 500), Car('Maruti', 250)]

#   list of map of lamba cannot be used for changing object's state, it can only replace an object.

for car in lst:

    if car.max_speed > 450:

        car.max_speed = 450


Wednesday, April 5, 2023

Design Patterns in Python

1. Creational Design Patterns

Factory Method: The constructor stores a dictionary. A Factory method gets the state value from this dictionary passing the key as String.

Abstract Factory method: Here, the constructor should return objects of other classes based on whatever key is passed.

Singleton: A class variable is initialized by the constructor and a static getInstance() method returns that variable every time an object creation is initiated.

Prototype: This uses copy.copy(obj) or copy.deepcopy(obj) to create objects.

Builder: A builder class is used to work on different objects which are called one after the other.


2. Structural Design Patterns

Adapter: An actual object is passed to an adapter constructor to modify it for further operations.

Bridge: A composition (has-a relationship) is used where a class has another class as its instance variable initialized through constructor.

Composite: This ensures that a hierarchy is defined in a tree model by adding leaf classes to a list type instance variable of the composite class.

Decorator:  Each child class adds extra behaviour to parent as,

class FirstConcreteHandler(AbstractHandler):

class SecondConcreteHandler(AbstractHandler):

class ThirdConcreteHandler(AbstractHandler):

self.handler = FirstConcreteHandler(SecondConcreteHandler(ThirdConcreteHandler()))


3. Behavioural Design Patterns

Observer: This ensures that all Observer classes are updated when there is a change.A common class has instance variables of all Observer classes. It also has methods to update the Observers whenever its onchange() method is called.

Tuesday, April 4, 2023

Modules in Python

 A Module is a file containing functions, variables and classes. 

eg: Create a module named printNumbers.py as,

def printForward(n):

    for i in range(n):

        print(i+1)

def printBackwards(n):

    for i in range(n):

        print(n-i)


This can be imported to other files as,

import printNumbers as pn     #   module import

pn.printForward(5)

or,

from printNumbers import printForward      #    function import

printForward(5)


In order to view all the functions inside a module, use dir.

dir(printNumbers)


A module can be executed as a script if the below code is added,

if __name__ == "__main__":  

        printForward(5)



Built-in Functions

Python provides some built-in functions which can be used directly without importing.

eg: any(), print(), list(), input(), id(), len(), type(), iter(), sum().


Built-in Modules

Built-in modules can be used directly without installing. Some common built-in modules are,

1. math

This module is for mathematical operations. It has functions like ceil(), cos(), factorial(), prod(), isfinite(), sqrt(), pow(), isnan() and constants like nan, pi.

import math

print(math.sqrt(16))


2. os

This module performs many tasks of operating system. It has the functions mkdir(), chdir(), rmdir(), listdir() and getcwd().

import os

os.mkdir("d:\\tempdir")

os.chdir("d:\\temp")

os.getcwd()     #returns 'd:\\temp'


3. random

It has various methods to return a random number.

import random

random.random()    #   Returns a random float number between 0.0 to 1.0. 

random.randint(1,100)   #   Returns a random integer between the specified integers

random.randrange(1,10,2)   #  Returns a random element from the range created by start, stop and step.

random.choice([12,23,45,67,65,43])   #  Returns a randomly selected element from a sequence object such as string, list or tuple

numbers=[12,23,45,67,65,43]

random.shuffle(numbers)    #  This functions randomly reorders elements in a list.


4. statistics

This provides various statistical modules.

mean() to find arithmetic mean of numbers in a list.

median() to get the middle value.

mode() for most repeated.

stdev() for standard deviation.

import statistics

lst = [2,5,3,2,8,3,9,4,2,5,6]

print(statistics.mode(lst))

print(statistics.stdev(lst))


5. requests

This allows us to make HTTP requests.

import requests

url = 'https://www.w3schools.com/python/demopage.php'

x = requests.get(url)

myobj = {'somekey': 'somevalue'}

x = requests.post(url, json = myobj)


6. re

This module is for regex expressions. Most functions take in two parameters - pattern and text.

import re  

text = "The dog is barking. The dog is sleeping."  

result = re.search("dog", text)   # result.start() gives starting index

result = re.sub("dog", "cat", text)    # substitute

result = re.split("\s", text)   #  split at space

result = re.match("\S+@\S+\.\S+" , text)    #   email pattern, result.group(1) gives first match

In the pattern = "^[A-Za-z]{2}[0-9]+$", ^ stands for start, [ ] for characterset, { } for repeat, + for one or more and $ stands for end. Also, * is for 0 or more, ? for 0 or 1, (a|b) for either 'a' or 'b' and /d is for digit.


7. unittest

Testing can be done for a single statement as,

assert sum([ 2, 3, 5]) == 10, "Should be 10"  

But for multiple statements, we need the unittest module.

import unittest  

class TestingSum(unittest.TestCase):  

    def test_sum(self):  

        self.assertEqual(sum([2, 3, 5]), 10, "It should be 10")  

    def test_sum_tuple(self):  

        self.assertEqual(sum((1, 3, 5)), 10, "It should be 10")    

if __name__ == '__main__':  

    unittest.main()  


Other built-in modules are,

threading - adds multithreading capabilities to a class as,

class ClickTheMouse(threading.Thread): 

sys  - for managing Python runtime environment. eg:sys.path, sys.argv

collections  - provides alternatives to built-in containers. eg: namedtuple(), OrderedDict(), deque()

time  - time.ctime() gives the current time.

email  - for managing email messages

gc  - for garbage collector

html  - for manipulating html requests.

json  - to create json notations

pickle, marshal  - for serialization

locale  - to set locale

logging  - to add logs

sched  - for schedulers


Monday, April 3, 2023

Functional Programming in Python

 Functional programming is a programming paradigm that breaks down a problem into individual functions. In this paradigm, we avoid mutable data types and state changes as much as possible. It also emphasizes RECURSION rather than loops. map(), filter() and reduce() are the three cornerstone functions of functional programming.



map()

This first argument to map() is a transformation function, where each original item is transformed into a new one. 

num = [2, 3, 6, 9, 10]

def cube(num):

  return num ** 3

cubed = map(cube, num)

print(list(cubed))


Another way:

cubed = map(lambda n: n ** 3, num)

 

Another method:

list(map(lambda x, y: x / y, [6, 3, 5], [2, 4, 6]))

[3.0, 0.75, 0.8333333333333334]


A lot of math-related transformations can be performed with map().

For tuples we use starmap().


import itertools 

num = [(2, 3), (6, 9), (10,12)] 

multiply = itertools.starmap(lambda x,y: x * y, num)

list(multiply) #  [6, 54, 120]



filter()

A filtering operation processes an iterable and extracts the items that satisfy a given condition. The function argument must be a single-argument function. It’s typically a boolean-valued function that returns either True or False. The filter() accepts only one iterable.


num = [12, 37, 34, 26, 9, 250, 451, 3, 10]

even = list(filter(lambda x: (x % 2 == 0), num))

print(even)


sort() 


The sort method is a helpful tool to manipulate lists in Python. For example, if you need to sort a list in ascending or reverse order, you can use the following:


num = [24, 4, 13, 35, 28]

num.sort()

num.sort(reverse=True)

It is important to note that the sort() method mutates the original list and it is therefore impossible to revert back the list’s items to their original position. 



groupby()

ITERTOOLS.GROUPBY() takes a list of iterables and groups them based on a specified key. The key is useful to specify what action has to be taken to each individual iterable. The return value will be similar to a dictionary, as it is in the {key:value} form. Because of this, it is very important to sort the items with the same key as the one used for grouping. 


import itertools

spendings = [("January", 25), ("February", 47), ("March", 38), ("March", 54), ("April", 67),

             ("January", 56), ("February", 32), ("May", 78), ("January", 54), ("April", 45)]

spendings_dic = {}

func = lambda x: x[0]

for key, group in groupby(sorted(spendings, key=func), func):

    spendings_dic[key] = list(group)

print(spendings_dic)


{'April': [('April', 67), ('April', 45)],

 'February': [('February', 47), ('February', 32)],

 'January': [('January', 25), ('January', 56), ('January', 54)],

 'March': [('March', 38), ('March', 54)],

 'May': [('May', 78)]}

In the above snippet, we used sorted() instead of sort(). This is because we wanted to sort an iterable that was not a list.


Contrary to sort(), sorted() will create a copy of the original list, making it possible to retrieve the original order. 

Finally, we can use map() from the previous section to sum the monthly expenses:


monthly_spendings = {key: sum(map(lambda x: x[1], value)) for key, value in spendings_dic.items()}

print(monthly_spendings)

{'April': 112, 'February': 79, 'January': 135, 'March': 92, 'May': 78}



reduce()

The REDUCE() function implements a technique called FOLDING or reduction. It takes an existing function, applies it cumulatively to all the items in iterable, and returns a single final value.


reduce() was originally a built-in function and was moved to functools.reduce() in Python 3.0.


Unless you cannot find any solution other than reduce(), you should avoid using it. The reduce() function can create some abysmal performance issues because it calls functions multiple times, making your code slow and inefficient. Functions such as sum(), any(), all(), min(), max(), len(), math.prod() are faster, more readable, and Pythonic. 


from functools import reduce

yearly_spendings = reduce(lambda x, y:x + y, monthly_spendings.values())

print(yearly_spendings)

496


Serialization in Python

 There are different ways in Python to read data from files and to write data to files.


1. Using input from console

x = input('Enter your name:')

print('Hello, ' + x)



2. Using File Handling methods

The file handling methods in Python include open, close, readlines and writelines.

Common operation modes for files are r(read), w(write), a(append at end) and r+(read and write).


# read

text_file = open('/Users/pankaj/abc.txt','r')

line_list = text_file.readlines()

for line in line_list:

    print(line)

text_file.close()


# write

text_file = open('/Users/pankaj/file.txt','w')

word_list= []

for i in range (1, 5):

    print("Please enter data: ")

    line = input() 

    word_list.append(line) 

text_file.writelines(word_list) # overwrites file data

text_file.close() 


# append

text_file = open('/Users/pankaj/file.txt','a')

word_list= []

for i in range (1, 5):

    print("Please enter data: ")

    line = input() 

    word_list.append(line)

text_file.writelines(word_list)

text_file.close() 


The file.seek(7) moves the cursor to the specified location.



3. Serialization

Serialization is the process of converting the object into a format that can be stored or transmitted.  Python has a number of built-in modules for this process: marshall, json, and pickle. The marshall is mainly used by the interpreter. The json produces human-readable output and works well with other languages, but it works only with certain data types.So pickle provides the best Serialization solution. 

pickle.dump(): to convert data into serialized form

pickle.load(): to convert serialized format into original data type.


# pickle

import pickle

number_of_data = int(input('Enter the number of data : '))

data = []

for i in range(number_of_data):

    raw = input('Enter data '+str(i)+' : ')

    data.append(raw)

file = open('important', 'wb')

pickle.dump(data, file)

file.close()


# unpickle

file = open('important', 'rb')

data = pickle.load(file)

file.close()

for item in data:

    print(item)

Static in Python

Instance variables

They are declared inside a method or the constructor of a class. Their values vary from object to object.

Class variables

They are declared inside the class, but outside all method definitions. They are shared among all instances of a class. They are allocated memory when an object for the class is created for the first time. They can be accessed by either using objects or class name.

Instance methods

They are specific to each object. They can access both class variables and instance variables. They use self as the first parameter. They can be called only using the object of the class.

Class methods

They are shared among all objects of the class. They can access only class variables. They use cls as the first parameter. They can be called using ClassName or by using a class object. They are mostly used as factory methods.

Static methods 

They are also shared among all objects of the class. They cannot access any variable of a class. They do not take any extra parameter. They can be called using ClassName or by using a class object. They are mostly used as utility methods.

class Student:

    # class variables

    school_name = 'ABC School'


    # constructor

    def __init__(self, name, age):

        # instance variables

        self.name = name

        self.age = age


    # instance method

    def show(self):

        print(self.name, self.age, Student.school_name)


    @classmethod

    def change_School(cls, name):

        cls.school_name = name


    @staticmethod

    def find_notes(subject_name):

        return ['chapter 1', 'chapter 2', 'chapter 3']

Static methods are useful in creating Utility functions.


Decorator

A decorator is a function that takes another function as an argument and extends its behavior without explicitly modifying it. eg: for logging, debugging, authentication, measuring execution time, and many more. 

def decorator(func):

  def wrapper():

    print("This is printed before the function is called")

    func()

    print("This is printed after the function is called")  

  return wrapper

This decorator function is called by adding @decorator above the actual function. eg: @staticmethod, @classmethod, @property (for getter/setter). Its main function is to support code reusability.


Iterator

It is a way of iterating over iterable objects like lists, tuples, dicts, and sets.

tup = ("apple", "banana", "cherry")

itr = iter(tup)

print(next(itr))

print(next(itr))

print(next(itr))


Generator

Generators are functions used to create iterators. These functions use one or more yield statements instead of return.

def seq(x):

    for i in range(x):

        yield i      

range_ = seq(4)

print(next(range_))

print(next(range_))

print(next(range_))

print(next(range_))

Generator functions are more memory efficient than normal functions.

Multithreading in Python

Multithreading makes threads appear to be running parallelly. We can do multithreading in Python using the threading module.


import threading

def even():

    for i in range(0,20,2):

        print(i)

def odd():

    for i in range(1,20,2):

        print(i)

if __name__ == "__main__":      # main method

    trd1 = threading.Thread(target=even)

    trd2 = threading.Thread(target=odd)

    trd1.start() 

    trd2.start() 

    trd1.join()   # waiting until thread 1 is done

    trd2.join()   # waiting until thread 2 is done

    print('End')


functions:

threading.active_count(), threading.main_thread(),  threading.current_thread().name 

threading.enumerate()    # list of active threads


Synchronization

A race condition occurs when two or more threads access shared data and try to change it at the same time. We use the locks to solve this problem. The locks provide two methods: acquire, release.

lock = threading.Lock() 

trd1 = threading.Thread(target=increment,args=(lock,))

def increment(lock):

    for _ in range(200000):

        lock.acquire()

        a += 1

        lock.release()