Module 2 - Day 3

Classes and OOPS

records = [("HCL", 23.5, 123),
           ("INFY", 12.5, 500.5)]
record = {"name":"HCL",
          "gain": 23.5,
          "price": 123}
record # named collection of data
{'name': 'HCL', 'gain': 23.5, 'price': 123}
r = records[0] # non named collection of data
r
('HCL', 23.5, 123)
record['name']
'HCL'
r[0] # name is in programmer's head! it is assumption that first entry is name... second is gain and third entry is price
'HCL'

Data encapsulation can be represented as

          Instance of class
            +---------------+
            |               |<---------methods to manipulate data
            |    data       |
            |               |<---------methods
            +---------------+
                    |
                    |
                    +------<------- methods to access data

Example Bank Account

What all is needed to create bank account

  • Identification and proof of indentification
  • balance (central requirement)!
  • salary/current/savings/FD/PPF
%%file bank0.py

balance = 0

def deposite(amount):
    global balance # this global statement tells that , don't treat balance as local variable
    balance = balance + amount

def withdraw(amount):
    global balance
    balance = balance - amount

def get_balance():
    return balance # if there is no local variable automatically global variable is accessed for reading
Writing bank0.py
import bank0 # we import our bank module
bank0.get_balance()
0
bank0.deposite(5000)
bank0.get_balance()
5000
bank0.withdraw(1000)
bank0.get_balance()
4000
import bank0 # reimporting a module is not possible. python allows importing it only once. this statement has no effect
bank0.get_balance()
4000
%%file bank1.py

def create_account(name, balance):
    return {"name": name,
            "balance": balance}


def deposite(account, amount):
    #account['balance'] = account['balance'] + amount
    account['balance'] += amount  # += operator says add whatever is on right to whatever is on left

def withdraw(account, amount):
    account['balance'] -= amount


def get_balance(account):
    return account['balance']
Overwriting bank1.py
import bank1
a1 = bank1.create_account("Vikrant", 5000)
a2 = bank1.create_account("Vishal", 4000)
bank1.get_balance(a1)
5000
bank1.get_balance(a2)
4000
bank1.deposite(a1, 10000)
bank1.get_balance(a1)
15000
bank1.get_balance(a2)
4000
a3 = bank1.create_account("Arcesium", 10000)
bank1.get_balance(a3)
10000
nums = [1, 2, 3, 4, 5, 6, 7, 8]
nums.index(3)
2
nums.append(3)
class BankAccount:

    def __init__(self, name, balance):
        """This is equivalent to our create_account function in bank1 module
        """
        self.name = name
        self.balance = balance

    def get_balance(self):
        return self.balance

    def deposite(self, amount):
        self.balance += amount


    def withdraw(self, amount):
        self.balance -= amount
        
v = BankAccount("Vikrant", 5000) # this is the class that we created now, and it can be called like a function with required arguments
v
<__main__.BankAccount at 0x7fb4242cbac0>
v.deposite(1000)
v.get_balance() # you don't need any argument here
6000
a = BankAccount("Arcesium", 10000) # we need two arguments name and balance
a
<__main__.BankAccount at 0x7fb40eedd210>
a.deposite(5000)  # we pass only amount 
a.get_balance()
15000
class Stock:

    def __init__(self, name, price):
        """there are some specific magic methods avaialble , if we implement those python will 
        do some magical operation for us!
        """
        self.name = name
        self.price = price
        self.gain = 0
        
    def update_price(self, newprice):
        self.gain = newprice - self.price
        self.price = newprice
         
    def get_gain(self):
        return self.gain

    def get_price(self):
        return self.price
hcl = Stock("HCL", 350) # this will automatically call __init__ method
hcl.get_gain()
0
hcl.update_price(400)
hcl.get_gain()
50
hcl.get_price()
400
class Foo:

    def __init__(self, name):
        self.name  = name
    
    def say_hello(self):
        print("Hello", self.name)

f = Foo("Vikrant") # this calls default __init__() with no arguments
f.say_hello()
Hello Vikrant
class Stock:

    def __init__(self, name, price):
        """there are some specific magic methods avaialble , if we implement those python will 
        do some magical operation for us!
        """
        self.name = name
        self.price = price
        self.gain = 0
        
    def update_price(self, newprice):
        self.gain = newprice - self.price
        self.price = newprice
         
    def get_gain(self):
        return self.gain

    def get_price(self):
        return self.price
inf = Stock("INFY", 2000)
inf
<__main__.Stock at 0x7fb40eedd090>
print(inf)
<__main__.Stock object at 0x7fb40eedd090>
class Stock:

    def __init__(self, name, price):
        """there are some specific magic methods avaialble , if we implement those python will 
        do some magical operation for us!
        """
        self.name = name
        self.price = price
        self.gain = 0
        
    def update_price(self, newprice):
        self.gain = newprice - self.price
        self.price = newprice
         
    def get_gain(self):
        return self.gain

    def get_price(self):
        return self.price

    def __repr__(self):
        """These should always return strings"""
        return "<" + self.name + ":"  + str(self.price) + ">"

    def __str__(self):
        """These should always return strings"""
        return self.name
hcl = Stock("HCL", 500)
hcl # this calls the magic method __repr__
<HCL:500>
infy = Stock("Infy", 1000)
rel = Stock("Reliance", 600)
stocks = [hcl, infy, rel]
stocks
[<HCL:500>, <Infy:1000>, <Reliance:600>]
nums
[1, 2, 3, 4, 5, 6, 7, 8, 3]
print(hcl) # because it converts into str before printing
HCL
hcl
<HCL:500>
str(hcl)
'HCL'
text = 'ttgdsgsadsajhd'
text
'ttgdsgsadsajhd'
print(text)
ttgdsgsadsajhd
hcl # __repr__
<HCL:500>
print(hcl) # __str__
HCL
str(hcl)
'HCL'
print(hcl)
HCL
print("skjdhfkjafha")
skjdhfkjafha
stocks
[<HCL:500>, <Infy:1000>, <Reliance:600>]
max(stocks)
TypeError: '>' not supported between instances of 'Stock' and 'Stock'
hcl > infy
TypeError: '>' not supported between instances of 'Stock' and 'Stock'
def get_price(stock):
    stock.get_price()
max(stocks, key=get_price)
TypeError: '>' not supported between instances of 'NoneType' and 'NoneType'
def get_price(s):
    return s.get_price()

max(stocks, key=get_price) # one item from the list at a time will be passed to get_price function defined here
<Infy:1000>
stocks
[<HCL:500>, <Infy:1000>, <Reliance:600>]
s
NameError: name 's' is not defined
words = "one two three four five six seven".split()
words
['one', 'two', 'three', 'four', 'five', 'six', 'seven']
max(words, key=len) # len is called by max for every word in given list
'three'
max(stocks, key=get_price) # function get_price is called for every stock in the list 
<Infy:1000>
hcl > infy
TypeError: '>' not supported between instances of 'Stock' and 'Stock'
class Stock:

    def __init__(self, name, price):
        """there are some specific magic methods avaialble , if we implement those python will 
        do some magical operation for us!
        """
        self.name = name
        self.price = price
        self.gain = 0
        
    def update_price(self, newprice):
        self.gain = newprice - self.price
        self.price = newprice
         
    def get_gain(self):
        return self.gain

    def get_price(self):
        return self.price

    def __repr__(self):
        """These should always return strings"""
        return "<" + self.name + ":"  + str(self.price) + ">"

    def __str__(self):
        """These should always return strings"""
        return self.name

    def __gt__(self, another_stock):
        return self.get_price() > another_stock.get_price()
h = Stock("HCL", 500)
i = Stock("Infy", 1000)
r = Stock("Reliance", 700)
stocks = [h , i , r]
h > i
False
max(stocks)
<Infy:1000>
class Foo:

    def __init__(self):
        pass

    def method(): # every method in class should have self!
        print("Hello")
f  = Foo()
f.method()
TypeError: Foo.method() takes 0 positional arguments but 1 was given
class Foo:

    def __init__(self):
        pass

    def method(self): # every method in class should have self!
        print("Hello")
f = Foo()
f.method()
Hello

Inheritence

class Light:

    def __init__(self):
        self.status = "off"

    def switch_on(self):
        self.status = "on"

    def switch_off(self):
        self.status = "off"

    def get_status(self):
        return self.status

hall = Light()
bedroom = Light()
kitchen = Light()
lobby = Light()
class WhiteLight(Light):

    color = "white"


class GreenLight(Light):
    color = "green"
gl = GreenLight()
wl = WhiteLight()
gl.get_status()
'off'
gl.switch_on()
gl.get_status()
'on'
gl.status
'on'
gl.color
'green'
class PPFAcount(BankAccount):

    def deposite(self, amount):
        if amount > 150000:
            raise Exception("Amount more than 150000 can not be deposited in PPF account!")
        super().deposite(amount)

    def withdraw(self, amount):
        raise Exception("You can not withdraw unless the account is matured!")
pa = PPFAcount("Vikrant", 1000)
pa.deposite(100)
pa.get_balance()
1100
pa.deposite(200000)
Exception: Amount more than 150000 can not be deposited in PPF account!
pa.withdraw(1000)
Exception: You can not withdraw unless the account is matured!
isinstance(h, Stock)
True
isinstance(pa, PPFAcount)
True
isinstance(pa, BankAccount)
True
isinstance([1, 2, 34], list)
True
list("jhhjghhj")
['j', 'h', 'h', 'j', 'g', 'h', 'h', 'j']
isinstance(5, int)
True
h = Stock("hcl", 500)
[1, 2, 3, 4] # this is also calling constructor of list
[1, 2, 3, 4]
l = [1, 2, 3,343]
isinstance(l, list)
True
list(range(10)) # two different ways to create instance of claas list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l = [0, 2, 3, 4, 5]

String formating

def greet(name):
    print(f"Hello {name}, How are you doing?")
greet("vikrant")
Hello vikrant, How are you doing?
name = "arcesium"
f"Hello {name}, welcome to python!"
'Hello arcesium, welcome to python!'
def message(user, module, maxscore, score, comments):
    msg = """
    Hi {user},
       I appreciate your efforts towards assignment of {module}.
    I have evaluated your assignment and here is your score.
    you have scored {score}/{maxscore}. Here are few comments about
    how you have done and how to improve.

    {comments}

    Feel free to get in touch on slack or email if you need 
    any help in resolving python related queries.

    Regards,
    Vikrant
    Pipal Academy
    https://pipal.in/
    """.format(user=user.capitalize(),
               module=module,
               maxscore=maxscore,
               score=score,
               comments=justify(comments))
    return msg
"hello {user}, how are you douing?".format(user="Vikrant")
'hello Vikrant, how are you douing?'
message_template = """
    Hi {user},
       I appreciate your efforts towards assignment of {module}.
    I have evaluated your assignment and here is your score.
    you have scored {score}/{maxscore}. Here are few comments about
    how you have done and how to improve.

    {comments}

    Feel free to get in touch on slack or email if you need 
    any help in resolving python related queries.

    Regards,
    Vikrant
    Pipal Academy
    https://pipal.in/"""
fmt = message_template.format(user="Arcesium",
                        module="Module I",
                        score=80,
                        maxscore=100,
                        comments="You have done well otherwise, but you should focus on list comprehensions!")
print(fmt)

    Hi Arcesium,
       I appreciate your efforts towards assignment of Module I.
    I have evaluated your assignment and here is your score.
    you have scored 80/100. Here are few comments about
    how you have done and how to improve.

    You have done well otherwise, but you should focus on list comprehensions!

    Feel free to get in touch on slack or email if you need 
    any help in resolving python related queries.

    Regards,
    Vikrant
    Pipal Academy
    https://pipal.in/

Homework problem

Write a function triangle which will print a text based traingle of given size hint: you can try making every line a seprate string , then can have list of lines

>>> traingle(5, "*")
    *
   * *
  * * *
 * * * *
* * * * *

scripts and arguments

%%file head.py
import sys

def check_args():
    first = sys.argv[1]
    if first == "-n":
        lines = sys.argv[2]
        filename = sys.argv[3]
    else:
        lines = 10
        filename = sys.argv[1]
    return lines, filename


def head(filename, lines):
    with open(filename) as f:
        for i in range(lines):
            print(f.readline(), end="")


lines, filename = check_args()
head(filename, lines)
Overwriting head.py
!python head.py -n 3 zen.txt
Traceback (most recent call last):
  File "/home/jupyter-vikrant/arcesium-python-2024/head.py", line 21, in <module>
    head(filename, lines)
  File "/home/jupyter-vikrant/arcesium-python-2024/head.py", line 16, in head
    for i in range(lines):
TypeError: 'str' object cannot be interpreted as an integer
%%file head.py
import sys

def check_args():
    first = sys.argv[1]
    if first == "-n":
        lines = int(sys.argv[2])
        filename = sys.argv[3]
    else:
        lines = 10
        filename = sys.argv[1]
    return lines, filename


def head(filename, lines):
    with open(filename) as f:
        for i in range(lines):
            print(f.readline(), end="")


lines, filename = check_args()
head(filename, lines)
Overwriting head.py
!python head.py -n 3 zen.txt
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
!python head.py  zen.txt
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
%%file head1.py
import typer

def head(filename, lines=10):
    with open(filename) as f:
        for i in range(lines):
            print(f.readline(), end="")


typer.run(head) # typer will take care processing argument from sys.argv
Overwriting head1.py
!python head1.py --help
Usage: head1.py [OPTIONS] FILENAME

Arguments:
  FILENAME  [required]

Options:
  --lines TEXT  [default: 10]
  --help        Show this message and exit.
!python head1.py --lines 3 zen.txt
Traceback (most recent call last):

  File "/home/jupyter-vikrant/arcesium-python-2024/head1.py", line 9, in <module>
    typer.run(head)

  File "/home/jupyter-vikrant/arcesium-python-2024/head1.py", line 5, in head
    for i in range(lines):

TypeError: 'str' object cannot be interpreted as an integer
def add(a, b):
    return a+b
def add(a:int, b:int): # typehints
    return a + b
%%file head1.py
import typer

def head(filename, lines:int=10):
    with open(filename) as f:
        for i in range(lines):
            print(f.readline(), end="")


typer.run(head)
Overwriting head1.py
!python head1.py --help
Usage: head1.py [OPTIONS] FILENAME

Arguments:
  FILENAME  [required]

Options:
  --lines INTEGER  [default: 10]
  --help           Show this message and exit.
!python head1.py --lines 5 zen.txt
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
!python head1.py  zen.txt
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
%%file add1.py
import typer

def add(a:int, b:int):
    return a+b

typer.run(add)
Overwriting add1.py
!python add1.py --help
Usage: add1.py [OPTIONS] A B

Arguments:
  A  [required]
  B  [required]

Options:
  --help  Show this message and exit.
%%file add10.py
import typer

def add(a:int, b:int=10):
    return a+b

typer.run(add)
Writing add10.py
!python add10.py --help
Usage: add10.py [OPTIONS] A

Arguments:
  A  [required]

Options:
  --b INTEGER  [default: 10]
  --help       Show this message and exit.
!python add10.py --b 5 30
%%file add10.py
import typer

def add(a:int, b:int=10):
    print(a+b)

typer.run(add)
Overwriting add10.py
!python add10.py --b 5 30
35
!python add10.py 30
40