Module 2 - Day 3

Login to Lab using your credentials. There is a notebook with name 2-3.ipynb already created for you. Open that and use it for today’s training.

Shut down all previous notebooks.

More about functions

def square(x):
    return x*x
square
<function __main__.square(x)>
x, y = 10, 20
x
10
y
20
x()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[6], line 1
----> 1 x()

TypeError: 'int' object is not callable
x
10
square
<function __main__.square(x)>
z = x
func = square # functions behave as other variables in python
              # you can assign it to other variable names
              # also you can pass it like argument ..just as other variable!
square(5)
25
func(5)
25
def square(x):
    return x**2

def sumofsquares(x, y, z):
    return x**2 + y**2 + z**2

def sumof(x, y, z, fun):
    return fun(x) + fun(y) + fun(z)

sumof(10, 20, 30, square)
1400
sumofsquares(10, 20, 30)
1400
def cube(x):
    return x**3

def sumof(x, y, z, f):
    return f(x) + f(y) + f(z)
    
sumof(10, 20, 30, cube)
36000
import math

sumof(math.pi, math.pi/2, math.pi/4, math.sin) # sum of sin!
1.7071067811865477
words = "one two three four five six seven eight nine ten".split()
words
['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']
max("ABCDEF")
'F'
max([1, 2, 3, 4, 5])
5
max(['a','z','b','c','x'])
'z'
max(words) # this is by dictionary order
'two'
"a" < "b"
True
def mymax(items):
    guess = items[0]

    for item in items[1:]:
        if item > guess:
            guess = item

    return guess
mymax(words)
'two'
def mymaxlen(items):
    guess = items[0]

    for item in items[1:]:
        if len(item) > len(guess):
            guess = item

    return guess
mymaxlen(words) # a word with max length
'three'
[1, 1, 2, 2, 3, 5, 5, 5]
[1, 1, 2, 2, 3, 5, 5, 5]
def mymaxlen_(items):
    guess = items[0]

    for item in items[1:]:
        if len(item) >= len(guess):
            guess = item

    return guess
mymaxlen_(words)
'eight'
max(words, key=len)
'three'
max(range(5))
4
def identity(x):
    return x
    
def mymax_(items, comp_func=identity):
    guess = items[0]

    for item in items[1:]:
        if comp_func(item) > comp_func(guess):
            guess = item

    return guess
mymax_([121, 2334, 23, 45, 2])
2334
mymax_(words) # dictinary order
'two'
mymax_(words, comp_func=len) # a word with max len
'three'
max(words, key=len) # nemed argument
'three'
sorted(words) # sort by dictionary order... 
['eight', 'five', 'four', 'nine', 'one', 'seven', 'six', 'ten', 'three', 'two']
sorted(words, key=len)
['one', 'two', 'six', 'ten', 'four', 'five', 'nine', 'three', 'seven', 'eight']
words
['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']
min(words, key=len)
'one'
records = [
    ("TATA", 200.0, 5.5),
    ("INFY", 2000.0, -5),
    ("RELIANCE", 1505.5, 50.0),
    ("HCL", 1200, 70.5)
  ]
l = [1,
     2,
     3,
     4]
l = [1,2,3,4]
records = [
    ("TATA", 200.0, 5.5), # this is one record
    ("INFY", 2000.0, -5),
    ("RELIANCE", 1505.5, 50.0),
    ("HCL", 1200, 70.5)
  ] # (name, value, gain)
max(records) # 
('TATA', 200.0, 5.5)
def get_price(record):
    return record[1]

max(records, key=get_price)
('INFY', 2000.0, -5)
min(records, key=get_price)
('TATA', 200.0, 5.5)
def get_gain(record):
    return record[2]

max(records, key=get_gain)
('HCL', 1200, 70.5)
min(records, key=get_gain)
('INFY', 2000.0, -5)
sorted(records, key=get_price)
[('TATA', 200.0, 5.5),
 ('HCL', 1200, 70.5),
 ('RELIANCE', 1505.5, 50.0),
 ('INFY', 2000.0, -5)]
sorted(records, key=get_price, reverse=True) # key and reverse are named only arguments
[('INFY', 2000.0, -5),
 ('RELIANCE', 1505.5, 50.0),
 ('HCL', 1200, 70.5),
 ('TATA', 200.0, 5.5)]
def write_tabular(data, filename):
    with open(filename,"w") as f:
        for row in data:
            line = ",".join([str(item) for item in row])
            f.write(line)
            f.write("\n")
write_tabular(records, "stocks.csv")
!python cat.py stocks.csv
TATA,200.0,5.5
INFY,2000.0,-5
RELIANCE,1505.5,50.0
HCL,1200,70.5
def get_price_(line):
    fields = line.strip().split(",")
    return float(fields[1])

with open("stocks.csv") as f: # f is filhandle... for loop on file handle will let me iterate over lines!
    print(max(f, key=get_price_))
INFY,2000.0,-5
import os
files = os.listdir()
files
['add.py',
 'printargs.py',
 '.ipynb_checkpoints',
 'testing.py',
 'lab.qmd',
 '2-2.ipynb',
 '1-3.ipynb',
 'test.py',
 '__pycache__',
 'index.qmd',
 '2-3.ipynb',
 'rev.py',
 'about.qmd',
 'hello1.py',
 'stocks.csv',
 'nums.txt',
 '_site',
 '.gitignore',
 'mysum.py',
 '1-nurturing-session.ipynb',
 'styles.css',
 'tabular1.csv',
 'printfirstarg.py',
 '1-1.ipynb',
 'salary.csv',
 'test.txt',
 '.quarto',
 'args.py',
 'syllabus.qmd',
 '1-2.ipynb',
 'testfolder',
 'csvdata.csv',
 'schedule.qmd',
 '1-5.ipynb',
 '_quarto.yml',
 'tabular.csv',
 'hello.py',
 '.git',
 'cat.py',
 'welcome.py',
 'out.txt',
 'README.md',
 'foo.py',
 'salary.txt',
 'n.txt',
 '2-1.ipynb',
 'zen.txt',
 '1-4.ipynb']
os.path.getsize("stocks.csv")
65
max(files, key=os.path.getsize) # find the file largest size in this folder
'1-1.ipynb'
max("one two three four") # this will work on character
'w'
max("one two three four".split()) # this will work on each word
'two'
def get_price_(line):
    fields = line.strip().split(",")
    return float(fields[1])

These are the steps for this function execution
"HCL,1200,70.5\n".strip()
"HCL,1200,70.5".split(",")
["HCL","1200","70.5"]
fields = ["HCL","1200","70.5"]
float(fields[1])
"   dsfsdf dsfasdf  asdfasd  ".strip()
'dsfsdf dsfasdf  asdfasd'
"one ksjdlksa klasjd   \n".strip()
'one ksjdlksa klasjd'
"\none\n".strip() # strip removes trailing white spaces from both ends
'one'
def foobar(x):
    print(type(x))
    return x
foobar(4)
<class 'int'>
4
max(words, key=foobar)
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
<class 'str'>
'two'
max([1231, 23, 234, 234], key=foobar)
<class 'int'>
<class 'int'>
<class 'int'>
<class 'int'>
1231
max(records, key=foobar)
<class 'tuple'>
<class 'tuple'>
<class 'tuple'>
<class 'tuple'>
('TATA', 200.0, 5.5)

Classes

"hello".upper() # this keeps the original data secure and allows user to play with data using allowed methods!
'HELLO'
"hello"[2] = "s"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[91], line 1
----> 1 "hello"[2] = "s"

TypeError: 'str' object does not support item assignment

Why classes?

  • Faciity to encapsulate the data (secure the data)
  • controlled way to modify the data
                  Light
            +---------------+
            |               |<---------switch_on
            |    on=True    |
            |               |<---------switch_off
            +---------------+
               |
               |
               +------<------- is_on
class Light:

    def __init__(self): # magic function!
        self.status = False # by default light off

    def switch_on(self):
        self.status = True

    def switch_off(self):
        self.status = False

    def is_on(self):
        return self.status
Light # a class
__main__.Light
l1 = Light() # here __init__ method will be called
type(l1)
__main__.Light
type(1)
int
type("sajdkjashdjk")
str
type([1, 2, 3, 4])
list
type(l1)
__main__.Light
l1.is_on()
False
l1.switch_on()
l1.is_on()
True
l1
<__main__.Light at 0x758cefcb51e0>
ones = [1, 2, 3, 4]
ones
[1, 2, 3, 4]
class Light:

    def __init__(self): # magic function!
        self.status = False # by default light off

    def switch_on(self):
        self.status = True

    def switch_off(self):
        self.status = False

    def is_on(self):
        return self.status

    def __repr__(self): # interpreter will show whatever we return here
        return f"Light<{self.status}>"

    def __str__(self): # will be used if we print the object or call str
        if self.status:
            return "Light - on"
        else:
            return "Light - off"
L = Light()
L
Light<False>
print(L)
Light - off
str(L)
'Light - off'
L
Light<False>
L.switch_on()
L
Light<True>
L1 = Light()
L2 = Light()
L1
Light<False>
L2
Light<False>
L1.switch_on()
L1
Light<True>
L2
Light<False>
lights = [Light(), Light(), Light(), Light()]
lights
[Light<False>, Light<False>, Light<False>, Light<False>]
for i, light in enumerate(lights):
    if i%2 == 0:
        light.switch_on()
lights
[Light<True>, Light<False>, Light<True>, Light<False>]

Example Bank Account

class BankAccount:

    def __init__(self, name, balance):
        self.name = name
        self.balance = balance

    def get_balance(self):
        return self.balance

    def deposit(self, amount):
        self.balance += amount   # this is equiavalent to self.balance = self.balance + amount    

    def withdraw(self, amount):
        self.balance -= amount

    def __repr__(self):
        return f"Account<{self.name}:{self.balance}>"

    def __str__(self):
        return f"Account<{self.name}>"

        
B1 = BankAccount("Vikrant", 1000) # here you will pass all those arguments which __init__ expects
B1
Account<Vikrant:1000>
B2 = BankAccount("Arcesium", 10000)
B2
Account<Arcesium:10000>
B1.deposit(21212)
B1
Account<Vikrant:22212>
B1.get_balance()
22212
class BankAccount:

    def __init__(self, name, balance):
        self.name = name
        self.balance = balance

    def get_balance(self):
        return self.balance

    def deposit(self, amount):
        self.balance += amount   # this is equiavalent to self.balance = self.balance + amount    

    def withdraw(self, amount):
        if self.get_balance() - amount < 0:
            raise Exception("Balance will go negative, withdraw not allowed")
        self.balance -= amount

    def __repr__(self):
        return f"Account<{self.name}:{self.balance}>"

    def __str__(self):
        return f"Account<{self.name}>"

        

Things to remeber about classes/methods

  • every method has first argument as self , which should not be passed while calling the method
  • every class will have __init__ mehtod which will be called when the class is instantiated
  • the data that you want encapusate should be stored inside self … like self.data = data
  • self is available automatically inside the method
  • you can access already stored data in self using self.
  • Variable names that you store in self should not be same as the method names!
word = "oiksd"
word.replace("o", "i")
'iiksd'

problem

  • Write a class Timer which will work like

    t = Timer() # this does not take any argument
    t.start() # this means there has to be a method with name start
    time.sleep(2)
    t.stop() # this means you class has to have a method with name stop which does not take argument other than self
    print(t.total_time_taken())
import time
time.time() # this is the time in seconds from 1970
1721196823.3781714
time.sleep(5) # some idle activity!
print("done!")
done!
import time

class Timer:

    def __init__(self):
        self.start_time = 0
        self.end_time = 0

    def start(self):
        self.start_time = time.time()

    def stop(self):
        self.end_time = time.time()

    def get_time_taken(self):
        return self.end_time - self.start_time 
t = Timer()
t.start()
time.sleep(2)
t.stop()
print(t.get_time_taken())
2.002189874649048

Inheritence - Class variables and Instance Variables

class YellowLight(Light): 
    color = "yellow"


class WhiteLight(Light): 
    color = "white"
Y1 = YellowLight()
Y2 = YellowLight()
Y1.switch_on()
Y1
Light<True>
Y2
Light<False>
Y1.color
'yellow'
Y2.color
'yellow'
YellowLight.color = "lemmon-yellow" # changing in a class
Y1.color
'lemmon-yellow'
Y2.color
'lemmon-yellow'
Y1.color
'lemmon-yellow'