Python Journey

Python Journey

·

16 min read

Day 1

2080/09/02


F-strings, or formatted string literals, provide a concise way to embed expressions inside string literals in Python. You create an f-string by prefixing the string with the letter "f" or "F". Inside the string, you can include expressions enclosed in curly braces {}. These expressions will be evaluated at runtime, and their values will be inserted into the string.

Typecasting:

Typecasting, also known as type conversion, is the process of converting a value from one data type to another.

  • Implicit (Auto)

  • Explicit (Manual)

The absolute value of a real number is its distance from zero on the number line, regardless of direction. It is always a non-negative value.

In the context of math.e, e refers to Euler's number, also known as the base of the natural logarithm. Euler's number is an irrational mathematical constant approximately equal to 2.71828.


Day 2

09/03


Day -3

09/05


Day -4

09/06


Day -5

09/08

Dictionaries

Imagine a real-world dictionary:

  • It stores words (keys) and their corresponding definitions (values).

  • You look up a word to find its definition quickly.

Python dictionaries work similarly:

  • They store key-value pairs.

  • You use keys to quickly access associated values.


Day -6

09/09

# Rock Paper Scissors game in python
import random

options = ("rock", "paper", "scissors")
playAgain = True

while playAgain:
    player = None
    computer = random.choice(options)

    while player not in options:
        player = input("Enter your choice 'rock', 'paper' or 'scissors': ")

        print(f"Player: {player}")
        print(f"Computer: {computer}")

        if player == computer:
            print("It's a tie")
        # The or \ is a way of continuing a line of code onto the next line in Python.
        elif (player == "rock" and computer == "scissors") or \
                 (player == "paper" and computer == "rock") or \
                 (player == "scissors" and computer == "paper"):
            print("You win")
        else:
            print("You lose")

    conti = input("Wanna play again? (y/n): ")
    while conti != 'y' and conti != 'n':
        print("Invalid entry, enter y/n:")
        conti = input("Wanna play again? (y/n): ")
        if conti == 'n':
            playAgain = False
            break
        elif conti == 'y':
            computer = random.choice(options)

# Encryption program in python
import random
import string

chars = " " + string.punctuation + string.digits + string.ascii_letters
chars = list(chars) #chars is typecasted into list

keys = chars.copy()
random.shuffle(keys)

# Encryption
msg = input("Enter message to encrypt: ")
cipher_text = ""
for letter in msg:
    index_ = chars.index(letter)
    cipher_text += keys[index_]

print(f"Text encoded: {cipher_text}")

#Decryption
original_msg = ""
cipher = input("Enter the cipher text to decode: ")
for item in cipher:
    ind_ = keys.index(item)
    original_msg += chars[ind_]
print(f"Original message: {original_msg}")

# function = A block of reusable code
# place ( ) after the function name to invoke it

def happy_birthday():
    print("Happy Birthday")

happy_birthday()#calling function

def happy_dashain(name):
    print(f"Happy Dashain {name}")

happy_dashain("Sujan")

# return = statement used to end a function and send a result back to the caller
def create_fullname(first,last):
    fullname = first+" "+last
    return fullname

full = create_fullname("Sujan", "Thapa")
print(f"Your full name is : {full}")

# default arguments

def volume(length, breadth = 5, height = 6):
    #the values in breadth and height are default
    return  length*breadth*height

store = volume(2)
print(store)

store = volume(4,5,8)#but if values are provided, it will override the default
print(store)

# default arguments

import time

def timer(start, end=0):
    for x in range(start,end,-1):
        print(x)
        time.sleep(1)
    print("Done")

timer(15)

# keyword arguments = an argument preceded by an identifier
#                     helps with readability
#                     order of arguments doesn't matter
def name(name,age,country, hobby):
    return  {f"{name}-{age}-{country}-{hobby}"}
#positional arg must be ahead than the keyword arguments like name
store = name("Sujan",hobby="fishing", country="Nepal", age=24)
print(store)

#but in this case, we are having all keywords arg, so the order doesn't matter
store = name(hobby="reading", country="USA", age=22, name="Smith")
print(store)

print("1","2","3","4",sep="-")#print has it's own keyword arg?

# *args = allows you to pass multiple non-key arguments
# **kwargs = allows you to pass multiple keyword-arguments

def add(*args):
    total = 0
    for arg in args:
        total += arg
    return total

store = add(3,4,3,34,4,5)
print(store)

# *args = allows you to pass multiple non-key arguments (tuple)
# **kwargs = allows you to pass multiple keyword-arguments (dictionary)

def names(*args):
    for arg in args:
        print(arg,end=" ")

names("Sujan", "Puja", "Rohan")

# *args = allows you to pass multiple non-key arguments
# **kwargs = allows you to pass multiple keyword-arguments
# * = unpacking operator

def names(**kwargs):
    for key in kwargs.keys():
        print(key, end=" ")
    print()
    for value in kwargs.values():
        print(value, end=" ")
    print()
    for item in kwargs.items():
        print(item, end=" ")

names(name = "Sujan",
      age = 24,
      hobby = "fishing")

# *args = allows you to pass multiple non-key arguments
# **kwargs = allows you to pass multiple keyword-arguments

def names(*args, **kwargs):
    for arg in args:
        print(arg, end=" ")

    print()
    print(kwargs.get("name"))
    print(kwargs.get("hobby"))

names("Bob", 3, "singing",
      name = "Sujan",
      age = 24,
      hobby = "fishing")


Day -7

09/10

# module = a file containing code you want to include in your program
# use 'import' to include a module (built-in or your own)
# useful to break up a large program reusable separate files

import math #built-in module
print(math.pi)

# read write and append in python

# The second argument is read by default, w for write a for append
text = "Let's learn some Python today"
text_plus = "Yeah, that will be fun"

#This will create a new file called a.txt and write the text.
with open("D:\\a.txt",'w') as file:#with open, it closes the file as soon as it does
    # it's work
    file.write(text)

# We are reading the text that is just written
with open("D:\\a.txt") as f:
    print(f.read())

#if we don't specify a as to append, then the previous data will be overridden

# Let's append (add) something new
with open("D:\\a.txt",'a') as fi:
    fi.write(text_plus)

# We are reading the text after appending something new
with open("D:\\a.txt") as fe:
    print(fe.read())
# copyfile() = copies contents of a file
# copy() = copyfile() + permission mode + destination can be a directory
# copy2() = copy() + copies metadata (file's creation and modification times)

import shutil

with open("D:\\z.txt") as file:
    print(file.read())

shutil.copy("D:\\z.txt","D:\\a.txt") #src, destination
with open("D:\\a.txt") as file:
    print(file.read())

# moving files in python
# same process is to move directories
import os
source = "D:\\z\\ab.txt"
destination = "D:\\anyname.txt"
# I tried doing between C and D drives but didn't work
os.replace(source,destination)# this moves the ab.txt to D:\anyname.txt the destination name
# can be whatever we want to save, in my case, anyname.txt

Day -8

09/11

# deleting files and folders in python

import os
path = "D:\\z.txt"
os.remove(path) # it removes the z.txt file

path = "D:\\anynamefolder"
os.rmdir(path)

# to delete a folder and the contents inside it
import  shutil
shutil.rmtree("D:\\exfolder") # considered quite dangerous
# this is info.py file
class Info:
    def __init__(self, name, age, address): # a constructor
        self.name = name # it's like this.name = name
        self.age = age
        self.address = address

    def study(self): # they're methods
        print(f"{self.name} of {self.age} is studying")

    def live(self):
        print(f"{self.name} is living in {self.address}")
from info import Info

person1 = Info("Sujan", 24, "Pokhara")
person1.live()

person2 = Info("Smith", 20, "KTM")
person2.study()

# class and instance variables
class Info:
    college = "PU"
    def __init__(self, name):
        self.name = name # instance variable

    def say(self):
        print(f"My name is {self.name}")
from info import Info
person1 = Info("Sujan")
person1.say()

print(person1.college) #Accessing class variable

Info.college = "TU" #changing the class variable
print(person1.college)

person1.college = "Other" #overrides all other?
print(person1.college)

# inheritance
class Animal:
    isAlive = True

    def eat(self):
        print("This animal eats")

class Rabbit(Animal):
    def run(self): # defining own method
        print("Rabbit is running")

rabbit = Rabbit() #creating object
print(rabbit.isAlive)
rabbit.eat() #accessing the parent's method
rabbit.run() #accessing own's method

# mulit-level inheritance
class Organism:
    isAlive = True

class Animal(Organism):
    def eat(self):
        print("Animal eats")

class Lion(Animal):
    def roar(self):
        print("Lion roars")

lion = Lion()
print(lion.isAlive)
lion.eat()
lion.roar()

# multiple inheritance
class Animal():
    def animal(self):
        print("I am animal")

class Air():
    def air(self):
        print("I can fly")

class Aqua():
    def aqua(self):
        print("I am aquatic")

class Whale(Animal, Aqua):
    print("I am Whale Woooooooooooooooooo!")

class Bird(Animal, Air):
    def fly(self):
        print("I am a bird. FLY FLY!!")

whale = Whale() #can't access land
whale.animal()
whale.aqua()

print()

bird = Bird() #can't access to aqua
bird.fly()
bird.animal()
bird.air()

# method overriding
class Animal:
    def eat(self):
        print("Animal eats")

class Lion(Animal):
    def eat(self):#ovveride the eat from the parent class
        print("Lion eats meat")

lion = Lion()
lion.eat()# output: Lion eats meat
# method chaining = calling mutliple methods sequentially
#                   each call performs an action on the same object and returns self
class Car:
    def start(self):
        print("Car is starting")
        return self

    def drive(self):
        print("I am driving car")
        return self

    def brake(self):
        print("Brake is applied")
        return self

    def stop(self):
        print("Car is stopped")
        return self

car = Car()
# after car.start it returns self and then again self.other methods sequentially
# \ for continuation on the next new line 
# we could also write car.start().drive().brake().stop()
car.start()\
    .drive() \
    .brake()\
    .stop()

# super =  Function used to give access to the methods of a parent class.
#          Returns a temporary object of a parent class when used 

# without super keyword
class Rectangle:
    pass
class Square:
    def __init__(self, length, breadth):
        self.length = length
        self.breadth = breadth

    def area(self):
        return self.length*self.breadth
class Cuboid:
    def __init__(self, length, breadth, height):
        self.length = length
        self.breadth = breadth
        self.height = height
    def vol(self):
        return self.length*self.breadth*self.height

square = Square(4,9)
print(square.area())

volume = Cuboid(5,2,9)
print(volume.vol())

# super =  Function used to give access to the methods of a parent class.
#          Returns a temporary object of a parent class when used

# with super keyword
class Rectangle:

    def __init__(self, length, breadth):
        self.length = length
        self.breadth = breadth

class Square(Rectangle):

    def __init__(self, length, breadth):
        super().__init__(length,breadth)

    def area(self):
        return self.length*self.breadth

class Cuboid(Rectangle):

    def __init__(self, length, breadth, height):
        super().__init__(length, breadth)
        self.height = height

    def vol(self):
        return self.length*self.breadth*self.height

sq = Square(2,88)
print(sq.area())

v = Cuboid(88,4,6)
print(v.vol())


Day 9

09/12

# Prevents a user creating an object ot that class
# compels a user to override abstract methods in a child class
# abstract class - a class which contains one or more abstract methods.
# abstract method - a method that has a declaration but does not have an implementation.

from abc import ABC, abstractmethod #abc = abstract class
class Vehicle(ABC):

    @abstractmethod
    def run(self):
        pass

    def stop(self):#this is not abstract method
        print("Vehicle is stopped")

class Bike(Vehicle):
    def run(self):
        print("Bike is running YEAH!!")

#vehicle = Vehicle()# -> can't create object of abstract class
bike = Bike()
bike.run()
bike.stop()#we used it without the need of overriding

# objects as arguments
class Vehicle:
    color = None

def changeColor(obj, rang):
    obj.color = rang

v = Vehicle()
changeColor(v, "red")
print(v.color)# output: red
# objects as arg
class Dog:
    def __init__(self, name):
        self.name = name

def myDog(obj):
    print(f"Hello {obj.name}")

obj = Dog("Kaley")
myDog(obj)# output: Hello Kaley
# Key Idea:
# 1) Focus on behavior, not explicit types: If an object "walks like a duck and quacks
# like a duck," it's considered a duck, regardless of its actual class.
# 2) Dynamic typing: Python doesn't require type declarations, so objects can change
# types during execution.
# 3) EAFP (Easier to Ask Forgiveness than Permission): Code tries to use methods or
# attributes directly, catching errors if they don't exist, rather than checking
# types beforehand.

class Duck:
    def quack(self):
        print("Quack!")

class Person:
    def quack(self):
        print("I'm imitating a duck!")

def make_it_quack(obj):
    obj.quack()  # Works for both Duck and Person objects

duck = Duck()
person = Person()

make_it_quack(duck)  # Output: Quack!
make_it_quack(person)  # Output: I'm imitating a duck!

# walrus operator :=
# new to Python 3.8
# assignment expression aka walrus operator
# It's a special operator that lets you assign a value to a variable and use
# that value in the same expression. 
# The walrus operator can make code more concise and readable.
# It's especially useful in conditionals, loops, and list comprehensions.

#foods = list()
#list() is a built-in Python function specifically designed to create lists.
#while True:
#    food = input("Enter the food you like (q to quit): ")
#    if food == 'q':
#        break
#    foods.append(food)

foods = list()
while (food := input("Enter the food you like (q to quit): ")) != 'q':
    foods.append(food)

# assign function to variable

say = print # assigning print function to variable 'say'
say("Hello")

def Ooh():
    print("This works")

lol = Ooh()
print(lol)

# lol = Ooh()
# This calls the function named Ooh that you defined earlier.
# The function's job is to print "This works" to the console.
# However, since the function doesn't specifically return any value,
# it automatically returns None.
# So, the variable lol ends up storing this None value.


Day 10

09/13

#  Higher Order Function =  a function that either:
#                           1. accepts a function as an argument
#                               or
#                           2. returns a function
#                           (In python, functions are also treated as objects)
# Example for no. 1
def write(txt):
    print("hello from write function")

def copy(f):
    store = f("print")
    print(store)

copy(write)

# Another example

def upper(text):
    return text.upper()

def lower(text):
    return text.lower()

def msg(f):
    store = f("HELloo")
    print(store)

msg(upper)
msg(lower)

# example for no. 2
def dividend(x):
    def divisor(y):
        return x/y
    return divisor

store = dividend(9)#this line will have the value 9 for x and returns divisor
print(store(3))#the store here acts as a divisor -> divisor(3)

# lambda function = function written in 1 line using lambda keyword
#                   accepts any number of arguments, but only has one expression.
#                   (think of it as a shortcut)
#                   (useful if needed for a short period of time, throw-away)
#
# lambda parameters:expression

add = lambda x,y : x + y
print(add(4,5))

agecheck = lambda age : print("Valid") if age >= 18 else print("Not eligible")
agecheck(78)

fullname = lambda fname, lname : fname+" "+lname
print(fullname("Smile", "Please!"))

# sort() method   = used with lists
# sorted() function = used with iterables

friends = ["Zion","Diken","Smith", "Bishwas", "Catrick","Rohan"]
friends.sort()# the list is sorted. Reverse? then reverse = False in the ()
for friend in friends:
    print(friend)

#this time for tuples
friends = ("Zion","Diken","Smith", "Bishwas", "Catrick","Rohan")
sorted_friends = sorted(friends, reverse=True)#remove reverse for normal AZ sort
for friend in sorted_friends:
    print(friend)

#sort according to the key
students = (("Squidward", "F", 60),
            ("Sandy", "A", 33),
            ("Patrick","D", 36),
            ("Spongebob","B", 20),
            ("Mr.Krabs","C", 78))

store = sorted(students)#this will sort acc. to the first coloumn
for student in store:
    print(student)

print()

#sorting acc. to the second coloumn
# This line creates a lambda function (g) that takes one argument (grades) and
# returns the element at index 1 of that argument.
# In the context of the students tuple, this lambda function is designed to
# operate on each student tuple and extract the grade, which is the second
# element of each tuple.

g = lambda grades : grades[1]#second column is 1 in index.
store1 = sorted(students, key=g)
for student in store1:
    print(student)

# same you can do for third column making another lambda function.

#sort according to the key
students = [("Squidward", "F", 60),
            ("Sandy", "A", 33),
            ("Patrick","D", 36),
            ("Spongebob","B", 20),
            ("Mr.Krabs","C", 78)]


g = lambda grades : grades[1]
# we are doing on list in this case
students.sort(key=g)
for student in students:
    print(student)

# map() =   applies a function to each item in an iterable (list, tuple, etc.)
#
# map(function,iterable)
numbers = [1,2,3,4,5]
square = map(lambda x: x*x, numbers)#could use here the list to shorten
result = list(square)
print(result)#Output: [1, 4, 9, 16, 25]
store = [("shirt",20.00),
         ("pants",25.00),
         ("jacket",50.00),
         ("socks",10.00)]
doguna = lambda data: (data[0], data[1]*2)
d = map(doguna,store)
result = list(d)
for item in result:
    print(item)

# filter() =    creates a collection of elements from an iterable,
#               for which a function returns true
#
#               filter(function, iterable)
friends = [("Rachel",19),
           ("Monica",18),
           ("Phoebe",17),
           ("Joey",16),
           ("Chandler",21)]

#In filter, none like in the map, you can use just one aspect?
store = lambda age : age[1]>=18#function

applyfilter = filter(store, friends)#applying filter

result = list(applyfilter)#Convert the filter object into a list

for drinkingfriend in result:#accessing friend in friends
    print(drinkingfriend)

# reduce() = apply a function to an iterable and reduce it to a single cumulative value.
#            performs function on first two elements and repeats process until 1 value remains
#
# reduce(function, iterable)
import functools
numbers = [1,2,3,4,5]
store = functools.reduce(lambda x,y: x+y, numbers)
print(store)

# starting from first 2 and reducing till the last one standing?

#another one
factorial = [4,5,6,7]
result = functools.reduce(lambda x,y: x*y, factorial)
print(result)

# list comprehension =  a way to create a new list with less syntax
#                       can mimic certain lambda functions, easier to read
#                       list = [expression for item in iterable]
#                       list = [expression for item in iterable if conditional]
#                       list = [expression if/else for item in iterable]

square = []
for i in range(1,10):
    square.append(i*i)
print(square)

#using list comprehension

square = [i*i for i in range (1,10)]
print(square)

students = [100,90,80,70,60,50,40,30,0]
store = lambda x : x > 45
passed = list(filter(store, students))
print(passed)

students = [i for i in students if i > 45]
print(students)

# dictionary comprehension = create dictionaries using an expression
#                            can replace for loops and certain lambda functions
#
# dictionary = {key: expression for (key,value) in iterable}
# dictionary = {key: expression for (key,value) in iterable if conditional}
# dictionary = {key: (if/else) for (key,value) in iterable}
# dictionary = {key: function(value) for (key,value) in iterable}

cities_in_F = {'New York': 32, 'Boston': 75, 'Los Angeles': 100, 'Chicago': 50}
cities_in_C = {key: round((value-39)*(5/9)) for (key,value) in cities_in_F.items()}
print(cities_in_C)

weather = {'New York': "snowing", 'Boston': "sunny", 'Los Angeles': "sunny", 'Chicago': "cloudy"}
sunny = {key: value for (key, value) in weather.items() if value == "sunny"}
print(sunny)

cities = {'New York': 32, 'Boston': 75, 'Los Angeles': 100, 'Chicago': 50}
summer = {key: ("WARM" if value>50 else "OOOOOH COLD") for (key, value) in cities.items()}
print(summer)

def check_temp(value):
    if value < 70:
        return "Warm"
    elif 50 <= value <40:
        return "Ok"
    else:
        return "Cold"

cities = {'New York': 32, 'Boston': 75, 'Los Angeles': 100, 'Chicago': 50}
hot = {key: check_temp(value) for (key, value) in cities.items()}
print(hot)
Output:
{'New York': -4, 'Boston': 20, 'Los Angeles': 34, 'Chicago': 6}
{'Boston': 'sunny', 'Los Angeles': 'sunny'}
{'New York': 'OOOOOH COLD', 'Boston': 'WARM', 'Los Angeles': 'WARM', 'Chicago': 'OOOOOH COLD'}
{'New York': 'Warm', 'Boston': 'Cold', 'Los Angeles': 'Cold', 'Chicago': 'Warm'}
# zip(*iterables) =  aggregate elements from two or more iterables (list,
#                    tuples, sets, etc.)
#                    creates a zip object with paired elements stored in tuples
#                    for each element
names = ["Sujan", "Sunil","Sabin"]
numbers = (2,3,8)
result = zip(names, numbers)
print(type(result))
for item in result:
    print(item)

print()

result = list(zip(names, numbers))
print(type(result))
for i in result:
    print(i)

print()

result = dict(zip(names, numbers))
print(type(result))
for key,value in result.items():
    print(f"{key} : {value}")

print()

age = [24, 34, 44]
store = zip(names, numbers, age)
for element in store:
    print(element)