May 19, 2020

Python List Vs Array

# Arrays Vs Lists
  • Arrays need to be declared. Lists don’t
  • Arrays can store data very compactly
  • Arrays are great for numerical operations

import array

# Array (stores single data type)
array.array('i', [1, 22, 30, 44, 51]) # integer
array.array('d', [2.5, 3.2, 3.3]) # float
array.array('u', ['a', 'b', 'c']) # unicode

#List
ll = ['abc', 10, ['a', 'b', 'c'], (1,2,3)] # List can store anything 


import numpy as np

# Numpy Array (it can store various data types)
array_2 = np.array(["numbers", 3, 6, 9, 12])
print (array_2)
print(type(array_2))





 

Python random

import random

random.random() ---> 0 to 1 float
random.randint() * 100 ---> 0 to 100 integer
random.randint() * 100 - 50       ---> -50 to 50 integer
random.randint(1, 40) ---> b/w 1 to 40 integer
random.uniform(1,50) ---> b/w 1 to 50 float


Python itertools

# Itertools
# Iterate over data structures that can be stepped over using a for-loop.
# Such data structures are also known as iterables.
# Itertools are more readable, fast, memory-efficient
# provides various functions that work on iterators to produce complex iterators.
# Infinite iterators: count, cycle, repeat
# Finite iterators: chain, compress, dropwhile

import itertools

# 1) Count - Infinite iterator
# print the first four even numbers
result = itertools.count(start = 0, step = 2)
for number in result:
if number < 8:
print (number)
else:
break

# Output:
# 0
# 2
# 4
# 6

# 2) Cycle - Infinite iterator
result = itertools.cycle('test')
counter = 0
for each in result:
if counter > 10:
break
counter += 1
print(each)

# Output:
# t
# e
# s
# t
# t
# e
# s
# t
# t
# e
# s

# 3) Repeat - Infinite iterator
result = itertools.repeat('test', 2)
for each in result:
print(each)

# Output:
# test
# test

# 1) Chain - Finite iterator
l1 = ['aaa', 'bbb', 'ccc']
l2 = ['ddd', 'eee', 'fff']
result = itertools.chain(l1, l2)
for each in result:
print(each)

# Output:
# aaa
# bbb
# ccc
# ddd
# eee
# fff

# 2) Compress - Finite iterator
l1 = ['aaa', 'bbb', 'ccc']
l2 = [True, False, False]
result = itertools.compress(l1, l2)
for each in result:
print(each)

# Output:
# aaa

# 3) Dropwhile - Finite iterator
# keeps on dropping values from the iterable until it encounters the first element
def is_positive(n):
return n > 0
value_list =[5, 6, -8, -4, 2]
result = list(itertools.dropwhile(is_positive, value_list))
print(result)

# Output:
# [-8, -4, 2]

# 4) groupby
ll = [("aaa", 1), ("aaa", 2), ("bbb", 3), ("bbb", 4)]
# Key function
key_func = lambda y: y[0]

for key, group in itertools.groupby(ll, key_func):
print(key + " :", list(group))

# Output:
# aaa : [('aaa', 1), ('aaa', 2)]
# bbb : [('bbb', 3), ('bbb', 4)]


Pandas Tutorial 2 (data formats, db, files, pickle)

import numpy as np
import pandas as pd

# nrows - just read first N rows
# usecols - read only those columns
df = pd.read_csv("input.csv", index_col="Id")
print(df.head(10))
# Name Age City
# Id
# 1 John 30 Bangalore
# 2 Doe 25 Chennai
# 3 Mary 22 Hyderabad
# 4 Tom 35 Mumbai

df = pd.read_csv("input.csv", index_col="Id", nrows=2, usecols=['Id', 'Name'])
print(df.head(10))
# Name
# Id
# 1 John
# 2 Doe

# Serialize & save to disk
df = pd.read_csv("input.csv", index_col="Id")
df.to_pickle('data_frame.pickle')

df1 = pd.read_pickle('data_frame.pickle')
print(df1.head(10))
# Name Age City
# Id
# 1 John 30 Bangalore
# 2 Doe 25 Chennai
# 3 Mary 22 Hyderabad
# 4 Tom 35 Mumbai

# Dataframe To Table
table_dtype = {
'name': VARCHAR(),
'city': VARCHAR(),
'state': VARCHAR(),
'pincode': INTEGER()
}
df1.to_sql(con=db_conn, name=table_name, dtype=table_dtype,
if_exists='replace', index=False)


# From Table to Dataframe
raw_data_sql = f""" select id, name, city, state
from event_raw_data
"""
df1 = pd.read_sql(raw_data_sql, con=db_conn, index_col="id")





Pandas Tutorial 1

import numpy as np
import pandas as pd

la = np.random.rand(3)
dd = pd.Series(la)
print(dd.head(10))
# 0 0.145517
# 1 0.718904
# 2 0.448772
# dtype: float64

ll = range(3)
dd = pd.Series(ll)
print(dd.head(10))
# 0 0
# 1 1
# 2 2
# dtype: int64

dd = pd.Series(ll, index=['First', 'Second', 'Third'])
print(dd.head(10))
# First 0
# Second 1
# Third 2
# dtype: int64

print(dd["First"]) # 0
print(dd[0]) # 0

nd = np.random.rand(3,2)
df = pd.DataFrame(nd)
df.columns = ["First", "Second"]
print(df.head(10))
# First Second
# 0 0.567850 0.275874
# 1 0.839382 0.792727
# 2 0.445246 0.537417

print(df.loc[0])
# First 0.567850
# Second 0.275874
# Name: 0, dtype: float64

print(df.iloc[0,1]) #0.275874
print(df.loc[0, "Second"]) #0.275874





May 18, 2020

Spark 1 vs Spark 2

 
Spark 1.xSpark 2.x
Spark Context is the entry pointSpark Session is the entry point
We need to create separately sql context, hive context if we have only SparkContext.Spark Session is enough
Spark 1.x uses compilers which uses of several function calls and CPU cycles, because of which so much unnecessary work spent on CPU cycles.Spark 2.x uses performance enhanced Tungsten engine
1X10X times faster than Spark 1.X
Spark Streaming (uses RDD batch concept)Structured Streaming (uses DataFrames/DataSet APIs)
Unified Dataset and DataFrame APIs (Dataset has more type safety, not available in Python). Now Dataframe is just an alias for Dataset of Row
Many machine learning algorithms like Gaussian Mixture Model, MaxAbsScaler, Bisecting K-Means clustering feature transformer are added to DataFrame based API and many ML algorithms added to PySpark and SparkR also.
RDD based API is going into maintenance modeDataFrame based has become the primary API now

 

Spark Intro

  • Spark
    • Apache is unified analytics engine and large-scale data processing
    • Latest version 2.4.5 Feb 2020
    • Speed
      • Apache Spark achieves high performance for both batch and streaming using state of the art DAG scheduler, query optimizer and physical execution engine
      • Runs 100X times faster than Hadoop
    • Ease of use
      • Write applications quickly in Java, Scala, Python, R and SQL
    • Generality
      • Spark SQL
      • Spark Streaming
      • MLib
      • GraphX
    • Runs every where
      • Spark runs on Hadoop, Apache Mesos, Kubernetes, standalone, or in the cloud. It can access diverse data sources.
      • You can run Spark using its standalone cluster mode, on EC2, on Hadoop YARN, on Mesos, or on Kubernetes. 
      • Access data in HDFS, Alluxio, Apache Cassandra, Apache HBase, Apache Hive, and hundreds of other data sources.

PySpark Streaming Vs Structured Streaming

Spark StreamingStructured Streaming
Spark 1.XIntroduced in Spark 2.X
Separate library in Spark to process continuously flowing Streaming dataBuilt on Spark SQL library
Uses DStreams API powered by Spark RDDs. It works on micro batches (each batch represent RDDs)This model is based on Dataframe and Dataset APIs. No batch concept here.
DStrams provide us data divided into chunks as RDDs received from source of Streaming to be processed and outputs batches of processed dataHere we keep adding stream data to DataFrame (Unbounded table)
Not easy to applyWe can easily apply SQL query or scala operations on streaming data
Result of Unbounded table/dataframe is based on mode of your operations Complete, Append, Update
RDDDataframe/Dataset are more optimized & less time consuming, easy to understand. Apply aggregations
No such option called event-time, only works with timestamp when the data is received. Based on the ingestion timestamp, Spark Streaming puts the data in a batch even if the event is generated early and belonged to the earlier batch, which may result in less accurate information as it is equal to the data loss(Windowing) With event-time handling of late data, Structured Streaming outweighs Spark Streaming.


May 15, 2020

Python Super Tutorial 2

super() 
  • will allow us not to call explicitly
  • Enable multiple inheritance

class Computer():
    def __init__(self, computer, ram, storage):
        self.computer = computer
        self.ram = ram
        self.storage = storage

# Class Mobile inherits Computer

class Mobile(Computer):
    def __init__(self, computer, ram, storage, model):
        super().__init__(computer, ram, storage)
        self.model = model


Apple = Mobile('Apple', 2, 64, 'iPhone X')
print('The mobile is:', Apple.computer)
print('The RAM is:', Apple.ram)
print('The storage is:', Apple.storage)
print('The model is:', Apple.model)


May 13, 2020

Python Unittest

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
         self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
         self.assertTrue('FOO'.isupper())
         self.assertFalse('Foo'.isupper())

    def test_split(self):
         s = 'hello world'
         self.assertEqual(s.split(), ['hello', 'world'])
         # check that s.split fails when the separator is not a string
         with self.assertRaises(TypeError):
              s.split(2)

if __name__ == '__main__':
    unittest.main()



May 4, 2020

python collections Counter

from collections import Counter

listA = [40, 10, 20, 30, 10]
c = Counter(listA)

print(c)  #Counter({10: 2, 40: 1, 20: 1, 30: 1})


python collections OrderedDict

from collections import namedtuple
from collections import OrderedDict, defaultdict

# count no elements
# print dict in same order
d = OrderedDict() 
n = 5
ll = ['aaa', 'bbb', 'ccc', 'aaa']
for i in ll:
    d.setdefault(i, 0)  #it initialises to, else index error may come first time 
    d[i]+= 1

print(len(d))            # 3 
print(*d.values())   # 2 1 1
print(d)              #OrderedDict([('aaa', 2), ('bbb', 1), ('ccc', 1)])


Apr 29, 2020

Python Collections Counter, Defaultdict

1) Counter

from collections import Counter
myList = [10,10,20,30,4,5,3,2,3,4,2,1,2,30]
print(Counter(myList))
# Counter({2: 3, 10: 2, 30: 2, 4: 2, 3: 2, 20: 1, 5: 1, 1: 1})

print(Counter(myList).items())
# dict_items([(10, 2), (20, 1), (30, 2), (4, 2), (5, 1), (3, 2), (2, 3), (1, 1)])

print(Counter(myList).keys())
# dict_keys([10, 20, 30, 4, 5, 3, 2, 1])

print(Counter(myList).values())
# dict_values([2, 1, 2, 2, 1, 2, 3, 1])

2) defaultdict

from collections import defaultdict

a)
d = defaultdict(list)

# Even if key not exists it defaults to list/int
d['python'].append("awesome")
d['others'].append("not relevant")
d['python'].append("language")
d['test']

for i in d.items():
    print(i)

# O/P:
# ('python', ['awesome', 'language'])
# ('others', ['not relevant'])
# ('test', [])


b)
d = defaultdict(int)
d['without_val']
d['with_val'] = 100
for i in d.items():
    print(i)

# O/P:
# ('without_val', 0)
# ('with_val', 100)

c)
demo = defaultdict(int)
print(demo[300]) # 0


Apr 28, 2020

Git How to Revert a commit and push to new branch

Say you committed files to a wrong branch
How to revert and push new branch?

Existing Branch
git log --oneline
check for commit id: E.g., e0db5f7
git revert <commit_id>
git push

Create a new branch
git checkout -b new_branch
git checkout <commit_id> .    #this will copy commit files to local branch
git commit
git push


Apr 25, 2020

How to Configure Zookeeper and Kafka?

How to Configure Kafka?

# Download Kafka

# Kafka ENV  
  • export KAFKA_HOME=$HOME/Workspace/prabhath/personal/kafka_2.12-2.5.0 
  • export PATH=$KAFKA_HOME/bin:$PATH
Zookeeper config:
  • bin/zookeeper-server-start.sh
  • bin/zookeeper-server-stop.sh
  • config/zookeeper.properties --> Default port: 2181, dataDir: /tmp/zookeeper

Kafka Config:
  • bin/kafka-server-start.sh
  • bin/kafka-server-stop.sh
  • config/server.properties --> Default port: 9092

1) Start zookeeper
  • zookeeper-server-start.sh $KAFKA_HOME/config/zookeeper.properties

2) Start Kafka server
  • kafka-server-start.sh $KAFKA_HOME/config/server.properties

3) Create a Kafka topic
  • kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic first_kafka_topic
  • kafka-topics.sh --list --zookeeper localhost:2181 consumer_offsets
  • It lists first_kafka_topic

4) Start Kafka Producer
  • kafka-console-producer.sh --broker-list localhost:9092 --topic first_kafka_topic
  • <start typing data>

5) Start Kafka Consumer
  • kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic first_kafka_topic --from-beginning


Apr 19, 2020

Python collections namedtuple

import collections

fields = ['OBJECTID', 'Identifier', 'Occurrence_Date', 'Day_of_Week', 'Occurrence_Month', 'Occurrence_Day', 'Occurrence_Year', 'Occurrence_Hour', 'CompStat_Month', 'CompStat_Day', 'CompStat_Year', 'Offense', 'Offense_Classification', 'Sector', 'Precinct', 'Borough', 'Jurisdiction', 'XCoordinate', 'YCoordinate', 'Location_1']

Crime = collections.namedtuple('Crime', fields)

row1_value = ['1', 'f070032d', '09/06/1940 07:30:00 PM', 'Friday', 'Sep', '6', '1940', '19', '9', '7', '2010', 'BURGLARY', 'FELONY', 'D', '66', 'BROOKLYN', 'N.Y. POLICE DEPT', '987478', '166141', '(40.6227027620001, -73.9883732929999)']

row1_obj = Crime(*row1_value)

print(row1_obj)


Output:
Crime(OBJECTID='1', Identifier='f070032d', Occurrence_Date='09/06/1940 07:30:0
0 PM', Day_of_Week='Friday', Occurrence_Month='Sep', Occurrence_Day='6', Occur
rence_Year='1940', Occurrence_Hour='19', CompStat_Month='9', CompStat_Day='7',
 CompStat_Year='2010', Offense='BURGLARY', Offense_Classification='FELONY', Se
ctor='D', Precinct='66', Borough='BROOKLYN', Jurisdiction='N.Y. POLICE DEPT', 
XCoordinate='987478', YCoordinate='166141', Location_1='(40.6227027620001, -73
.9883732929999)')

Mar 29, 2020

Python Puzzle Remove even numbers

Python Puzzle Remove even numbers


# Wrong approach (incorrect - using For loop)
def removeEven(List):
    print(id(List)) # 139909029905664
    for each in List:
       i f each % 2 == 0:
          List.remove(each)


myList = [152, 168, 154, 32, -55, 81, 146, -34, -124, -9, 4, -31, -131, -86, -190, -38]
print(id(myList)) # 139909029905664
print(myList) # [1, 2, 4, 5, 10, 6, 3]
removeEven(myList)
print(myList) # [168, 32, -55, 81, -34, -9, -31, -131, -190]
# Wrong as when element gets deleted, index goes down

print('-' * 60)

# Correct approach (using While loop)
def removeEvenNew(List):
    print(id(List))
    i = 0
    while i < len(List):
      if List[i] % 2 == 0:
          List.remove(List[i])
      else:
          i += 1 


myList = [152, 168, 154, 32, -55, 81, 146, -34, -124, -9, 4, -31, -131, -86, -190, -38]
print(id(myList))
print(myList) # 
removeEvenNew(myList)
print(myList) # [-55, 81, -9, -31, -131]


Output:
140407115793920
[152, 168, 154, 32, -55, 81, 146, -34, -124, -9, 4, -31, -131, -86, -190, -38]
140407115793920
[168, 32, -55, 81, -34, -9, -31, -131, -190]
------------------------------------------------------------
140407114861888
[152, 168, 154, 32, -55, 81, 146, -34, -124, -9, 4, -31, -131, -86, -190, -38]
140407114861888
[-55, 81, -9, -31, -131]


Python Lists Advanced

ll = [10, 20, 30, 40, 50]

# insert, remove, pop
ll.remove(20) #[10, 30, 40, 50]
ll.pop() # #[10, 30, 40]

ll = [1, 3, 5, 'seven']
ll.insert(0, 2) 
print(ll) # [2, 1, 3, 5, 'seven']

ll.pop(2) # pops 2nd index element
print(ll) # [2, 1, 5, 'seven']

ll.pop() # takes out last item
print(ll) # [2, 1, 5]

# Slice
gg = [1, 3, 5, 'seven', 'eight', 'nine', [10, 20,]]
print(gg[1:4])  # [3, 5, 'seven']
print(gg[3:])  # ['seven', 'eight', 'nine', [10, 20]]
print(gg[:3])  # [1, 3, 5]
print(gg[:])  # [1, 3, 5, 'seven', 'eight', 'nine', [10, 20]]

print(gg[-1:]) # [[10, 20]]
print(gg[:-1]) # [1, 3, 5, 'seven', 'eight', 'nine']

print(gg[-3:-1]) # ['eight', 'nine']

# list[start:stop:step]
print(gg[0:7:2]) # [1, 5, 'eight', [10, 20]]

ff = [1, 3, 5, 'seven', 'eight', 'nine']
print(ff) #[1, 3, 5, 'seven', 'eight', 'nine']
ff[2:2] = ['test']
print(ff) # [1, 3, 'test', 5, 'seven', 'eight', 'nine']
ff[1:3] = []
print(ff) # [1, 5, 'seven', 'eight', 'nine']

del ff[::2] # Delete even numbred indeces
print(ff)  # [5, 'eight']

# Concatenate
kk = [1, 2, 3, 4] # [1, 2, 3, 4]
kk += 'ab' # since string, it takes as two elements
print(kk) # [1, 2, 3, 4, 'a', 'b']

kk += ['c', 'd']
print(kk) # [1, 2, 3, 4, 'a', 'b', 'c', 'd']

kk.extend(['e', 'f'])
print(kk) # [1, 2, 3, 4, 'a', 'b', 'c', 'd', 'e', 'f']

# List Vs Array
# Array has homogenous elements
# Python arrays are just wrappers for C language
import array
# type: 'd' (float), initializer list: [1, 2, 3]
newArray = array.array('i', [1, 2, 3])
print(newArray) # array('i', [1, 2, 3])



Python AsyncIO Example

import asyncio
import aiohttp
import time

async def crawl_one_url(url, session):
    get_request = session.get(url)
    print(url)
    res = await get_request
    txt = await res.text()
    get_request.close()
    return txt


async def crawl_urls(urls_to_crawl):
    session = aiohttp.ClientSession()

    work_to_do = list()
    for url in urls_to_crawl:
        work_to_do.append(crawl_one_url(url, session))
    print(*work_to_do)
    res = await asyncio.gather(*work_to_do)
    # print(res)
    await session.close()
    return res


def main():
    t0 = time.time()
    urls_to_crawl = list()
    urls_to_crawl.append('http://blog.prabhathkota.com/search/label/python')
    urls_to_crawl.append('http://blog.prabhathkota.com/search/label/perl')
    urls_to_crawl.append('http://blog.prabhathkota.com/search/label/unix')
    urls_to_crawl.append('http://blog.prabhathkota.com/search/label/aws')
    urls_to_crawl.append('http://blog.prabhathkota.com/search/label/java')
    asyncio.run(crawl_urls(urls_to_crawl))
    elapsed = time.time() - t0
    print(f"{len(urls_to_crawl)} URLS downloaded in {elapsed:.2f}")


if __name__ == '__main__':
    main()


Output:
<coroutine object crawl_one_url at 0x7f5fe36181c0> <coroutine object crawl_on
e_url at 0x7f5fe3618240> <coroutine object crawl_one_url at 0x7f5fe36182c0> <
coroutine object crawl_one_url at 0x7f5fe3618340> <coroutine object crawl_one
_url at 0x7f5fe36183c0>
http://blog.prabhathkota.com/search/label/python
http://blog.prabhathkota.com/search/label/perl
http://blog.prabhathkota.com/search/label/unix
http://blog.prabhathkota.com/search/label/aws
http://blog.prabhathkota.com/search/label/java
5 URLS downloaded in 0.48

Python Generator Send & Recieve

#############################################
# Yield returns a generator object
# use next() to access the return value
# If you attempt to invoke next() on a generator object that had already produced (yielded) all its values, it will throw StopIteration exception
# We can pass data to a generator function using the send() method defined
# Use generators to generate values and coroutines to consume values. Generator functions receive values are called coroutines. 
#############################################


############# Test1 (send & receive) ############ 
def test_yield_send():
    while True:
        item = yield
        print(f'Received item: {item}')

if __name__ == '__main__':
    gen = test_yield_send()
    print(gen)  #<generator object test_yield at 0x7ff2f0510c80>
    next(gen)
    gen.send(100)

Output:
<generator object test_yield_send at 0x7ff700b45c80>
Received item: 100

############ Test2 (send & receive) ############ 
def generate_num():
    i = 0
    while True:
        i += 1
        t = (yield i)
        print(t)


if __name__ == "__main__":
    gen = generate_num()

    item = gen.send(None)
    print("First received " + str(item))

    for i in range(0, 5):
        item = gen.send(100 + i)
        print("Other received " + str(item))


Output:
First received 1
100
Other received 2
101
Other received 3
102
Other received 4
103
Other received 5
104
Other received 6

Python Generator yield

"""
Yield returns a generator object
use next() to access the return value
If you attempt to invoke next() on a generator object that had already produced (yielded) all its values, it will throw StopIteration exception
It can yield multiple items as well (as shown in second example)
"""

################# Test1 #################
def test_yield():
    yield 'hi'

if __name__ == '__main__':
  a = test_yield()
  print(a)  #<generator object test_yield at 0x7ff2f0510c80>
  val = next(a)
  print(val) # hi

Output:
hi

################# Test2 #################
# You can yield multiple values
def test_yield(name):
    yield 'hi'
    yield name

if __name__ == '__main__':
  a = test_yield('John')
  print(a)  #<generator object test_yield at 0x7ff2f0510c85>
  for item in a:
    print(item)

Output:
<generator object test_yield at 0x7f93f8066c80>
hi
John

Mar 27, 2020

Python multiprocessing spawn

Fork Vs Spawn
Fork child inherit all resources from the parent process
Fork is the default method for multi-processing
Spawn child doesn't inherit any resources from the parent process other than those required to execute the specified callable target.
Spawning a process is slower (because it re-imports) than forking a process

Spawn
Spawn is essentially a combination of fork followed by an exec system call
When a child process is spawned, anything imported at the module level in the (above )__main__ module of the parent process gets reimported in the child
Anything below __main__ will not be copied

test1.py
#########
Inside test1

test1.py
#########
Inside test2

spawn_test.py
############
from multiprocessing import Process
import test1
import test2

def process_task():
    print("I am inside child process")


if __name__ == '__main__':

    # Change the method to 'spawn' and verify
    # that the modules are reimported in the child process
    ### Spawn
    multiprocessing.set_start_method('spawn') 
    process = Process(target=process_task)
    process.start()
    process.join()
    print("I am inside parent process")


# Child reimports the test1, test2 module again...

Output:
Inside test1
Inside test2
Inside test1
Inside test2
I am child process
I am parent process

Python multiprocessing fork

Fork
# When we fork, the entire Python process is duplicated in memory including the Python interpreter, code, libraries, current stack, etc.
# This creates a new copy of the python interpreter.
# Fork creates two python interpreters each with its own GIL.
# Fork is faster than Spawn (Fork child inherit all resources from the parent process, Spawn re-imports all above main() method)
# Fork is the default method for multi-processing
 
#Disadvantages of Fork
# It won't work on windows
# When child shares parent libraries, values, data-structures, if a lock acquired by parent, child ends up waiting for that lock ever
# Very hard to debug when you import a third-party module/library that uses threads behind the scenes
# Fork and Multi-threading won't go well


from multiprocessing import Process
import multiprocessing
import os

file_desc = None

def process_task1():
    # write to the file in child process
    file_desc.write(f"\nWritten by child process with id {os.getpid()}")
    file_desc.flush()


if __name__ == '__main__':
    # create a file in the parent process
    file_desc = open("sample.txt", "w")
    file_desc.write(f"\nWritten by parent process with id {os.getpid()}")
    file_desc.flush()

    # Fork is default method to create a process
    # multiprocessing.set_start_method('fork')

    p = Process(target=process_task1)
    p.start()
    p.join()
    file_desc.close()

    file_des = open("sample.txt", "r")
    print(file_des.read())

    os.remove("sample.txt")


Output:
Written by parent process with id 288
Written by child process with id 294


Mar 25, 2020

python how to overcome GIL

Ref: https://realpython.com/python-gil/

# single_threaded.py
import time
from threading import Thread

COUNT = 50000000

def countdown(n):
    while n>0:
        n -= 1

start = time.time()
countdown(COUNT)
end = time.time()

print('Time taken in seconds -', end - start)
#Time taken in seconds - 6.20024037361145


# multi_threaded.py
import time
from threading import Thread

COUNT = 50000000

def countdown(n):
    while n>0:
        n -= 1

t1 = Thread(target=countdown, args=(COUNT//2,))
t2 = Thread(target=countdown, args=(COUNT//2,))

start = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end = time.time()

print('Time taken in seconds -', end - start)
# Time taken in seconds - 6.924342632293701
# No improvement here due to GIL & due to sharing of lock & releases


# multi_processing.py
# Uses cores instead of threads
from multiprocessing import Pool
import time

COUNT = 50000000
def countdown(n):
    while n>0:
        n -= 1

if __name__ == '__main__':
    pool = Pool(processes=2)
    start = time.time()
    r1 = pool.apply_async(countdown, [COUNT//2])
    r2 = pool.apply_async(countdown, [COUNT//2])
    pool.close()
    pool.join()
    end = time.time()
    print('Time taken in seconds -', end - start)
    #Time taken in seconds - 4.060242414474487


Python GIL, lock, mutex, semaphore

Lock
#####
A lock allows only one thread to enter the part that's locked and the lock is not shared with any other processes.

Mutex
#######
Mutex as the name means mutual exclusion.
A mutex is used to guard shared data such as a list, dict.
A mutex allows only a single thread to access a resource or critical section.
A mutex is the same as a lock but it can be system wide (shared by multiple processes).
Same thread can acquire and release lock
Owned by thread

Semaphore
##########
Semaphore, is used for limiting access to a collection of resources.
A semaphore does the same as a mutex but allows x number of threads to enter, this can be used
E.g., to limit the number of cpu, io or ram intensive tasks running at the same time.
      Pool of DB connections to be handed out to requesting threads
Different threads can call acquire and release on the semaphore
No ownership

GIL
####
Ref: https://realpython.com/python-gil/

The Python interpreter can only execute a single thread at a time
If your machine has one or hundred processors, the Python interpreter is only able to run a single thread at a time using a single processor.
Two threads on a machine with two available processors can't be executed in parallel each running on a single CPU.
This lock is known as the Global Interpreter Lock using CPython (to avoid deadlocks)
Python implementations which overcome the GIL altogether. E.g., Jython, IronPython and pypy-stm.

Python uses reference counting for memory management. (unlike garbage collection in other languages)
It means that objects created in Python have a reference count variable that keeps track of the number of references that point to the object. When this count reaches zero, the memory occupied by the object is released.

This reference count variable can be kept safe by adding locks to all data structures that are shared across threads so that they are not modified inconsistently.

But adding a lock to each object or groups of objects means multiple locks will exist which can cause another problem—Deadlocks (deadlocks can only happen if there is more than one lock). Another side effect would be decreased performance caused by the repeated acquisition and release of locks.

The GIL is a single lock on the interpreter itself which adds a rule that execution of any Python bytecode requires acquiring the interpreter lock. This prevents deadlocks (as there is only one lock) and doesn’t introduce much performance overhead. But it effectively makes any CPU-bound Python program single-threaded.


Python sum using multiprocessing pool

#####
# Read all text files & calculate Sum of all numbers in txt files

# Threading/input1.txt
# 100
# 200
# 300
# 400
# 500

# Threading/input2.txt
# 600
# 700
# 800
# 900
# 1000
#####

import glob
import multiprocessing

def get_txt_files():
    my_files = []
    for each in glob.glob('Threading/*.txt'):
        my_files.append(each)
    return my_files

def calculate_sum(input_file):
    with open(input_file) as fh:
      return sum(map(int, fh.readlines()))


if __name__ == '__main__':
    my_files = get_txt_files()
    #['Threading/input1.txt', 'Threading/input2.txt']
    print(my_files)

    # sys.exit()
    pool = multiprocessing.Pool(processes=2)
    results = pool.map(calculate_sum, my_files)
    print(f'Sum of individual files: {results}')
    total_sum = sum(results)
    print(f'Total Sum: {total_sum}')


Output:
######
['Threading/input1.txt', 'Threading/input2.txt']
Sum of individual files: [1500, 4000]
Total Sum: 5500

Python thread lock release

#####
#If you don't use lock, sum will be not printed as 500000
#If you use lock, you get the sum as expected 500000 (5 threadsList, 100000 each)
#####

from threading import Thread
from threading import Lock
import sys

class CounterClass:
    def __init__(self):
        self.count = 0
        self.lock = Lock()

    def increment(self):
        for _ in range(100000):
            self.lock.acquire()
            self.count += 1
            self.lock.release()


if __name__ == "__main__":
   
    # Sets the thread switch interval
    sys.setswitchinterval(0.005)

    numThreads = 5
    threadsList = []
    counterObj = CounterClass()

    for i in range(0, numThreads):
        threadsList.append(Thread(target=counterObj.increment))

    for i in range(0, numThreads):
        threadsList[i].start()

    for i in range(0, numThreads):
        threadsList[i].join()

    if counterObj.count != 500000:
        print(" count = {0}".format(counterObj.count))
    else:
        print(" count = 500000")


Mar 24, 2020

Python Palindrome

Using Recursion

def isPalindrome(testVariable):
  print(testVariable)
  if len(testVariable) <= 1:
    return True
  
  length = len(testVariable)
  if testVariable[0] == testVariable[length-1]:
      return isPalindrome(testVariable[1:length-1])

  return False

a = isPalindrome('MADAM')
print(a)  ## True

Using normal way

def isPalindrome(testVariable):
  if testVariable == testVariable[::-1]:
    return True
  return False

a = isPalindrome('MADAM')
print(a)  ## True

Python recursion

def factorial(num):
    if num == 1:
       return num
    else:
       return num * factorial(num-1)

target = factorial(5)
print(target)  ## 120


def square(num):
    if num == 1:
      return num
    else:
      return square(num-1) + 2*num - 1

target = square(6)

print(target)  ## 36

python reverse string

def reverse_fuc(input_str):
    reverse = ''
    length = len(input_str) - 1
    while length >= 0:
       reverse = reverse + input_str[length]
       length = length - 1
return reverse

target = reverse_fuc('prabhath')
print(target)  #htahbarp