0% found this document useful (0 votes)
23 views95 pages

Module 1

Uploaded by

Pranjali Kathait
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views95 pages

Module 1

Uploaded by

Pranjali Kathait
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 95

MODULE 1: Introduction to Computer Programming

Chapter 1: Getting Started with Python


Introduction to Python Programming
In the realm of data science, we encounter three key types of analytics: descriptive, predictive, and prescriptive.
Each serves a specific purpose, offering valuable insights and guiding decision-making.

Descriptive Analytics:
Descriptive analytics is all about looking back in time to understand what has happened. It answers the
question, "What happened?" Data analysts use historical data to create reports and summaries that
provide a clear picture of past events and trends.
For example: A data analyst at an e-commerce company might examine past sales data to generate
reports that reveal revenue trends over the last year. This helps the company gain insights into its
historical performance.
1. Predictive Analytics:
Predictive analytics, on the other hand, is all about forecasting the future. It answers the question,
"What will happen?" Data scientists use historical data and various models to make predictions and
forecasts.
For example: A data scientist at a weather forecasting agency may use historical weather data and
models to predict future weather conditions, such as forecasting the likelihood of rain in a specific
location over the next week.
2. Prescriptive Analytics:
Prescriptive analytics goes a step further by providing recommendations on what actions to take. It
answers the question, "What should we do?" This advanced form of analysis guides organizations on
the best actions to achieve specific outcomes.
For example: In healthcare, a data analyst might predict patient readmission risks using a predictive
model. Prescriptive analytics takes it a step further by recommending personalized interventions and
treatment plans to reduce those risks, ultimately improving patient outcomes.
These three types of analytics work in harmony, with descriptive analytics providing historical context,
predictive analytics offering future insights, and prescriptive analytics guiding organizations in making informed
decisions. Together, they enable businesses and organizations to leverage data for improved strategies and
operations.
Data-related roles can be grouped into broader categories based on their primary functions and areas of
expertise. Here's a grouping of data roles:
Data Analysis and Reporting:
 Data Analyst: Examines and interprets data to help organizations make informed decisions.
 Business Intelligence Analyst: Focuses on transforming data into actionable insights to drive business
strategies.
 Market Research Analyst: Collects and analyzes data to provide insights into market trends and
consumer behavior.
 Healthcare Data Analyst: Specializes in analyzing healthcare data to improve patient care and
operational efficiency.
Data Science and Machine Learning:
 Data Scientist: Develops models and algorithms to extract insights and knowledge from data.
 Machine Learning Engineer: Designs and builds machine learning systems and models for predictive
analytics.
 AI Engineer: Develops artificial intelligence systems and applications using machine learning and
deep learning techniques.
 Statistician: Applies statistical methods to analyze data and draw meaningful conclusions.

What makes Netflix Chill?


Imagine this: You settle in for a relaxing evening, ready to stream your favorite shows on Netflix. Little do you
know that behind the scenes, an intricate web of data professionals is working tirelessly to ensure you have the
best possible viewing experience.
1. Data Analysts: Data analysts at Netflix are the historians of your watching patterns. They analyze your
past viewing history and identify what genres, actors, or directors you prefer. Their work is like the
foundation of your streaming experience, understanding what you've enjoyed before.
2. Data Scientists: Now, let's introduce data scientists. They take the information gathered by the data
analysts and use it to build predictive models. These models examine your past behavior and real-time
data to predict what you're likely to watch next. They are the wizards behind the personalized
recommendations that keep you glued to your screen.
3. Data Engineers: Data engineers provide the infrastructure that keeps the entire system running
smoothly. They make sure that massive amounts of data are collected and processed efficiently. Your
seamless streaming experience is made possible by their work, ensuring data is available in real-time to
support recommendations and content delivery.
4. Descriptive, Predictive, and Prescriptive Analytics: These analytics techniques underpin the entire
experience. Descriptive analytics provides a historical context, showing how you've interacted with the
platform in the past. Predictive analytics anticipates your future choices and makes real-time
suggestions, while prescriptive analytics helps determine which content to recommend based on your
preferences and trending shows.
As you browse Netflix, these roles and analytics work in concert to create a tailored streaming experience. You
might not realize it, but every recommendation, every category, and every show you see is influenced by these
data professionals and their efforts. They ensure that the platform continues to be a place where you can relax,
enjoy, and discover new content, all through the magic of data.

1. Python:
Used for: Writing code to analyze, manipulate, and visualize data, as well as building machine learning models
for predictions and automation.
1. SQL (Structured Query Language):
Used for: Querying and managing large datasets in databases, enabling data retrieval, filtering, and aggregation
for analysis and reporting.
1. Excel:
Used for: Organizing, calculating, and conducting basic data analysis, making it a valuable tool for small-scale
data management and visualization.
1. Business Intelligence (BI) Tools:
Used for: Creating interactive, user-friendly dashboards and reports that help businesses and organizations make
informed decisions based on data insights.
1. Statistics:
Used for: Employing mathematical techniques to summarize, interpret, and draw conclusions from data,
allowing for a deeper understanding of underlying trends and patterns.
1. Machine Learning (ML) and Related Libraries:
Used for: Developing and applying algorithms that enable computers to learn from data, recognize patterns, and
make predictions or automated decisions, with applications ranging from image recognition to recommendation
systems.

Journey to Become a DA/DS:


Python is a good choice for beginners in data science for several reasons.
1. Python has a simple and easy-to-learn syntax that is designed to be readable and resembles the English
language. This makes it easy for beginners to write code and understand what is happening in their
code.
2. Python has a vast array of libraries and tools that are specifically designed for data science, including
NumPy, Pandas, Matplotlib, and Scikit-learn. These libraries are essential for data manipulation,
visualization, and machine learning tasks.
3. Python has a large open source community that provides support, tutorials, and documentation, making
it easier for beginners to learn and use Python for data science tasks.
4. Python is a flexible language that can be used for a wide range of data science tasks and can handle
large datasets.
Overall, Python's simplicity, vast array of libraries, open source community, and flexibility make it an excellent
choice for beginners in data science.

Understanding a Python Compiler Through the Human Body:


Imagine that the process of compiling code in Python is somewhat similar to how our body receives and
processes sensory information. Let's use the analogy of our senses (like our eyes and ears) and our brain to
understand this.
1. Sensory Input - Eyes and Ears:
Just like our eyes and ears receive information from the outside world, in Python, your code often
receives information or data from various sources. This could be user input, data from files, or data
from the internet. In our analogy, think of these sources as the "eyes" and "ears" of your Python
program.
2. Compilation - The Brain:
Now, think of the Python compiler as the brain of your program. Just as our brain processes and makes sense of
the signals received by our eyes and ears, the Python compiler processes the information it receives from your
code. This information is written in a way that the computer can understand - just like our brain processes visual
and auditory signals into thoughts and understanding.
3. Forming Meaningful Information:
Our brain takes all the information from our senses and compiles it into something meaningful. It forms a
complete picture of the world around us. In Python, the compiler takes all the pieces of code, processes them,
and compiles them into a set of instructions that the computer can execute.
4. Execution - Action:
Once our brain has processed the sensory information and made sense of it, we can take action. In
Python, after the code is compiled, the computer can execute the instructions, perform calculations, and
produce results, just like we can react to what we see and hear.
So, in summary, a Python compiler is like the brain of your program. It takes in the sensory input (code),
processes it, and compiles it into instructions that the computer can understand and execute.
Where do we practice coding?
IDE:
An Integrated Development Environment (IDE) is a software application that provides programmers with a
comprehensive set of tools and features to write, debug, and test their code in a single interface. IDEs often
include a text editor, a debugger, a compiler, and other features like autocomplete, syntax highlighting, and code
formatting. IDEs are designed to make the coding process more efficient and productive by providing all the
necessary tools and features in one place, which can save time and effort for developers. IDEs are commonly
used for programming languages like Python, Java, C++, and many more.
Few examples of IDE:
 PyCharm
 Spyder
 IDLE
 Jupyter
 Visual Studio Code (VSCode):
 Google Colab

Why Google Colab?


Google Colaboratory is a good choice of IDE for beginners for several reasons.
1. It is a web-based platform that does not require any installation or setup, making it easily accessible to
anyone with an internet connection.
2. It offers a free environment for users to run and execute code, making it ideal for beginners who may
not want to invest in expensive hardware or software.
3. It allows for collaborative work and sharing of code and notebooks, making it easy for beginners to
work with others and get feedback on their work.
4. It comes with pre-installed libraries and tools commonly used in data science, such as NumPy, Pandas,
and Matplotlib, making it easy for beginners to start working on their projects without having to worry
about setting up their environment.

Google Colab Basics:


Google Colaboratory (also known as Google Colab or simply Colab) is a cloud-based platform provided by
Google that allows users to write, run, and share code in a Jupyter notebook environment. Colab offers free
access to GPUs, which makes it an attractive tool for machine learning and deep learning practitioners.
The toolbar in Colab provides access to a variety of tools that are useful when working with notebooks. Here is
a brief overview of some of the key features:
File operations: The toolbar allows you to create new notebooks, open existing ones, save your work, and
download notebooks in various formats.
Runtime options: You can use the toolbar to start and stop the notebook's runtime environment, which
determines the computing resources available to your notebook.
Code execution: The toolbar includes buttons that allow you to execute code cells in your notebook. You can
also use the keyboard shortcuts Shift+Enter to execute the current cell and move to the next one or Ctrl+Enter to
execute the current cell and remain on it.
Cell operations: The toolbar provides options to insert new cells, delete existing ones, move cells up or down,
and merge cells.
print("Hello World")

Wondering what happened in the backend of this code?


A compiler in Python, or any programming language, is like a translator for computers. It takes the human-
readable code that a programmer writes and turns it into something the computer can understand and execute.
Here's how it works in simple terms:
1. Writing Code: First, a programmer writes code in a high-level language like Python. This code is easy
for humans to read and write because it uses words and structures that make sense to us.
2. Compilation: When the programmer is ready to run their program, the Python compiler comes into
play. The compiler is a special program that reads the Python code and translates it into a lower-level
language called "machine code" or "bytecode." This lower-level language is like the computer's native
language, and it can be executed very quickly.
3. Errors and Optimization: The compiler checks the code for any mistakes or errors. If it finds any, it
will report them to the programmer, so they can fix the issues. The compiler can also optimize the code
to make it run faster, just like an editor might revise and improve a written text.
4. Execution: Once the code is error-free and optimized, it's ready to be executed by the computer. The
computer can now understand the translated code and perform the tasks described in the original
Python code.

Importance of Commenting your Codes:


Commenting code is an essential practice in software development. It involves adding explanatory remarks
within the source code to provide context, explanations, or documentation about the code. This helps other
developers (including your future self) understand the purpose, functionality, and logic of the code. Here are
some reasons why commenting code is important:
 Code Readability and Understandability: Well-commented code is easier to read and understand. It
provides context and explanations that make it easier for other developers (or even yourself in the
future) to grasp the code's logic and functionality.
 Collaboration and Teamwork: When working on projects with other developers, clear comments
facilitate collaboration. They allow team members to understand each other's code and make it easier to
integrate different components of a project.
 Debugging and Troubleshooting: Comments can be invaluable during the debugging process. They can
provide insights into the author's intentions, making it easier to identify and fix bugs.
 Documentation: Comments serve as a form of documentation for the codebase. They explain why
certain decisions were made, the purpose of specific functions or blocks of code, and any important
considerations.
Maintenance and Updates: When returning to code after some time, comments can help you quickly reacquaint
yourself with the codebase, reducing the time it takes to make updates or improvements.

In Python, there are several ways to add comments to your code:


1. Single-line Comments: Use the # symbol to create a single-line comment. Anything following # on the
same line is treated as a comment.
# This is a single-line comment
2. Multi-line Comments (String Literals): You can also use triple quotes ''' or """ to create multi-line
comments. While this is not a standard way of commenting, it can serve the purpose.
'''
This is a multi-line comment.
It spans multiple lines.
'''
3. Inline Comments: You can add comments on the same line as code. However, it's generally
recommended to use these sparingly and only for very short, self-explanatory comments.
x = 5 # This is an inline comment

Numeric Data Types:


We will be focusing on two main numeric data for the time being:
Integers: Integers (or ints) are whole numbers without decimal points. In Python, integers can be positive,
negative, or zero. They can be created by writing a number without a decimal point or by using the int()
function.
Floats: Floats are numbers with decimal points. In Python, floats can be positive, negative, or zero. They can be
created by writing a number with a decimal point, by using scientific notation (e.g. 1.23e-4), or by using the
float() function.
Booleans: Boolean is a data type in Python that can have two values: True or False, used for logical operations.

Python also supports other numeric data types, such as complex numbers, bool for boolean values (True or
False) and decimal for fixed-precision decimal values. Additionally, Python provides a rich set of operators and
functions for performing arithmetic operations and other manipulations on numeric data.

Integers are just whole numbers, positive or negative. For example: 2 and -2 are examples of integers.
Floating point numbers in Python are notable because they have a decimal point in them, or use an exponential
(E) to define the number. For example 2.0 and -2.1 are examples of floating point numbers. 4E2 (4 times 10 to
the power of 2) is also an example of a floating point number in Python.
In computing, floating-point arithmetic is arithmetic using formulaic representation of real numbers as an
approximation to support a trade-off between range and precision. You can always control the number of digits
coming after the decimal, hence they are called floating-point numbers.

Basic Arithmetic:
# Addition= 3
2+1
# Subtraction= -3
2-5
# Multiplication= 4
2*2
# Division= 1.5
3/2
# Exponential= 8
2**3
# Order of Operations followed in Python= 105
2 + 10 * 10 + 3

Arithmetic Precedence:
In Python, the order of operations follows the PEMDAS rule, which is an acronym to help remember the
sequence of operations:
1. P - Parentheses:
o Operations enclosed in parentheses are performed first. Nested parentheses are evaluated from
the innermost level to the outermost.
2. E - Exponents:
o After parentheses, exponentiation is performed next. This includes powers and square roots.
3. M - Multiplication and D - Division:
o These operations are performed next and are of equal precedence. They are executed from left
to right. Division includes true division (/), floor division (//) and Modulo (%) . (You'll
learn about the differences soon enough).
4. A - Addition and S - Subtraction:
o Finally, addition and subtraction are performed, also from left to right. They are of equal
precedence.
Python adheres to this order of operations, which is consistent with the standard mathematical conventions.
Understanding PEMDAS is crucial for writing correct expressions in Python, as it dictates how complex
expressions will be evaluated and in what sequence the operations will occur.

Questions:
An operator working behind the scoreboard of a inter cohort AlmaBetter cricket tournament, is responsible for
updating the scores and points of each team. However, the operator is currently facing a challenge. He has been
tasked with updating the total number of points gained by Team London, but he does not possess the necessary
programming skills to complete this task. According to the tournament's rules, teams are awarded the following
points based on the outcome of a match:
 wins: 3 points
 draws: 1 point
 losses: 0 points
Team London has played 9 matches in this tournament. They won 6 matches, lost 2 matches and drew 1. The
operator is in need of assistance to calculate the total number of points earned by Team London.
london_points = (6*3)+(2*0) + (1*1)
print(london_points)

What is a Variable?
Variables are entities which help us store information and retrieve it later.
 A variable with a fixed name can store information of nature like numeric, textual, boolean etc.
 A Python variable is a reserved memory location to store values. In other words, a variable in a python
program gives data to the computer for processing.

 The type of data contained in a variable can be changed at user's will.

Rules for naming a variable in Python:


 Variables names must start with a letter or an underscore like _ product , product_
 The remainder of your variable name may consist of letters, numbers and underscores
 spacy1, pyThon,machine_learning are some valid variable names
 Names are case sensitive.
 case_sensitive, CASE_SENSITIVE, and Case_Sensitive are each a different variable.
 It's a best practice to create a variable that should be coherent with the context you are working on. Let's say
you are working with fruit data; the variable name should be "fruit" or "fruit_mango."

 Names cannot begin with a number. Python will throw an error.


 Names can not contain spaces, use _ instead
 Names can not contain any of these symbols:
:'",<>/?|\!@#%^&*~-+
 It is considered best practice that names are lowercase with underscores
 Avoid using Python built-in keywords like list, str , def, try etc. We will talk more about such
conventions later on

Questions:
Imagine you are a data analyst at a nutrition bar manufacturing company. Your department head approaches you
with a question. The company produces a nutrition bar that contains 50g of raisins, 60g of almonds, and 20g of
apricots. The head of the manufacturing department wants you to create an ingredient percentage list for the
nutrition bar using python.
# Calculate the percentage of raisins and print the variable
rai_sins=50/(50+60+20)*100
print(rai_sins)
# Calculate the percentage of almonds and print the variable
almonds=60/(50+60+20)*100
print(almonds)
# Calculate the percentage of apricots and print the variable
apricots=20/(50+60+20)*100
print(apricots)

Boolean Variables:
True or False

Comparison Operators:
 These operators will allow us to compare variables and output a Boolean value (True or False).
 If you have any sort of background in Math, these operators should be very straight forward.
 First we'll present a table of the comparison operators and then work through some examples:
 In the table below, a=3 and b=4.
Operators Example
== (a==b) is not true
!= (a!=b) is true
> a>b is not true
< a<b is true
>= a>=b is not true
<= a<=b is true
Python comes with Booleans (with predefined True and False displays that are basically just the integers 1 and
0). It also has a placeholder object called None.
# Set object to be a boolean
boolean_variable = False
type(boolean_variable) #bool
#Show
boolean_variable #False
== is a comparison operator, while = is an assignment operator.

History of Python:
Python is a popular programming language that was created in the late 1980s by Guido van Rossum while he

was working at the Centrum Wiskunde & Informatica (CWI) in the Netherlands. 🇳🇱

In 1991, van Rossum released the first version of Python, which was inspired by the ABC language and
designed to be easy to read and write. The name "Python" was inspired by the British comedy group Monty
Python, which van Rossum was a fan of.
Python quickly gained popularity in the scientific and academic communities, as well as among hobbyists and
developers. It was praised for its clean syntax, readability, and ease of use.
Over the years, Python has continued to evolve and improve, with new features and modules being added to the
language. One of the most significant updates was the release of Python 3 in 2008, which introduced several
important changes and improvements, including better Unicode support and a more consistent syntax.
Today, Python is one of the most widely used programming languages in the world, with a thriving community
of developers and users. It is used for everything from web development and data analysis to machine learning
and artificial intelligence.
Python's popularity can be attributed to its simplicity, versatility, and power. It is an accessible language for
beginners, yet powerful enough for advanced users to build complex applications. Python's community and
ecosystem are also strong, with a wealth of libraries, frameworks, and tools available to help developers.
Other Programming Languages similar to Python:

1. Ruby :
Ruby is a dynamic, object-oriented programming language that shares many similarities with Python. Both
languages emphasize simplicity, readability, and developer productivity. Ruby is often used for web
development, and is the primary language behind the popular web framework Ruby on Rails.

2. JavaScript :
JavaScript is a high-level, dynamic programming language that is commonly used for web development. Like
Python, JavaScript is easy to learn and has a simple syntax, and is often used for scripting and automation tasks.
JavaScript is also an interpreted language, meaning that it can be run directly in a web browser without the need
for compilation.

3. Perl :
Perl is a high-level, general-purpose programming language that shares many similarities with Python. Like
Python, Perl is easy to learn and has a simple syntax, and is often used for text processing and system
administration tasks. Perl is known for its powerful regular expression engine and its flexibility, making it ideal
for a wide range of applications.

4. Java :
Java is a general-purpose, object-oriented programming language that is widely used for enterprise
development. While Java has a more complex syntax than Python, it shares many similarities in terms of its
focus on readability, maintainability, and performance. Java is known for its platform independence, which
allows developers to write code once and run it on any platform that supports Java.

5. Julia :
Julia is a high-level, high-performance programming language that is designed for numerical and scientific
computing. Like Python, Julia is easy to learn and has a simple syntax, and is often used for data analysis,
machine learning, and scientific computing. Julia is known for its speed and performance, and is often used for
complex computations that require high-level abstractions and numerical precision.
Each of these programming languages has its own unique features and strengths, but all share a focus on
simplicity, readability, and ease of use. Whether you're building web applications, analyzing data, or working on
large-scale enterprise systems, there's a programming language that's right for your needs.

Python Syntax:
Python is a high-level programming language widely used for web development, data analysis, artificial
intelligence, and other applications. The syntax of Python is simple, readable, and easy to learn.
Syntax is the rule that defines a program's writing and structure.
Some of the critical components of python structure include:
 Variables
 Keywords
 Identifiers
 Indentation
 Function
 Data types → Numbers, String, List, Tuple, Set, Dictionary
 Operators → Arithmetic, Relational
 Input and Output

Python's syntax is designed to be easy to read and understand, with a simple and consistent structure. Here are
some examples of common elements of Python's syntax and how they are used:
 Variables: Variables in Python are used to store data. You can assign a value to a variable using the
assignment operator (=). For example, the following code assigns the value "Hello, World!" to the
variable message:
message = "Hello, World!"
print(message)
# It will print: Hello, World!

 Data types: Python has several built-in data types, including integers, floats, strings, and booleans. For
example, the following code assigns an integer, a float, a string, and a boolean value to four different
variables:

x = 5 # integer
y = 3.14 # float
name = "John" # string
is_student = True # Boolean

 Operators: Python has several built-in operators, including arithmetic operators (e.g., +, -, *, /, %),
comparison operators (e.g., ==, !=, >, <, >=, <=), and logical operators (e.g., and, or, not). For example,
the following code uses the + operator to add two numbers and the == operator to compare two values:
x = 5
y = 3
z = x + y
print(z) # 8
a = "Hello"
b = "World"
c = a == b
print(c) # False

 Identifiers: In Python, an identifier is a name given to a variable, function, class, module, or any other
object. Identifiers are used to refer to these objects in the code. There are some rules and conventions to
follow when naming identifiers in Python:
1. Identifiers can contain letters, numbers, and the underscore (_) character.
2. Identifiers cannot start with a number.
3. Identifiers cannot contain spaces.
4. Python has some reserved keywords that cannot be used as identifiers. (e.g. for, if, else, etc).
5. Python is case-sensitive, so the identifiers x and X are different. It is a good practice to follow the
convention of using lowercase letters for identifiers, with words separated by underscores.
Valid identifier
 my_var
 myVar
 my_var_1
Invalid identifier
 1_var - starts with a number
 my var - contains a space
 for - a reserved keyword

 Loops: Python also has several looping structures that can be used to execute a block of code multiple
times. For example, the "for" loop is used to iterate over a sequence of elements. The following code
uses a "for" loop to print all the elements in a list:
fruits = ["apple", "banana", "orange"]
for fruit in fruits:
print(fruit)

 Modules: Python has a vast library, which is a collection of modules that provides additional
functionality to the programming language. These modules can be imported into the program using the
"import" keyword. For example, the following code imports the "math" module and uses the "sqrt"
function from it to find the square root of a number:
import math
x = 16
result = math.sqrt(x) # 4.0

 Control Flow: Python has several control flow statements, including if-else, for loops, and while
loops. For example, the following code uses an if-else statement to check whether a number is greater
than or less than 10:
x = 15
if x > 10:
print("x is greater than 10")
else:
print("x is less than 10")

 Functions: Python allows you to define your own functions using the def keyword. Functions take
one or more arguments and return a value. For example, the following code defines a function that
takes two arguments and returns their sum:
def add(x, y):
return x + y
result = add(3, 5)
print(result) # 8

Many built-in functions come with the Python language. These functions can perform various tasks, such as
converting data types, manipulating strings, working with numbers, and more. Some of the most commonly
used built-in functions in Python include print(), type(), len(), str(), etc.

Line Structure:
In Python, the structure of a line of code is relatively simple and consistent. Most lines of code will follow a
similar structure, which can be broken down into the following parts:
 Keywords: Python uses certain keywords to indicate different types of statements and expressions. For
example, keywords like "if", "for", "def", and "import" are used to indicate control structures, loops,
function definitions, and module imports, respectively.
Here is a list of some of the most commonly used Python keywords:
 and: Logical operator
 as: To create an alias
 break: To exit a loop
 class: To define a class
 continue: To continue to the next iteration of a loop
 def: To define a function
 del : To delete an object
 elif : Short for "else if" in a conditional statement
 else : To specify an alternative block of code to execute
 except : To handle exceptions
 False: Boolean value
 for: To create a for loop
 from: To import specific elements from a module
 while: To create a while loop
 global: To indicate a global variable
 if: To create a conditional statement
 import: To import a module
 in: To specify a membership test
 is : To test object identity
 lambda: To create an anonymous function
 None: A special value that indicates the absence of a value
 not: Logical operator
 or: Logical operator
 pass: A null statement, used as a placeholder
 return: To exit a function and return a value
 True: Boolean value
 try: To handle exceptions

All the keywords except True, False, and None are in lowercase, and they must be written as it is.

 Expressions: Expressions are used to perform operations on variables and values. They can include
mathematical operations, function calls, and variable assignments. For example, the following line of
code assigns the result of the expression "5 + 7" to a variable "x":
x = 5 + 7

 Operators: Operators are used to performing operations on variables and values. They include
mathematical operators like "+", "-", "*", and "/", as well as comparison operators like "==", "!=", ">",
and "<".
For example, the following line of code compares two variables "x" and "y" and assigns the result to a
variable "result":
result = x > y

 Separators: Separators are used to separate different parts of a line of code. They include punctuation
marks like commas and colons, as well as whitespace characters like spaces and tabs. For example, the
following line of code uses a comma to separate two arguments passed to the "print()" function:
print("Hello,", "world!")

 Comments: Comments are used to provide explanations or documentation for the code. They are
ignored by the interpreter and are indicated by a "#" symbol at the beginning of the line. For example,
the following line of code is a comment:
# This is a comment

Overall, the structure of a line of code in Python is relatively simple and consistent. Most lines of code will
include a combination of keywords, expressions, operators, separators, and comments, depending on the type of
statement or expression being used.

Input/Output Structure:
In Python, some several built-in functions and methods can be used to handle input and output (I/O) operations.
Here are some examples of how to use the most common I/O structures in Python:
 Input: The input() function reads input from the user. This function reads a line of text from the
user and returns it as a string. For example, the following code prompts the user to enter their name and
assigns the value to a variable "name":
name = input("What's your name? ")
 Output: The print() function displays output to the user. This function takes one or more
arguments and displays them to the standard output (usually the console). For example, the following
code prints a string "Hello, world!" to the console:
print("Hello, world!")

 File input/output: Python also provides functions and methods for reading from and writing to files.
The open() function is used to open a file, and the read() and write() methods are used to read
from and write to the file. For example, the following code opens a file "example.txt", reads the
contents of the file and assigns it to a variable "content", and then closes the file.
file = open("example.txt", "r")
content = file.read()
file.close()

 String formatting: The format() method can insert values into a string. This method is used to
insert values into placeholders in a string, which are indicated by curly braces {}. For example, the
following code creates a string "Hello, {}!" with a placeholder and then uses the format method to
insert a value into the placeholder:
greeting = "Hello, {}!"
name = "John"
print(greeting.format(name))

 Integer input/output: To read an integer input from the user, you can use the input() function and
then convert the result to an integer using the int() function. For example:
age = int(input("What's your age? "))
print("Your age is:", age)

 Floating-point input/output: To read a floating-point input from the user, you can use
the input() function and then convert the result to a float using the float() function. For
example:
height = float(input("What's your height? "))
print("Your height is:", height)

 List input/output: To read a list input from the user, you can use the input() function and then
convert the result to a list using the list() function. For example:
colors = list(input("What are your favorite colors? "))
print("Your favorite colors are:", colors)

 Boolean input/output: To read a boolean input from the user, you can use the input() function and
then convert the result to a boolean using the bool() function. For example:
is_student = bool(input("Are you a student? "))
print("You are a student:", is_student)

 JSON input/output: To read or write JSON data, you can use the json module. For example, the
following code writes a dictionary to a json file "example.json":
import json
data = {"name": "John", "age": 30}
with open("example.json", "w") as file:
json.dump(data, file)

Data Types in Python

Data types are basically labels that we use to categorize different types of information in our code. Python is a
dynamic language, which means that it can figure out the data type of a value on its own, based on what it looks
like.

f(x)=x^2+3x-4
Find the value of f(x) at x =2 , x=-1, and x=1
x=2
func_evaluated_at_2 = (x**2)+(3*x)-4
x=-1
func_evaluated_at_minus1 = (x**2)+(3*x)-4
x=1
func_evaluated_at_1 = (x**2)+(3*x)-4
#Check if func_evaluated_at_1 >= func_evaluated_at_minus1
func_evaluated_at_1 >= func_evaluated_at_minus1

Adhering to naming conventions for variables is important in Python as it helps improve code readability and
maintainability. Python follows some standard guidelines for naming variables.
Variable Names: Variable names should be descriptive and indicative of their purpose in the code. They should
be meaningful and reflect the data they store. Avoid using generic names like "a," "x," or "temp" unless they
serve a very specific purpose.
Case Sensitivity: Python is case-sensitive, meaning that variable names like "myVariable" and "myvariable" are
treated as two different variables. It's essential to use consistent casing for your variable names.
Snake Case: The recommended convention for naming variables is to use snake_case, where words are
separated by underscores. This makes the variable name more readable.
eg: snake_case_variable
Avoid Reserved Words: Do not use Python's reserved words (keywords) as variable names, as they have
specific meanings in the language. For example, you shouldn't use words like "print," "if," "while," "for," and so
on as variable names.

To create a string in Python you need to use either single quotes or double quotes. For example:
# Single word
my_first_string= ' Hello World !!! '
my_first_string
type(my_first_string) #str
print(my_first_string *2) #Hello World !!! Hello World!!!
# We can also use double quote
my_string = "String built with double quotes"
print(my_string) # Use the print command
type(my_string)

# Backslash for quotes


print('I\'m fine. How are\'s you?')
I'm fine. How are's you?

# Multiline String
print('''
Alok :Hi I am Alok.I will be your instructor for the first module
Anfal: Thank you
''')

Immutability:
message = "Hey muddy, how are you doing, man?"
# Oops, I made a typo! Meant buddy instead of muddy
message[4] = 'b' #this is called slicing. You'll learn more about it in
upcoming sessions
# message[1]
print(message) #error

# There is a way to change the string.


message.replace('m','b')
# print(message) #Hey buddy, how are you doing, ban?

#.replace hasn't corrected the string yet, you have to reassign the
variable.
# print(message)
message_1 = message.replace('muddy','buddy')
print(message) #Hey muddy, how are you doing, man?
# Data type from String to int Conversion or vice versa
string_int = '25.0'
string_to_int = float(string_int)
print(string_to_int)
type(string_to_int)
25.0
float
strings are immutable in Python, which means that once you create a string, you can't change it.

Some Basic String Operations:


 len() function returns the length of the string
 count() method returns the count of a string in the given string.
Unlike lower() and upper() method, the count() method takes a string as an argument
# Print the length of the string
machine_learning='Neural Networks '
len(machine_learning) #16
machine_learning.count('N') #2
algorithm.count('etrte') #0

Program:
write a program that finds the number of occurance of letter 'e' .
# Store the user input in a variable
books = "Python for Everyone, Learn Python the Hard Way, Python Crash
Course , Starting Out with Python, Automate the Boring Stuff with Python"
# Store the count of books containing e in the title.
x_count = books.count('e')
print(x_count) #7

'a'*2 #aa
# Fill in the blanks
print("The total number of 'e's in the list of books is " + str(x_count))

#but this code would miss out on 'E's. So it might be a good idea to
convert the whole string to lower and then look for the 'e's
e_counts = books.lower().count('e')
print("The total number of 'e's in the list of books is " + str(e_counts))
The total number of 'e's in the list of books is 8
sample_string = "This is Alok hEre"
sample_string.count('e') #1
sample_string.lower().count('e') #2

Alex wants to convert a string representation of a number, "25", into an integer so that he can perform
mathematical operations with it. He wrote the following code:
num_str = "25"
num_int = int(num_str)
result = num_int + 5
print(result)

List:
we use lists and arrays to store collections of data, such as a list of names or a list of numbers. Arrays are similar
to lists, but they are more suited to working with large amounts of numerical data. Lists are constructed with
brackets [] and commas separating every element in the list.
# Assign a list to an variable named my_list
my_list = [1,2,3,4]
type(my_list) #list

lists can actually hold different object types:


my_list = ['A string',23,100.232,'o',True]

In Python, lists are mutable, which means that you can modify them after you've created them.
# create a list
my_list = [1, 2, 3, 4, 5]
# modify the list
my_list[0] = 10
# print out the modified list
print(my_list)

Basic List Functions:


 len() function returns the length of the list
 sum() function returns the sum of the elements of the list
sum() function only works with lists of numeric data types
new_list = [6, 9, 1, 3, 5.5]
len(new_list) #5
sum(new_list) #24.5

Program: The teacher wants to calculate the mean score to assess the overall performance of the class.
marks = [87, 76, 95, 68, 80, 83, 92, 74, 79, 89]
# Calculate the mean of the marks. Use list functions.
mean_marks = sum(marks)/len(marks)
print(mean_marks)

Tuples:
Tuples are immutable, which means that you can't modify them after they've been created.
For example, if you have a set of data that you know won't change, you can store it in a tuple to make sure that it
stays the same. This can be particularly important if you're working on a large project with many different
pieces of code, because it helps to ensure that your data stays consistent throughout. The construction of a tuples
use () with elements separated by commas.
# Create a tuple
my_tuple = (1,2,3)
another_tuple = ('one',2, 4.53, 'asbc')

create a tuple with a single element:


yet_another_tuple = (1,)
type(yet_another_tuple)

my_tuple[0] #1
my_tuple[0] = 'change' #error
# There is a way to change it , convert to list, change and then convert to
tuple
my_tuple_list = list(my_tuple)
my_tuple_list[0] = 'change'
my_tuple = tuple(my_tuple_list)
print(my_tuple) #('change', 2, 3)

Dictionaries:
They allow you to store key-value pairs, which can be used to represent complex data structures in a simple and
efficient way.
One of the key benefits of dictionaries is that they make it easy to look up values based on a key. This can be
particularly useful when you're working with large sets of data and need to find specific pieces of information
quickly. For example, imagine you're working on a project that involves storing information about a large
number of customers. You could use a dictionary to store each customer's information as a set of key-value
pairs, with the customer ID as the key and their information as the value.
A dictionary object is constructed using curly braces {key1:value1,key2:value2,key3:value3}

# Make a dictionary with {} and : to signify a key and a value


marvel_dict = {'Name':'Thor','Place':'Asgard','Weapon' : 'Hammer', 1:2, 3 :
'power', 'alibies' : ['Ironman','Captain America'], 'abc' : {1:2, 4:5}}
# Call values by their key
marvel_dict['Place'] #’Asgard’
marvel_dict['Random'] #error
type(marvel_dict['abc']) #dict

In Python, dictionaries are mutable, which means that you can modify their contents after they've been created.
This makes them a very flexible and powerful data structure for working with complex data.
When you modify a dictionary, you can add, remove, or update key-value pairs as needed. For example, you
might add a new key-value pair to represent a new piece of data, or update an existing key-value pair to reflect a
change in the data. You can also remove key-value pairs that are no longer needed.
customer = {
"id": 1234,
"name": "John Smith",
"email": "[email protected]"}
# Update the customer's email address
customer["email"] = "[email protected]"
# Add a new key-value pair
customer["phone"] = "555-555-1212"
# Remove the customer's ID
del customer["id"]
print(customer)
{'name': 'John Smith', 'email': '[email protected]', 'phone': '555-
555-1212'}
Basic Dictionary Methods:
 keys() method returns the list of keys in the dictionary object
 values() method returns the list of values in the dictionary object
 items()Displays elements in a dictionary

marvel_dict.keys()
dict_keys(['Name', 'Place', 'Weapon', 1, 3, 'alibies', 'abc'])
list(marvel_dict.keys())
['Name', 'Place', 'Weapon', 1, 3, 'alibies', 'abc']
list(marvel_dict.values())
['Thor',
'Asgard',
'Hammer',
2,
'power',
['Ironman', 'Captain America'],
{1: 2, 4: 5}]
marvel_dict.items()
dict_items([('Name', 'Thor'), ('Place', 'Asgard'), ('Weapon', 'Hammer'), (1, 2), (3, 'power'), ('alibies', ['Ironman',
'Captain America']), ('abc', {1: 2, 4: 5})])

marvel_dict['abc'][1] #2

#Here's John's list:


fruits=['Mango','Mango','Pineapple','Pineapple','Apple','Mango','Banana','B
anana','Pineapple','Apple','Pineapple']
# Step 2: Create an empty dictionary which will contain the unique fruit
names
fruit_dict={}
# Set the values for each key separately
# First Key
fruit_dict['Mango']= fruits.count('Mango') #use the count method you
learned in Lists to get the count of Mango
# Second Key
fruit_dict['Pineapple']= fruits.count('Pineapple')
# Third Key
fruit_dict['Apple']=fruits.count('Apple')
# Fourth Key
fruit_dict['Banana']=fruits.count('Banana')
print(fruit_dict)
{'Mango': 3, 'Pineapple': 4, 'Apple': 2, 'Banana': 2}

Sets:
In Python, sets are a powerful tool for working with collections of unique elements. They allow you to perform
operations like union, intersection, and difference on sets of data, making it easy to compare and manipulate
large data sets.
One of the key benefits of sets is that they ensure that each element is unique, so you don't have to worry about
duplicates. This can be particularly useful when you're working with data that has a lot of overlap or
redundancy. For example, imagine you're analyzing a large dataset of customer orders, and you want to identify
all of the unique products that have been ordered. By storing the product names in a set, you can ensure that
each product is only counted once, even if it appears in multiple orders.
 Sets are an unordered collection of unique elements. We can construct them by using
the set() function.
 Sets cannot have duplicates.
 Sets are mutable just like lists.
 You can create a non-empty set with curly braces by specifying elements separated by a comma.
# Create an empty set
empty_set = set()
type(empty_set) #set
# Create a non-empty set within curly braces
non_empty_set = {1,6,4,'abc'}
# Mutability in set
non_empty_set = non_empty_set | {1,7}
print(non_empty_set) # {1, 4, 6, 7, 'abc'}

An empty set cannot be represented as {}, which is reserved for an empty dictionary
my_object = {}
type(my_object) #dict
my_set = set()
type(my_set) #set

We can cast a list with multiple repeat elements to a set to get the unique elements.
# Create a list with repeats
my_list = [1,1,2,2,3,4,5,6,1,1]
# Cast as set to get unique values
my_set = set(my_list)
my_set # {1, 2, 3, 4, 5, 6}

# A set cannot have mutable items


my_set = {1, 2, [3, 4]} X
my_set = {1,2, {3,5}} X
# But we can have tuples as set elements, they are immutable
my_set = {1, 2, (2,3)}

In Python, data types can be broadly categorized into two groups: ordered and unordered data types. These
categories define how the elements in the data type are stored and accessed.
Ordered Data Types:
Ordered data types maintain the sequence or order of elements. You can access elements by their position, and
they are indexed with integers, starting from 0.

String (str): A string is a sequence of characters, and each character has a specific position within the string.
You can access individual characters or substrings by their index.
text = "Hello, Python!"
print(text[0]) # Output: 'H'
print(text[7:13]) # Output: 'Python'

List (list): Lists are ordered collections of items. You can access list elements by their index, and they maintain
the order in which items were added.
fruits = ['apple', 'banana', 'cherry']
print(fruits[1]) # Output: 'banana'
fruits = ['apple', 'banana', 'cherry']
fruits1= ['apple', 'cherry','banana'] #False
fruits==fruits1 #Using comparison operator to check if two variables are
equal.
Tuple (tuple): Tuples are similar to lists, but remember they are immutable, meaning their elements cannot be
changed after creation. They also maintain order.
coordinates = (3, 4)
print(coordinates[0]) # Output: 3

Unordered Data Types:


Unordered data types do not maintain the order of elements, and you cannot access elements by their position.
Common examples of unordered data types in Python include sets and dictionaries.

Set (set): A set is an unordered collection of unique elements. Sets are commonly used for membership testing
or eliminating duplicates.
colors = {"red", "green", "blue"}
colors[0] #Would throw you an error

colors = {"red", "green", "blue"}


colors1={"green","red", "blue"}
colors==colors1 #True

Dictionary (dict): Dictionaries as you know are collections of key-value pairs. They are not ordered, but you
access values by their associated keys.
person = {"name": "John", "age": 30}
print(person["name"]) # Output: 'John'
Indexing & Slicing
In Python, slicing and indexing are fundamental techniques that enable you to access specific elements within a
sequence, including strings, lists, tuples and more.
Indexing helps retrieve individual elements from a sequence by their position, while slicing enables you to
extract a range of elements from a sequence.
Slicing is also commonly used in data analysis and visualization. For example, if you have a large dataset of
customer information, you can use slicing to extract a specific subset of the data based on certain criteria. This
can help you identify patterns and trends in the data more quickly.
In Python, indexing starts at 0, which means the first element in a sequence has an index of 0, the second
has an index of 1, and so on.
Indexing:

Strings are a type of sequence in Python, which means that they can be indexed using the square bracket []
notation.
my_string = "Hello, world!"
print(my_string[0]) # Output: "H"
print(my_string[-1]) # Output: "!"

Tuples, similar to strings, can be indexed using the square bracket [] notation.
my_tuple = (1, 2, 3, 4, 5)
print(my_tuple[2]) # Output: 3
print(my_tuple[-1]) # Output: 5

Lists are another type of sequence in Python, similar to tuples and strings, that can be indexed using the square
bracket [] notation.
my_list = [1, 2, 3, 4, 5]
print(my_list[2]) # Output: 3
print(my_list[-1]) # Output: 5

Sets are an unordered collection of unique elements in Python, and they do not support indexing.
my_set = {1, 2, 3, 4, 5}
print(my_set[2]) #error

Dictionaries are a collection of key-value pairs in Python, and they can be indexed using the keys.
my_dict = {"name": "John", "age": 30, "city": "New York"}
print(my_dict["name"]) # Output: "John"
print(my_dict["city"]) # Output: "New York"

Consider the following list of iconic marvel movie characters. Create two teams of superheroes based on their
index position: one team should consist of all the characters located at even index positions, while the other
team should consist of all the characters located at odd index positions.
#Here's the list
marvel_words = ['Avengers', 'X-Men', 'Spider-Man', 'Iron Man', 'Hulk',
'Thor', 'Black Widow', 'Captain America', 'Wolverine', 'Doctor
Strange','Namor']
team1=[marvel_words[0],marvel_words[2],marvel_words[4],marvel_words[6],marv
el_words[8],marvel_words[10]] #team odd
team2=[marvel_words[1],marvel_words[3],marvel_words[5],marvel_words[7],marv
el_words[9]] #team even
print(team1)
print(team2)
#Reverse the string- Namor
new_word=marvel_words[-1]
namor_reverse=new_word[::-1] #This step might be confusing for you right
now. Worry not. We'll explore this technique in a minute.
print(namor_reverse)

Slicing:
Slicing is another common operation in Python that allows us to extract a portion of a sequence, such as a string,
tuple, or list. Different types of data structures have different slicing methods, but the general syntax is similar
for all of them.
The basic syntax for slicing with a step size is as follows:
sequence[start:stop:step]
Here's what each part means:
 start: The index from which the slicing begins (inclusive).
 stop: The index at which the slicing ends (exclusive).
 step: The number of elements to skip between each included element.

We can use the slice notation [start:end] to extract a portion of the string.

my_string = "Hello, world!"


print(my_string[0:5]) # Output: "Hello"
print(my_string[7:]) # Output: "world!"

How do we use the step size?


Here we have sliced the whole string but with a step size of 2. So the program would output every second
element starting from the first
my_string[::2] # ‘Hlo ol!’
my_string[::-1] #step size of -1 to reverse the string
‘!dlrow ,olleH’
my_string[::-2] #step size of -2 would reverse the string skipping one
element in between
‘!lo olH’

How can you extract "yget" from the string "strategy"?


my_string[-1:-5:-1]

my_tuple = (1, 2, 3, 4, 5)
print(my_tuple[1:3]) # Output: (2, 3)
print(my_tuple[:2]) # Output: (1, 2)

my_list = [1, 2, 3, 4, 5]
print(my_list[2:]) # Output: [3, 4, 5]
print(my_list[:3]) # Output: [1, 2, 3]
print(my_list[1:4:2]) # Output: [2, 4]

Sets are an unordered collection of unique elements in Python, and they do not support slicing, since they don't
support indexing.
my_set = {1, 2, 3, 4, 5}
print(my_set[1:3]) # Error: 'set' object is not subscriptable

Dictionaries are a collection of key-value pairs in Python, and we can slice a dictionary using a list of keys.
However, unlike lists or tuples, slicing with dictionaries is not done using the : operator.
Instead, you can use the dict() constructor to create a new dictionary from a slice of an existing dictionary.
Indexing in a dictionary is done using the keys. You can access the value associated with a particular key by
using the square bracket notation with the key inside the brackets.
my_dict = {'a': 1, 'b': 2, 'c': 3}
value_a = my_dict['a'] # returns 1
value_a

Slicing a dictionary is not possible since dictionaries are unordered and do not support slicing. However, you
can get a subset of a dictionary by creating a new dictionary with only the desired keys. You can use the dict()
constructor and a list comprehension to achieve this. For example:
my_dict = {'a': 1, 'b': 2, 'c': 3}
subset_dict = dict((key, my_dict[key]) for key in ['a', 'c']) #This is an
advanced technique which you'll explore later. Don't worry if you don't
understand this yet.
# returns {'a': 1, 'c': 3}
subset_dict

Operators in Data Types

In Python, Operators are like special symbols that you can use to perform different actions on your data. There
are so many types of operators available, including arithmetic, comparison, logical, assignment, and bitwise
operators. Each type has its own unique set of skills that can help you manipulate data in all sorts of ways.

 Arithmetic operators:
 Assignment operators =
 Comparison operators: == != < > <= >=

 Logical operators: and, or, not

 Identity operators: is ,is not


 Membership operators: in, not in
 Bitwise operators: &|⊕~<< >>

1. Arithmetic Operators:
These are used for performing arithmetic operations on numerical values, such as addition, subtraction,
multiplication, division, and more.
Examples:
Addition (+): 2 + 3 = 5
Subtraction (-): 5 - 2 = 3
Multiplication (*): 2 * 3 = 6
Division (/): 6 / 3 = 2
x = 10
y = 3
print(x + y)
print(x - y)
print(x * y)
print(x / y)

#The "//" operator is known as the integer division operator, which


performs a division operation between
#two numbers and returns the quotient without the remainder. For example,
10 // 3 would result in 3, because
#3 is the largest integer that can fit into 10 without leaving a remainder.
a = 10
b = 3
c = a // b
print(c) # Output: 3
#The "%" operator is known as the modulus operator, which performs a
division operation between two numbers
# and returns the remainder. For example, 10 % 3 would result in 1, because
1 is the remainder when you divide 10 by 3.
a = 10
b = 3
c = a % b
print(c) # Output: 1
#The "* *" operator is known as the exponentiation operator, which raises
the first operand to the power of the second
# operand. For example, 2 ** 3 would result in 8, because 2 multiplied by
itself 3 times equals 8.
a = 2
b = 3
c = a ** b
print(c) # Output: 8

2.Comparison Operators:
x = 5
y = 10
print(x == y) #False
print(x != y) #True
print(x > y) #False
print(x < y) #True
print(x >= y) #False
print(x <= y) #True

3. Logical Operators:
Boolean values (True or False) can be combined and manipulated using logical operators in Python.
Python has three logical operators:
AND: Returns True if both conditions are true, otherwise returns False.
OR: Returns True if at least one condition is true, otherwise returns False.
NOT: Returns the opposite of the condition, i.e., True if the condition is false and False if the condition is true.
Logical operators are useful in controlling the flow of execution based on multiple conditions in conditional
statements and loops. They are also a fundamental concept in Boolean algebra, which forms the basis of digital
electronics and computer science.
x = 5
y = 10
z = 5
print((x < y) and (x == z)) #True
print((x > y) or (x == z)) #True
print(not(x == z)) #False

Order of Precedence:
The conditional statements have a specific order of priority, also known as the "order of precedence," which
determines how they are executed when multiple conditions are present. The order of priority for conditional
statements in Python is as follows:
1. Parentheses (Grouping):
Parentheses () are used to group expressions, and they have the highest priority. When used within
conditional statements, they allow you to control the order of evaluation. Expressions inside
parentheses are evaluated first.
2. Comparison Operators:
Comparison operators like == (equal), != (not equal), < (less than), > (greater than), <= (less than or
equal to), and >= (greater than or equal to) are evaluated next. They are used to compare values and
return Boolean results (True or False).
3. Logical NOT (not):
The not operator is used to negate a Boolean value. It has higher precedence than other logical
operators. For example, not True is False.
4. Logical AND (and):
The and operator is used to combine two or more conditions. It returns True if all conditions are True.
It has lower precedence than the not operator.
5. Logical OR (or):
The or operator is used to combine two or more conditions. It returns True if at least one condition is
True. It has lower precedence than the and operator.
6. Ternary Conditional Operator (if-else):
The if-else conditional expression (also known as the ternary operator) has lower precedence than the
logical operators. It allows you to evaluate an expression based on a condition and return one of two
values.
7. Conditional Statements (if, elif, and else):
The if, elif (else if), and else statements are used for controlling the flow of your code based on
conditional expressions. They are executed sequentially from top to bottom, and each condition is
checked in the order they appear.
The order of priority is important when combining multiple conditions in a single statement, as it affects the
order in which conditions are evaluated. To override the default order of precedence, you can use parentheses to
explicitly specify the order in which conditions should be evaluated.
x = 10
y = 6
result = x > 5 or y < 10 and y == 5
result #True
True or True and False
1. In this expression, the and operator has higher precedence than the or operator. So, True and False is
evaluated first.
2. True and False results in False because both operands must be True for the and operation to yield True.
3. The expression now becomes True or False.
4. The or operator is evaluated, and it returns True because at least one of its operands is True.

4. Assignment Operators:
These are used for assigning values to variables.
Assignment operators in Python are used to assign values to variables. They combine the assignment operator
(=) with one of the arithmetic or bitwise operators to perform an operation and then assign the result to the
variable on the left-hand side. Here are some examples:
#Simple assignment (=): Assigns the value on the right-hand side to the
variable on the left-hand side.
x = 5
print(x)
#Addition assignment (+=): Adds the value on the right-hand side to the
variable on the left-hand side
#and assigns the result to the variable on the left-hand side.
x = 5
x += 3 # Equivalent to x = x + 3
print(x)
#Subtraction assignment (-=): Subtracts the value on the right-hand side
from the variable on the
#left-hand side and assigns the result to the variable on the left-hand
side.
x = 5
x -= 3 # Equivalent to x = x - 3
print(x)
#Multiplication assignment (*=): Multiplies the value on the right-hand
side with the variable on
# the left-hand side and assigns the result to the variable on the left-
hand side.
x = 5
x *= 3 # Equivalent to x = x * 3
print(x)
#Division assignment (/=): Divides the variable on the left-hand side by
the value on the right-hand
# side and assigns the result to the variable on the left-hand side.
x = 5
x /= 3 # Equivalent to x = x / 3
print(x)
#Modulus assignment (%=): Calculates the modulus of the variable on the
left-hand side with the value on the
#right-hand side and assigns the result to the variable on the left-hand
side.
x = 5
x %= 3 # Equivalent to x = x % 3
print(x)
#Floor division assignment (//=): Performs floor division of the variable
on the left-hand side by the value on the right-hand
# side and assigns the result to the variable on the left-hand side.
x = 5
x **= 3 # Equivalent to x = x ** 3
print(x)

5.Membership operators:
The membership operators in Python are "in" and "not in". These operators are used to test if a value is a
member of a sequence, such as a string, list, or tuple.
Here's an example: Imagine you have a string called "sentence" that contains a sentence. You can use the "in"
operator to check if a particular word is present in the sentence.
sentence = "The quick brown fox jumps over the lazy dog"
print("fox" in sentence) # True
print("cat" in sentence) # False
sentence = "The quick brown fox jumps over the lazy dog"
print("cat" not in sentence) # True
print("fox" not in sentence) # False

Program: Find out how many of your books contain the word "Python" in them.
books = [
"Python for Data Science Handbook by Jake VanderPlas",
"The Pragmatic Programmer by Andrew Hunt and David Thomas",
"Python Machine Learning by Sebastian Raschka",
"The Alchemist by Paulo Coelho",]
count=0
"Python" in books[0] # Change x to 0,1,2 and 3.
#If True, increase the count variable by 1.
count=count+1
print(count)

Lists:
Using the + operator to concatenate two lists
list1 = [1,2,3]
list2 = [4,5,6]
result = list1 + list2
print(result) #[1, 2, 3, 4, 5, 6]
Using the * operator to repeat a list
list1 = [1, 2, 3]
result = list1 * 3
print(result) #[1, 2, 3, 1, 2, 3, 1, 2, 3]

Strings:
use the + operator to concatenate two strings, or use the * operator to repeat a string a certain number of times.
# concatenation
string1 = "Hello"
string2 = "world"
result = string1 + " " + string2
print(result) # Output: "Hello world"
# repetition
string = "spam"
result = string * 3
print(result) # Output: "spamspamspam"

You can also use the in and not in operators to check if a substring exists in a larger string. These operators
return a Boolean value True or False.
# checking if a substring exists in a string
string = "Hello world"
print("world" in string) # Output: True
print("Python" in string) # Output: False

Tuples:
tuple1 = (1, 2, 3)
tuple2 = (4, 5, 6)
result = tuple1 + tuple2
print(result) #(1, 2, 3, 4, 5, 6)
tuple3=tuple1*2
print(tuple3) #(1, 2, 3, 1, 2, 3)

Dictionary:
Removing elements: Use the del keyword with the key to remove an element from the dictionary.
my_dict = {"apple": 2, "banana": 3, "cherry": 4}
del my_dict["banana"]
print(my_dict) # Output: {"apple": 2, "cherry": 4}

Checking for key existence: Use the in keyword to check if a key exists in the dictionary.
my_dict = {"apple": 2, "banana": 3, "cherry": 4}
print("apple" in my_dict) # Output: True
print("orange" in my_dict) # Output: False

Copying a dictionary: Use the copy() method to create a shallow copy of the dictionary.
my_dict = {"apple": 2, "banana": 3, "cherry": 4}
new_dict = my_dict.copy()
print(new_dict) # Output: {"apple": 2, "banana": 3, "cherry": 4}

Clearing a dictionary: Use the clear() method to remove all elements from the dictionary.
my_dict = {"apple": 2, "banana": 3, "cherry": 4}
my_dict.clear()
print(my_dict) # Output: {}

Sets:
Sets in Python are unordered collection of unique elements.
Sets have their own set of methods and operators. The most common operators used with sets include:
 Union operator (|), returns a new set with all the elements from both sets
 Intersection operator (&), returns a new set with only the common elements between both sets
 Difference operator (-), returns a new set with only the elements from the first set that are not in the
second set
and many more...
set_a = {1, 2, 3}
set_b = {2, 3, 4}
# # union operator
# print(set_a | set_b) # {1, 2, 3, 4}
# # intersection operator
# print(set_a & set_b) # {2, 3}
# # difference operator
# print(set_a - set_b) # {1}
print((set_a | set_b) - (set_a & set_b))

Identity operators:
Identity operators in Python are used to compare the memory addresses of two objects to check if they refer to
the same object. These operators evaluate to True or False based on whether the two objects are the same or not.
There are two identity operators in Python:
is : Returns True if the two objects are the same, i.e., they have the same memory address.
is not : Returns True if the two objects are not the same, i.e., they have different memory addresses.
Identity operators are often used in Python to compare objects for equality. Since Python objects are created
dynamically and stored in memory, identity operators provide a way to compare objects based on their
memory address rather than their value. This is particularly useful when working with mutable objects, such
as lists and dictionaries, where two objects may have the same value but different memory addresses.

A = [1, 2, 3]
B = [1, 2, 3]
print(A is B) # False
print(A == B) # True since '==' compares value.

a = {"Alok","Anand"}
b = {"AlmaBetter","Instructor"}
a = b
print(a is b)
print(a)
print(b)
True
{'AlmaBetter', 'Instructor'}
{'AlmaBetter', 'Instructor'}

b.add("Head Instructor")
print(a)
print(b)
{'AlmaBetter', 'Instructor', 'Head Instructor'}
{'AlmaBetter', 'Instructor', 'Head Instructor'}

Bitwise Operators:
Bitwise operators are used to perform operations at the bit-level of binary numbers. They manipulate bits in a
number, changing their values to produce a result.
Types of Bitwise Operators
Python provides the following bitwise operators:
 & - Bitwise AND
 | - Bitwise OR
 ^ - Bitwise XOR
 ~ - Bitwise NOT
 < < - Bitwise Left Shift
 .>> - Bitwise Right Shift

a = 60 # 60 = 0011 1100
b = 13 # 13 = 0000 1101
# Bitwise AND
c = a & b # 12 = 0000 1100
print(c)
# Bitwise OR
c = a | b # 61 = 0011 1101
print(c)
# Bitwise XOR
c = a ^ b # 49 = 0011 0001
print(c)
# Bitwise NOT
c = ~a # -61 = 1100 0011
print(c)
# Bitwise Left Shift
c = a << 2 # 240 = 1111 0000
print(c)
# Bitwise Right Shift
c = a >> 2 # 15 = 0000 1111
print(c)

# Bitwise Left Shift


a = 60 # 60 = 0011 1100
c = a << 2 # 240 = 1111 0000
print(c)
# Bitwise Right Shift
c = a >> 2 # 15 = 0000 1111
print(c)

In-Built Functions & Methods


In programming, an in-built function or method is like having a shortcut to pre-existing code included in a
programming language's standard library.
These functions and methods can be used by programmers to perform various operations on data types such as
strings, numbers, lists, dictionaries, and more, without having to write the code for these operations from
scratch.
In-built functions and methods are essential to any programming language, as they allow developers to quickly
and efficiently perform common tasks. They are also highly optimized and thoroughly tested, making them
reliable and robust.
Built-in Functions are pre-defined operations provided by Python, such as print(), len(), and sum(), designed to
perform common tasks effortlessly. They allow you to manipulate, process, and analyze data with ease. On the
other hand, Built-in Methods are functions that are specific to certain data types, like strings, lists, and
dictionaries. These methods, such as split(), append(), and keys(), enable you to interact with data structures in a
more object-oriented manner.

Differences between Functions and Methods:


Functions are like standalone tools that you can use to do a specific task. For example, print() is a function that
you can use to show something on the screen. You call functions with parentheses ().
Methods are like special tools that come with an object, like a string or a list. They are specific to that object,
and you use them by putting a dot . after the object's name. For example, "hello".upper() is a method that makes
the string all uppercase.
Functions are like a hammer that you can use to drive a nail into a piece of wood. It doesn't matter what kind of
wood it is or where the nail goes. You just use the hammer to do a general task.
Methods are like a specialized tool that is made for a specific kind of wood. It might be a saw that can only cut a
certain shape or a drill that can only make a certain size hole.

Taking inputs from user:


In Python, you can take user inputs using the input() function. This function reads a line of text from the user,
converts it into a string, and returns it as the result.
name = input("Enter your name: ")
print("Hello, " + name + "!")
Enter your name: Alex
Hello, Alex!

The input() function always returns a string, even if the user enters a number. If you need to convert the user
input into a different data type (such as an integer or a float), you can use the appropriate conversion function
(int(), float(), etc.) to convert it.
age_str = input("Enter your age: ")
age = int(age_str)
print("You will be " + str(age + 1) + " next year.")
Enter your age: 30
You will be 31 next year.

General Purpose Functions:


In Python, "general-purpose functions" typically refer to functions that are designed to perform a specific task or
set of tasks that can be used in a wide range of applications or scenarios. These functions are not limited to a
single, specialized use case but can be employed in various contexts. They are part of Python's standard library
or created by developers to provide commonly needed functionality. Some of the common examples of general-
purpose functions are:

CRUD:
CRUD stands for Create, Read, Update, and Delete. It's a common paradigm used in software development to
describe the basic operations that can be performed on data. In Python, we can use functions and methods to
implement these CRUD operations on different data structures.

1.Create:
In Python, we can create new objects or data structures using functions such as list(), dict(), set(), tuple(), str(),
and int().
# Create a new list object
my_list = list([1, 2, 3])
print("my_list -", my_list) # Output: my_list - [1, 2, 3]
# Create a new dictionary object
my_dict = dict({'name': 'John', 'age': 30})
print("my_dict -", my_dict) # Output: my_dict - {'name': 'John', 'age':
30}
# Create a new set object
my_set = set(['apple', 'banana', 'cherry', 'apple'])
print("my_set -", my_set) # Output: my_set - {'apple', 'banana', 'cherry'}
# Create a new tuple object
my_tuple = tuple((1, 2, 3))
print("my_tuple -", my_tuple) # Output: my_tuple - (1, 2, 3)
# Create a new string object
my_string = str('Hello, world!')
print("my_string -", my_string) # Output: my_string - Hello, world!
# Create a new integer object
my_int = int(42)
print("my_int -", my_int) # Output: my_int – 42

Converting Data Types:


Type casting, also known as type conversion, is the process of converting a variable of one data type to another
data type. This is a common practice in programming, as it allows us to manipulate and work with data in
different ways.
For example, we might need to convert a string to an integer to perform mathematical operations on it, or we
might need to convert a list to a set to remove duplicate elements.
Type casting can be done using built-in functions like str(), int(), float(), list(), set(), and tuple(), which convert
variables from one data type to another.
It's important to note that type casting can result in a loss of data or precision, depending on the data types being
converted. For example, when we convert a float to an integer, we lose the decimal portion of the number.
Therefore, it's important to use type casting with care and understand its implications.
# Type casting from int to str
my_int = 42
my_string = str(my_int)
print("Type casting from int to str:", type(my_int), type(my_string))
# Type casting from str to int
my_string = '42'
my_int = int(my_string)
print("Type casting from str to int:", type(my_string), type(my_int))
# Type casting from list to set
my_list = [1, 2, 3, 2, 1]
my_set = set(my_list)
print("Type casting from list to set:", type(my_list), type(my_set))
# Type casting from set to list
my_set = {1, 2, 3}
my_list = list(my_set)
print("Type casting from set to list:", type(my_set), type(my_list))

2. Read:
We can use functions and methods to read or retrieve data from objects or data structures in Python.

# Retrieving
the length
of a list
my_list = [1, 2, 3, 4, 5]
print("Length of my_list:", len(my_list)) #5

# Retrieving the index of an element in a list


my_list = [10, 20, 30, 40, 50]
index = my_list.index(30)
print("Index of element 30:", index) #2

# Retrieving a value from a dictionary using get()


my_dict = {"name": "John", "age": 25, "city": "New York"}
age = my_dict.get("age")
print("Age:", age) #25

# Retrieving the index of a substring in a string


my_string = "Hello, world!"
index = my_string.find("world")
print("Index of 'world':", index) #7

# Retrieving the count of an element in a list


my_list = [1, 2, 3, 2, 1, 2, 3, 2, 1]
count = my_list.count(2)
print("Count of element 2:", count) #4

Other Methods used specifically for Dictionary in Read Operations:


Dictionary Methods:
 dict.keys() : returns a list of all the keys in the dictionary
 dict.values() : returns a list of all the values in the dictionary
 dict.items() : returns a list of all the key-value pairs in the dictionary
 dict.get(key, default) : returns the value for the specified key, or the * default value if the key is not
found

# Define a sample dictionary


my_dict = {"apple": 2, "banana": 3, "orange": 4}

# dict.keys()
print("Keys in the dictionary- ",list(my_dict.keys())) # output:
dict_keys(['apple', 'banana', 'orange'])

# dict.values()
print("Values in the dictionary- ",list(my_dict.values())) # output:
dict_values([2, 3, 4])

# dict.items()
print("Dictionary items- ",list(my_dict.items())) # output:
dict_items([('apple', 2), ('banana', 3), ('orange', 4)])

# dict.get(key, default)
print("Fetching value corresponding to 'apple'- ",my_dict.get("apple", 0))
# output: 2
print("Fetching value corresponding to 'grape' (non existent)-
",my_dict.get("grape", 0)) # output: 0

Note that in the dict.keys(), dict.values(), and dict.items() functions, the output is a special dictionary object
(e.g., dict_keys, dict_values, and dict_items). You can convert these to a regular list by wrapping them in a list()
function, as we did in the example.

3.Update:
We can use functions and methods to update or modify existing objects or data structures in Python.
# 1. sorted() - Sorts a list object in ascending order
my_list = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
sorted_list = sorted(my_list)
print("Original list:", my_list)
print("Sorted list:", sorted_list)
Original list: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
Sorted list: [1, 1, 2, 3, 3, 4, 5, 5, 6, 9]

#2. append() - Adds a new element to the end of a list


my_list = [1, 2, 3, 4]
print("Original list:", my_list)
my_list.append(5)
print("List after appending 5:", my_list)
Original list: [1, 2, 3, 4]
List after appending 5: [1, 2, 3, 4, 5]

# 3. update() - Adds or updates a key-value pair in a dictionary


my_dict = {"name": "John", "age": 30}
print("Original dictionary:", my_dict)
my_dict.update({"age": 31, "gender": "Male"})
print("Dictionary after updating:", my_dict)
Original dictionary: {'name': 'John', 'age': 30}
Dictionary after updating: {'name': 'John', 'age': 31, 'gender': 'Male'}

# 4. replace() - Replaces a substring in a string with a new substring


my_string = "Hello, World!"
new_string = my_string.replace("World", "Python")
print("Original string:", my_string)
print("String after replacing:", new_string)
Original string: Hello, World!
String after replacing: Hello, Python!

#5. extend() - Adds multiple elements to the end of a list


my_list = [1, 2, 3]
print("Original list:", my_list)
my_list.extend([4, 5, 6])
print("List after extending:", my_list)
Original list: [1, 2, 3]
List after extending: [1, 2, 3, 4, 5, 6]

Other Methods for update operation:


insert(): Adds an element at a specified position.
#Example:
ex = [1,2,3,3,5]
ex.insert(0,6)
print(ex) #[6, 1, 2, 3, 3, 5]
setdefault(): Updates the dictionary with specified key-value pairs.
#Example:
ex = {'a' :1 , 'b':2}
ex.setdefault('a',"None") #1
ex1 = {'a' :1 , 'b':2}
ex1.setdefault('c',"None") #None

4.Delete:
We can use functions and methods to delete or remove objects or data structures in Python.

# 1. del - Deletes a specific variable or object


a = 10
del a
print(a) # Would result in an error

# 2. remove() - Removes a specific element from a list


my_list = [1, 2, 3, 4, 5]
my_list.remove(3)
print(my_list)

# 3. pop() - Removes and returns the last element of a list


my_list = [1, 2, 3, 4, 5]
print("my_list-", my_list)
# pop method removes the element at the specified and also returns it. So
you can store that value in a variable as well
popped_element_last = my_list.pop() #pops last element by default
popped_element_3=my_list.pop(3) #pops element at index 3
print("popped element at last index-",popped_element_last)
print("popped element at index 3- ",popped_element_3)
print("final List -",my_list)
my_list- [1, 2, 3, 4, 5]
popped element at last index- 5
popped element at index 3- 4
final List - [1, 2, 3]

#4.clear() - Removes all elements from a dictionary, set and list


my_dict = {'a': 1, 'b': 2, 'c': 3}
my_dict.clear()
print(my_dict) #{}
my_list=[1,2,3,4]
my_list.clear()
print(my_list) #[]

my_set={1,2,3,4}
my_set.clear()
print(my_set) #set()

#5. discard() - Removes a specific element from a set, if it exists


my_set = {1, 2, 3, 4, 5}
my_set.discard(3)
print(my_set)

Strings:
 string.upper(): returns a copy of the string with all characters in upper case
 string.lower(): returns a copy of the string with all characters in lower case
 string.title(): returns a copy of the string with the first character of each word in title case (i.e. upper
case)
 string.replace(old, new): returns a copy of the string with all occurrences of old replaced with new
 string.split(separator): returns a list of substrings separated by the specified separator
 string.join(iterable): returns a string that is the concatenation of the elements in the specified iterable,
separated by the string string
 string.isnumeric(): returns True if all characters in the string are numeric, otherwise False

# Sample string
string = "alma better"
print("Original String ",string)

# Using upper() to convert string to all uppercase


upper_case = string.upper()
print("Uppercase String:", upper_case)

# Using lower() to convert string to all lowercase


lower_case = string.lower()
print("Lowercase String:", lower_case,)

# Using title() to convert the first character of each word to uppercase


title_case = string.title()
print("Titlecase String:", title_case)

# Using replace() to replace all occurrences of a substring with another


substring
print("Original String:", string,'\n')
new_string = string.replace("better", "best")
print("New String:", new_string)

# Using split() to split a string into a list of substrings based on a


separator
word_list = string.split(" ")
print("List of words:", word_list)

# Using join() to concatenate a list of strings into a single string with a


separator
new_string = "-".join(word_list)
print("Joined String:", new_string)
Original String alma better
Uppercase String: ALMA BETTER
Lowercase String: alma better
Titlecase String: Alma Better
Original String: alma better
New String: alma best
List of words: ['alma', 'better']
Joined String: alma-better

What is Unicode?
Unicode is a standard that assigns a unique number to every character, symbol, and emoji in every language and
writing system in the world. This makes it possible for computers to store, process, and communicate text in any
language or script, including emojis.
To find the Unicode of a character in Python, you can use the ord('character') method. And to generate a
character corresponding to a Unicode, you can use the chr('Unicode') method. You need not worry about these
now.
So in the case of "bear" and "Bear," the lowercase "b" has a higher Unicode value than the uppercase "B," which
is why "bear" is considered to be greater than "Bear" when comparing strings in Python.
animals=['Bear','bear']
print(max(animals)) #bear
print("Unicode for 'b'- ",ord('b')) #98
print("Unicode for 'B'- ",ord('B')) #66

Program:
statement= "arnav is writing a book titled 'python made easy'. His book is
expected to release in March"
1. Create a list containing the two sentences in the statement.
sentence_list=statement.split('.')
print(sentence_list)
["arnav is writing a book titled 'python made easy'", ' His book is
expected to release in March']
2. The name 'arnav' is spelled wrong. Replace it with 'Arnav'.
statement=statement.replace('arnav','Arnav')
print(statement)
Arnav is writing a book titled 'Python Made Easy'. His book is expected to
release in March
3. The title of the book is not written in the correct cases as well. Make corrections there.
statement=statement.replace('python made easy','python made easy'.title())
print(statement)
Arnav is writing a book titled 'Python Made Easy'. His book is expected to
release in March
4. Reschedule the release of the book to month of your choice. Take input of month from the user and repalce it
with "March" in the statement.
reschedule_month=input("Which month would you like to reschedule the launch
to? ")
statement=statement.replace("March",reschedule_month)
print(statement)
Which month would you like to reschedule the launch to? Aril
Arnav is writing a book titled 'Python Made Easy'. His book is expected to
release in April

Lists:
 list.insert(index, item): inserts an item at a specific index in the list
 list.reverse(): reverses the order of the items in the list
 list.copy(): returns a copy of the list

# create a list
my_list = [4, 2, 8, 1, 6, 5]

my_list.insert(2, 7) # Syntax - my_list.append(index, element)


print("List after inserting item:", my_list,'\n')

my_list.reverse()
print("Reversed list:", my_list,'\n')

new_list = my_list.copy()
print("New copied list:", new_list,'\n')

Program: Finally, you want to be able to count how many movies you have in your collection and reverse the
order of the list.
# Start with an empty list
movie_collection = []
# Add some movies to the collection using the append() method
movie_collection.append("The Godfather")
movie_collection.append("The Shawshank Redemption")
movie_collection.append("The Dark Knight")
movie_collection.append("Pulp Fiction")
movie_collection.append("Goodfellas")

# Print the current collection of movies


print("My Movie Collection:", movie_collection)

# Remove a movie from the collection using the pop() method


movie_collection.pop(3) # remove "Pulp Fiction"
print("After removing Pulp Fiction:", movie_collection)

# Count how many movies are in the collection using the count() method
num_movies = len(movie_collection)
print("Number of The Godfather movies:", num_movies)

# Reverse the order of the list using the reverse() method


movie_collection.reverse()
print("Reversed movie collection:", movie_collection)
Sets:
 set.add(item): adds an item to the set
 set.union(set2): returns a new set with all items from both sets
 set.intersection(set2): returns a new set with only the items that are in both sets
 set.difference(set2): returns a new set with items in the set that are not in set2
 set.symmetric_difference(set2): returns a new set with items in either set or but not in both sets
 set.copy(): returns a copy of the set

# Create a set
fruits = {"apple", "banana", "cherry"}
#Adding another fruit to the set

fruits.add("orange") # ➕ Adds an item to the set


print("Set after adding 'Orange'-",fruits)

# Create another set


colors = {"red", "green", "orange"}

print("Union of two sets",colors.union(fruits)) # 🔀 Returns a set


containing the union of sets

print("Intersection of two sets", colors.intersection(fruits)) # 🤝 Returns


a set containing the intersection of sets

print("Difference of two sets",colors.difference(fruits)) # ➖ Returns a


set containing the difference between two sets

Program: A store sells different types of fruits in baskets. The storekeeper keeps track of the availability of
each fruit type in separate sets. The storekeeper wants to know which fruits are available in both baskets, which
fruits are unique to each basket, and the total number of fruits available.
# Define sets for fruits in each basket
basket1 = {'apple', 'banana', 'grape', 'orange'}
basket2 = {'banana', 'mango', 'pineapple', 'orange'}

# Find the total number of fruits available


total_fruits = len(basket1)+len(basket2)
print("Total number of fruits available:", total_fruits)

# Find fruits available in both baskets


both_baskets = basket1 | basket2
print("All the available fruits:", both_baskets)

# Find only the common fruits in both baskets


common_fruits = basket1 & basket2
print("Common fruits in both baskets:", common_fruits)

# Find fruits unique to each basket


unique_basket1 = basket1 - basket2
unique_basket2 = basket2 - basket1
print("Fruits unique to basket 1:", unique_basket1)
print("Fruits unique to basket 2:", unique_basket2)
Chapter 2: Python Control Flow
Statements, Indentation and Conditionals

Conditional statements are programming that allow a program to make based on certain conditions. These
statements enable the program to execute different depending on whether a particular condition is true or false.
In programming, conditional statements typically take the form of "if-then" statements, where the "if" part
specifies the condition, and the "then" part specifies what action the program should take if the condition is true.
There are also "if-then-else" statements that provide an alternative action to take if the condition is false.
Program: This conditional statement in Python checks whether the temperature is less than 90 degrees
Fahrenheit. If it's true, Jack will go to the park, and if it's false, he will stay inside and play video games.
temperature = 85
if temperature < 90:
print("Let's go to the park!")
else:
print("Let's stay inside and play video games.")
Indentation:
In Python, indentation is used to determine the grouping of code blocks. It is a way of visually indicating which
statements are part of a particular code block, such as a function, loop, or conditional statement.
Indentation is done using spaces or tabs, but it is important to be consistent in the style used throughout the
code. Typically, four spaces are used for each level of indentation.

Indentation is a fundamental part of Python's syntax, and it is important to use it correctly in order to ensure that
the code is readable and functions properly.

An "if" statement allows us to check a single condition and execute a block of code only if that condition is
true. An "else" statement allows us to execute a block of code if the condition in the "if" statement is false.
age = int(input())
if age >= 18:
print("You are an adult")
else:
print("You are not an adult")

An "elif" statement allows us to check multiple conditions and execute different blocks of code depending on
which condition is true.
age = int(input())
if age >= 18:
print("You are an adult")
elif age >= 13:
print("You are a teenager")
else:
print("You are a child")

A nested if statement is simply an if statement that is located within the body of another if statement. The nested
if statement is executed only if the condition of the outer if statement is true.
# asking user for input
age = int(input("Enter your age: "))
height = int(input("Enter your height in cm: "))
# nested if statements to determine eligibility for a rollercoaster ride
if age >= 10:
print("You are old enough to ride the rollercoaster")
if height >= 120:
print("And you meet the height requirement.")
else:
print("But you do not meet the height requirement.")
else:
print("You are not eligible to ride the rollercoaster because you are
too young.")

One-Line Coding in Conditionals:


One-line conditionals are compact expressions that allow you to write simple conditional statements in a single
line. They are particularly useful when the conditions are straightforward and the actions are concise. Python
provides a shorthand syntax to achieve this, using the ternary operator (also known as the conditional
expression).
Ternary Operator Syntax:
value_if_true if condition else value_if_false
Explanation:
 The condition is evaluated first.
 If the condition is true, the expression returns value_if_true.
 If the condition is false, the expression returns value_if_false.
Benefits of One-Line Conditionals:
 One-line conditionals can improve code readability and maintainability, especially when the logic is
simple and doesn't require complex actions or multiple nested conditions.
 They can also help reduce the number of lines of code, making your code more concise and easier to
understand.

# Traditional way
a = 10
b = 15
if a > b:
max_val = a
else:
max_val = b
# Using one-line conditional
max_val = a if a > b else b
The one-line conditional provides a concise and readable way to express the same logic in a single line.

One-Line Coding with Loops (List Comprehensions):


List comprehensions are a concise way to create lists in Python. They allow you to create new lists by applying
expressions to each element in an existing iterable (e.g., list, tuple, or range) while filtering elements that meet
specific conditions.
# List comprehension (one-line loop)
# Syntax: [expression for item in iterable if condition]
numbers = [1, 2, 3, 4, 5]
squared_numbers = [num**2 for num in numbers]
print(squared_numbers) # Output: [1, 4, 9, 16, 25]
even_numbers = [num for num in numbers if num % 2 == 0]
print(even_numbers) # Output: [2, 4]
Caution and Readability:
While one-line coding can be powerful and concise, it is essential to maintain code readability and avoid
excessively complex expressions. Overusing one-liners can make the code less readable and harder to debug.
Always strike a balance between brevity and clarity when utilizing one-line coding techniques.

score = int(input("What is your numeric score? "))


# calculate the letter grade
if score < 60:
letter_grade = "F"
else:
if score < 70:
letter_grade = "D"
else:
if score < 80:
letter_grade = "C"
else:
if score < 90:
letter_grade = "B"
else:
letter_grade = "A"
print("Your letter grade is", letter_grade)

Loops & Iterations


Loops and iterations are important concepts in Python programming that allow you to execute a block of code
multiple times.
The loop construct in Python enables you to repeat a certain task until a certain condition is met, making it a
powerful tool for automating repetitive operations.
Iteration, on the other hand, refers to the process of iterating or going through a collection of elements one by
one. This is done using iterable objects such as lists, tuples, and dictionaries, among others.
In Python, there are two main types of loops: the "for" loop and the "while" loop. The "for" loop is used to
iterate over a collection of elements, while the "while" loop is used to execute a block of code repeatedly until a
certain condition is met.
For Loops:
A for loop in Python is used to iterate over a sequence of elements, such as a list, tuple, string, or range. It
allows you to perform a certain action for each element in the sequence.
Syntax: The basic syntax of a for loop in Python is as follows:
for variable in sequence:
Here, variable is a variable that takes on the value of each element in the sequence, and sequence is the sequence
of elements to be iterated over.
Example: Here's an example of a for loop that iterates over a list of numbers and prints each number:
Scenarios: for loops can be used in a variety of scenarios, including:
1. Iterating over a list or other sequence to perform a certain action for each element.
2. Looping a specific number of times using the range() function.
3. Processing a file line by line using the open() and readline() functions.
4. Iterating over a dictionary to perform a certain action for each key-value pair.
# Define a list of pizza toppings
toppings = ["pepperoni", "mushrooms", "olives", "sausage"]
# Loop through each topping in the list and print it out
for topping in toppings:
print(topping)
# Add new toppings to the list using a loop and the append method
new_toppings = ["bacon", "peppers", "pineapple"]
for topping in new_toppings:
toppings.append(topping)
# Print out the updated list of toppings
print(toppings)

toppings = ["pepperoni", "mushrooms", "olives", "sausage"]


new_toppings = ["bacon", "peppers", "pineapple"]
toppings.extend(new_toppings[0:2]) #adding bacon and peppers to topping
list
toppings

Program: Write a program that takes a string as input and counts the number of vowels (a, e, i, o, u) in the
string using a for loop.
# Take input from the user
string = input("Enter a string: ")
# Initialize a count variable to 0
count = 0
# Loop through each character in the string
for char in string:
# Check if the character is a vowel
if char in "aeiouAEIOU":
# If it is, increment the count
count += 1
# Print the count of vowels
print("The number of vowels in the string is:", count)

Generating numbers- range():


range() is a built-in Python function used to generate a sequence of numbers. It is commonly used for looping a
specific number of times in a for loop. The range() function returns an object of the range class, which
represents a sequence of numbers.
Syntax: The basic syntax of the range() function is as follows:
range(start, stop,step)
# Loop through numbers from 0 to 4
for i in range(5):
print(i) #0 1 2 3 4

# Loop through numbers from 2 to 8, incrementing by 2 each time


for i in range(2, 9, 2):
print(i) #2 4 6 8

range() returns a special type of object that behaves like a sequence but does not actually generate the entire
sequence in memory. This can be more memory-efficient when dealing with large sequences. If you need to
generate the entire sequence at once, you can convert the range() object to a list using the list() function.
Using Breaks & continue:
Loops and iterations are powerful tools in Python programming that allow you to repeat a set of instructions
until a certain condition is met.
Sometimes you might want to exit a loop early or skip over certain iterations. This is where the break and
continue statements come in.
The break statement is used to exit a loop prematurely. When the break statement is encountered inside a loop,
the loop is immediately terminated and the program execution continues with the next statement outside of the
loop.
for i in range(10):
if i == 5:
break
print(i) #0 1 2 3 4

The continue statement is used to skip over certain iterations of a loop. When the continue statement is
encountered inside a loop, the current iteration is skipped and the loop continues with the next iteration.
for i in range(10):
if i % 2 == 0:
continue
print(i) #1 3 5 7 9

Nested Loops in Python:


Bring together the players whose name start with 'A' or 'B' in one team, and 'C' or 'D' in another team.
tournament = [['Alice', 'Charlie'], ['Bob', 'Diana']]
team1=[]
team2=[]
for group in tournament: # First Loop/ Outer Loop.
for player in group: # Inner loop.
if player[0]=='A' or player[0]=='B':
team1.append(player)
else:
team2.append(player)
print(f'Team 1 is: {team1} \n And Team 2 is {team2}')
Team 1 is: ['Alice', 'Bob']
And Team 2 is ['Charlie', 'Diana']

You're given a nested list of numbers, and your task is to count the number of odd and even numbers in the list.
# List of numbers
numbers = [
[2, 5, 11, 20, 8],
[9, 4, 15, 28, 17],
[1, 6, 21, 18, 3],
[10, 13, 25, 33, 30],
[14, 7, 16, 19, 22]]
# Initialize counters for odd and even numbers
even_count = 0
odd_count = 0
# Iterate through the main list and its sublists using nested loops
for sublist in numbers:
for number in sublist:
# Check if the number is odd or even and increment the counters
accordingly
if number % 2 == 0:
even_count += 1
else:
odd_count += 1
# Print the total count of odd and even numbers
print(f"Total even numbers: {even_count}")
print(f"Total odd numbers: {odd_count}")

Imagine you are a teacher and you need to calculate the average grade for a class of 25 students. You have a list
of grades for each student and you need to loop through the list to calculate the average grade for the class.
# Create a list of grades for each student
grades = [90, 85, 95, 80, 75, 85, 90, 85, 70, 80,
90, 75, 80, 85, 90, 95, 80, 75, 85, 90,
85, 90, 75, 80, 85]
# Use a for loop to calculate the total grade for the class
total_grade = 0
for grade in grades:
total_grade += grade
# Calculate the average grade for the class
average_grade = total_grade / len(grades)
# Print the average grade for the class
print(f"The average grade for the class is {average_grade}.")

You need to implement a feature that allows users to count the number of vowel in a string of text. You have a
string containing the text and you need to loop through the string to search for the character.
# for loop to count the number of vowels in a string
my_string = "Hello, World!"
count = 0
for char in my_string:
if char in "aeiouAEIOU":
count += 1
print(count)

# while loop to reverse a string


my_string = "Hello, World!"
reversed_string = ""
i = len(my_string) - 1
while i >= 0:
reversed_string += my_string[i]
i -= 1
print(reversed_string)

You want to print out all the books in your library.


library = {
'To Kill a Mockingbird': 'Harper Lee',
'1984': 'George Orwell',
'The Catcher in the Rye': 'J.D. Salinger',
'The Great Gatsby': 'F. Scott Fitzgerald'}
for book in library:
print(book)

you want to print out all the authors in your library.


for author in library.values():
print(author)

you want to print out both the books and their authors
for book, author in library.items():
print(f"{book} is written by {author}")

Write a program that calculates the total revenue for each day of the week using a loop and prints the results.
#Below we have revenue for previous week. Update this and add this week's
revenue as well.
revenue = {
'Monday': 250,
'Tuesday': 230,
'Wednesday': 45,
'Thursday': 100,
'Friday': 109,
'Saturday': 234,
'Sunday': 567}
for day, sales_amount in sales.items():
revenue[day] += sales_amount
for day, revenue_amount in revenue.items():
print(f"{day}: ${revenue_amount}")

Write a program that takes a list of rolls as input for each player and uses a for loop to calculate the average roll
for each player. The winner gets bragging rights for being the luckiest!
# define a list of rolls for each player
player_rolls = [
[4, 2, 6, 3, 1], # player 1's rolls
[3, 5, 6, 4, 2], # player 2's rolls
[1, 2, 2, 6, 4] # player 3's rolls]
# create an empty list to store each player's average roll
average_rolls = []
# loop through each player's rolls
for i, rolls in enumerate(player_rolls, 1):
# calculate the average roll for the player using the sum() and len()
functions
# hint: the formula for average is sum of rolls divided by number of
rolls
avg_roll = sum(rolls) / len(rolls)
# add the player's average roll to the list of average rolls
average_rolls.append(avg_roll)
# loop through each player's average roll and print it out with the player
number
for i, avg_roll in enumerate(average_rolls):
print(f"Player {i+1}'s average roll: {avg_roll}")

You want to print out all the grades in your tuple.


grades = (90, 78, 85, 92, 76, 88, 79)
for grade in grades:
print(grade)

Looping through tuple elements using index:


for index in range(len(grades)):
print(f"Grade {index + 1}: {grades[index]}")
Grade 1: 90
Grade 2: 78
Grade 3: 85
Grade 4: 92
Grade 5: 76
Grade 6: 88
Grade 7: 79

Looping through tuple elements using the enumerate() function


for index, grade in enumerate(grades):
print(f"Grade {index + 1}: {grade}")
enumerate() is a built-in Python function that allows you to loop through an iterable (like a list, tuple, or string)
and keep track of the index (position) of the current item, along with the item itself. It simplifies your code when
you need to perform operations that require both the element and its index.
enumerate() takes an iterable as its argument and returns an enumerate object, which is an iterator containing
pairs of indices and elements. You can loop through this enumerate object using a for loop.

Here's an example using enumerate() to loop through a list of colors:


colors = ['red', 'green', 'blue', 'yellow']
for index, color in enumerate(colors):
print(f"Color {index + 1}: {color}")

Conditional & Infinite Looping

Conditional Looping:
Conditional looping, also known as a "loop with a condition," is a control flow statement that allows a
programmer to execute a certain block of code repeatedly until a certain condition is no longer met. In Python,
there are two types of conditional loops: the "while loop" and the "for loop."

The while loop:


The while loop allows a programmer to execute a block of code repeatedly while a certain condition is true. The
basic syntax of the while loop is:
while condition:
# block of code
The code block will be executed repeatedly as long as the condition remains true. The condition is checked at
the beginning of each iteration.
The for loop:
The for loop, as you have already learned is used to iterate over a sequence (such as a list, tuple, or string) and
execute a block of code for each item in the sequence. The basic syntax of the for loop is:
for item in sequence:
# block of code
Infinite Looping:
An infinite loop is a loop that never stops executing. It is generally used in situations where a programmer wants
to keep a program running indefinitely, or until it is explicitly stopped by the user. In Python, an infinite loop
can be created using the "while True" statement:
while True:
# block of code
The code block will continue to execute indefinitely until the program is terminated by the user or the operating
system. To avoid infinite loops, it's important to include a way to break out of the loop (such as a "break"
statement) within the code block.

Scenarios where while loops can be used:


1. Repeatedly asking for user input until a valid input is received.
2. Continuously reading data from a sensor until a certain threshold is reached.
3. Implementing game logic such as a player's turn until a game-ending condition is met.
text = ''
while text != 'quit':
text = input('Enter a string (type "quit" to exit): ')
print('You entered:', text)

Write a Python program that takes a list of numbers as input and finds the largest even number in the list using
while loop.
numbers = [12, 7, 8, 15, 20, 10, 45]
numbers = [12, 7, 8, 15, 20, 10, 45]
# Initialize variable to hold largest even number found
largest_even = None
# Initialize counter variable for while loop
i = 0
# While loop to iterate over each number in the list
while i < len(numbers):
num = numbers[i]
i += 1
# Check if the number is even
if num % 2 == 0:
# If largest_even is None or the current number is greater than the
current largest_even, update largest_even
if largest_even is None or num > largest_even:
largest_even = num
# Output the largest even number found
if largest_even is not None:
print(f"The largest even number in the list is {largest_even}.")
else:
print("There are no even numbers in the list.")

for i in range(5, -1, -1):


print(i) #5 4 3 2 1 0
Infinite looping is a common issue that can occur in computer programs. It happens when a loop or a recursive
function does not have a stopping condition, causing the program to run indefinitely or until it crashes. This can
be a serious problem, as it can consume system resources and cause the program to freeze or crash, making it
difficult to recover any unsaved data.
 Always include a stopping condition in your loops and recursive functions. This ensures that the
program will terminate when it has completed its intended task.
 Test your code thoroughly to ensure that it does not enter an infinite loop. Run your code with
different inputs to verify that it terminates properly and does not consume excessive resources.
 Use debugging tools like breakpoints and print statements to help identify and fix infinite loop issues.
These tools can help you pinpoint where the loop is occurring and what is causing it.
 By being vigilant and proactive in avoiding infinite looping, you can help ensure that your programs
run smoothly and efficiently.
while True:
print("This is an infinite loop")

Nesting a while loop within another while loop can lead to code that is difficult to understand and maintain.

It can also result in an loop, which can crash a program or cause it to run indefinitely.
When you have nested while loops, the inner loop will execute its entire code block multiple times for each
iteration of the outer loop. This can result in a large number of iterations, which can make your code less
efficient and slower to run. Additionally, if you need to make a change to the inner loop, you may need to
modify the outer loop as well, which can lead to errors and make your code more difficult to maintain.
Instead of using nested while loops, it's often better to use a single loop with conditional statements to
control the flow of the program. This approach can make your code more readable and easier to
understand, and it can also help you avoid common programming errors.

For loops are typically used when you know the number of iterations in advance or want to iterate over a
sequence of values. You could use a for loop when you want to perform a fixed number of tasks or iterate over a
list, tuple, or string.
While loops, on the other hand, are typically used when you don't know the number of iterations in advance or
want to iterate until a certain condition is met. You could use a while loop when you want to perform a task until
a certain condition is met or keep prompting the user for input until they enter a valid value.

Suppose you work at a bakery and you need to count the number of loaves of bread that are sold each day for a
week. You have a list daily_sales that contains the number of loaves sold each day. You need to calculate the
total number of loaves sold during the week, as well as the average number of loaves sold per day.
# Define the list of daily sales
daily_sales = [10, 15, 20, 12, 8, 17, 9]
# Initialize variables to store total sales and count of days
total_sales = 0
num_days = 0
# Loop through the daily sales list
for sales in daily_sales:
# Add the sales for the current day to the total sales
total_sales += sales
# Increment the count of days
num_days += 1
# Calculate the average number of loaves sold per day
avg_sales = total_sales / num_days
# Print the total number of loaves sold during the week and the average
number of loaves sold per day
print("Total loaves sold during the week:", total_sales)
print("Average loaves sold per day:", avg_sales)
height = int(input("Enter the height of the triangle: "))
for i in range(1, height+1):
for j in range(1, i+1):
print("*", end=" ")
print()

Suppose you work at a gym and you need to count the number of members who visited the gym each day for a
week. You have a list daily_visitors that contains the number of members who visited the gym each day. You
need to calculate the total number of members who visited the gym during the week, as well as the average
number of members who visited the gym per day.

You are a teacher who wants to grade student answers to a quiz. The quiz has 5 questions, and each question is
worth 10 points. You want to use a for loop to go through each question and assign a grade for that question
based on the student's answer. If the answer is correct, the student gets 1 mark for that question. If the answer is
incorrect, the student gets zero marks.

You are an emoji artist and you want to create a pattern of smiley faces using a for loop. You want to print out 5
smiley faces in a row, and then move to the next row and print 4 smiley faces, then 3, then 2, then 1. Your final
pattern should look like a pyramid of smiley faces.
Example output:

😊😊😊😊😊

😊😊😊😊

😊😊😊

😊😊

Write a program to calculate the sum of all the even numbers between 1 and 100.
sum_of_evens = 0
for num in range(1, 101):
if num % 2 == 0:
sum_of_evens += num
print("The sum of all the even numbers between 1 and 100 is:",
sum_of_evens)

Write a program to know the odd numbers & even numbers.


numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for num in numbers:
if num % 2 == 0:
print(num, "is even")
else:
print(num, "is odd")
Write a Python program that takes a number as input and prints out its factorial. A factorial is defined as
the product of all positive integers up to and including the input number.
num = int(input("Enter a number: "))
fact = 1
while num > 0:
fact *= num
num -= 1
print("The factorial is:", fact)

Program to check if user is old enough to vote


# Initialize variables
voting_age = 18
age = 0
# Loop to get input from user
while age < 1:
try:
age = int(input("What is your age? "))
if age < 1:
print("Invalid input. Please enter a positive number.")
except ValueError:
print("Invalid input. Please enter a number.")
# Check if user is old enough to vote
if age >= voting_age:
print("You are old enough to vote!")
else:
print("Sorry, you are not old enough to vote.")

Program to print a multiplication table using nested loops


# Nested loop to iterate through each number in the table
for i in range(1, 11):
for j in range(1, 11):
# Calculate the product of i and j
product = i * j
# Print out the product with proper formatting
print(f"{i} x {j} = {product:>2}", end="\t")
# Move to the next line after printing all products for the current
number
print()

Program to print a rectangle using nested loops


height = int(input("Enter the height of the rectangle: "))
width = int(input("Enter the width of the rectangle: "))
# Outer while loop to iterate over the height of the rectangle
i = 0
while i < height:
# Inner while loop to iterate over the width of the rectangle
j = 0
while j < width:
print("*", end="")
j += 1
# Move to the next line after printing the width of the rectangle
print()
# Move to the next row of the rectangle
i += 1

Chapter 3: Object Oriented Programming


Custom Functions

Custom functions are user-defined functions in a programming language that allow developers to encapsulate a
block of code with a specific functionality, which can be called repeatedly throughout their programs.
Custom functions are blocks of code created by programmers to perform specific tasks or operations within a
program. They can be created in most programming languages and are often used to simplify and modularize
code by separating out specific functions that can be called upon multiple times within the program.
A custom function can be designed to take one or more input parameters, which can be used to customize its
behavior. These parameters allow the function to be called with different arguments, enabling it to perform the
same operation on different sets of data. The output of a function can also be customized, allowing it to return
specific results based on its inputs.
One of the main advantages of custom functions is that they can be reused throughout a program or even across
multiple programs. This reduces the amount of code that needs to be written, which can save time and effort.
Custom functions also make code easier to read and understand because they encapsulate complex operations in
a single function with a clear name and purpose.
Custom functions are widely used in programming tasks such as data processing, mathematical calculations, and
user interface design. They are an essential tool for any programmer looking to write efficient and maintainable
code.
In Python, you can create your own functions using the def keyword. Here is the basic syntax for defining
a function:
def function_name(parameters :"Parameter Hints" = "Default Parameter
Value"):
"""
Docstring

"""
# function code goes here
return output_value

 def: This keyword indicates the start of a function definition. It stands for "define"
 Function_name: This is the name of the function you are defining.
 Parameters: These are the input values that the function will take. They are optional, and you can have
as many or as few as you need.
 Parameter Hints : Pressing "Shift+Tab" while calling a function will display the parameter hints that
were initially defined.
 Default Parameter Value : If a value is not provided when calling a function, this value will be used
instead.
 Docstring : A docstring is a string literal that appears as the first statement in a module, function, class,
or method definition. Its purpose is to provide documentation for the code and to describe what the
function does, what arguments it takes, and what it returns.
 Function code : This is the code that gets executed when the function is called. It can be any valid
Python code.
 return: This keyword indicates the value that the function will return. This is also optional, and you
can have multiple return statements in a function.

you can simply use the "calculate_sales" function to get the total for that day
def calculate_sales(monday_sales=0, tuesday_sales=0, wednesday_sales=0,
thursday_sales=0, friday_sales=0, saturday_sales=0, sunday_sales=0):
"""
Calculates the total sales for a given week by adding up the sales for
each day of the week.
Args:
monday_sales (float): Sales figure for Monday.
tuesday_sales (float): Sales figure for Tuesday.
wednesday_sales (float): Sales figure for Wednesday.
thursday_sales (float): Sales figure for Thursday.
friday_sales (float): Sales figure for Friday.
saturday_sales (float): Sales figure for Saturday.
sunday_sales (float): Sales figure for Sunday.
Returns:
float: The total sales for the week.
"""
# Sum up the sales figures for each day of the week
total_sales = monday_sales + tuesday_sales + wednesday_sales +
thursday_sales + friday_sales + saturday_sales + sunday_sales
# Return the total sales
return total_sales
total_sales = calculate_sales(monday_sales = 100, tuesday_sales = 200,
wednesday_sales=150, thursday_sales=300, friday_sales=250,
saturday_sales=180, sunday_sales=150)
print(total_sales)

Write a program which would calculate the count of each unique characters in a words and returns an
appropriate dictionary. Use a custom function to achieve this.
word = "Custom Function"
def count_letters(word):
letter_count = {}
for letter in word:
if letter.isalpha():
letter = letter.lower()
letter_count[letter] = letter_count.get(letter, 0) + 1
return letter_count
# Try out here
word = "Custom Function"
result = count_letters(word)
print(result)
In Python, variables that are defined outside of a function are called global variables, while variables that are
defined inside a function are called local variables. The scope of a variable refers to the area of the program
where it can be accessed and used.
Global variables can be accessed and modified by any part of the program, including within functions. However,
local variables are only accessible within the function where they are defined.
# Defining a global variable
global_var = 10

def modify_global_var():
"""
Modifies the global variable by adding 5 to its current value.
"""
# Accessing the global variable
global global_var

# Modifying the global variable


global_var += 5

def use_local_var():
"""
Prints the value of a local variable.
"""
# Defining a local variable
local_var = 20

# Printing the value of the local variable


print("Local variable value: ", local_var)

# Calling the functions

# Modifying the global variable using the modify_global_var function


modify_global_var()

# Printing the new value of the global variable


print("Global variable value: ", global_var)

# Using the use_local_var function to print the value of a local variable


use_local_var()
Global variable value: 15
Local variable value: 20

In the above example, global_var is a global variable that can be accessed from any part of the program,
5️⃣
including within functions. The modify_global_var() function modifies the value of global_var by adding to it.
To modify a global variable within a function, you need to use the global keyword before the variable name.
The use_local_var() function defines a local variable local_var within the function. This variable is only
accessible within the function and cannot be accessed outside of it. When the function is called, it prints the
value of local_var.
It is generally considered best practice to use local variables whenever possible, rather than global variables.
This is because local variables have a smaller scope, meaning they are only accessible within the function where
they are defined. This can help to prevent unintended changes to the variable value and make the code more
modular.
On the other hand, global variables can be accessed and modified from any part of the program, which can make
it harder to keep track of changes to the variable value. Additionally, if multiple functions or modules in a
program use the same global variable, it can lead to naming conflicts and make the code harder to maintain.
However, there may be situations where using global variables is necessary. For example, if a variable needs to
be accessed and modified by multiple functions or modules in a program, using a global variable may be the
best option.

Write a function which takes a string arguments and returns the reversed string to the user.
my_string = "Hello, world!"
def reverse_string(string_input):
reversed_string = string_input[::-1]
return reversed_string
# Try out here
input_string = "Hello, World!"
reversed_string = reverse_string(input_string)
print(reversed_string)

Note that default parameters should always come after non-default parameters in the function definition. This is
because Python assigns values to parameters based on their position in the argument list.
# This is not allowed, since non-default parameter follows default
parameter
def my_function(y=10, x):
return x+y
# do something with x and y
my_function(10) #error

Write a function that uses a while loop to calculate calories burned. The function takes workout duration and
activity type as input and calculates calories burned based on the duration and activity type.
Refer to following chart: Running- Burns 10 calories per minute Swimming- Burns 8 calories per minute
Cycling- Burn 6 calories per minute.
def calculate_calories_burned(duration_mins, activity):
calories_burned = 0
while duration_mins > 0:
if activity.lower() == "running":
calories_burned += 10
elif activity.lower() == "swimming":
calories_burned += 8
elif activity.lower() == "cycling":
calories_burned += 6
duration_mins -= 1
return f"You burned {calories_burned} calories during the {activity}
workout."
# Test the function
calculate_calories_burned(duration_mins=20, activity="running")
You burned 200 calories during the running workout.

The function takes a dictionary of book titles as keys and current stock as values, as the input and should return
a dictionary consisting of title as the key and number of books to be ordered as the value.
# List of books and their current stock
books = {
"Harry Potter": 10,
"Lord of the Rings": 5,
"Game of Thrones": 2,
"The Hunger Games": 8,
"To Kill a Mockingbird": 4}
def calculate_books_to_order(books):
#Your code here
# Calculate books to order
calculate_books_to_order(books)

Recursion :
Recursion is a powerful concept in programming where a function calls itself from within its own. In Python,
recursion is often used to solve problems that can be broken down into smaller, similar problems.
In a recursive function, the function is called with a parameter that is gradually reduced or changed until it
reaches a base case that can be directly solved. The base case is the simplest possible case that the function can
solve without calling itself.
Recursive functions can be used to solve problems like searching through a list, or finding the factorial of a
number. However, it's important to be careful when using recursion, as it can lead to infinite loops or stack
overflow errors if not implemented correctly.
def factorial(n=0):
"""
Calculates the factorial of a non-negative integer.
Args:
n (int): A non-negative integer to calculate the factorial of.
Returns:
int: The factorial of the input integer.
"""
# Base case: if n is 0, the factorial is 1
if n == 0:
return 1
# Recursive case: multiply n by the factorial of n-1
else:
return n * factorial(n-1)
factorial(4) #24

A teacher wants to create a Python function to grade a multiple-choice test. The function should take one
argument: a list of answers given by a student. The function should compare the answers given by the
student with the predefined answer key and return the score obtained. Each correct answer is worth 1
point, and each incorrect answer is worth 0 points.

# Here is the answer key


answer_key = ["A", "B", "C", "D", "E"]
# Here are the student's answers
student_answers = ["A", "C", "A", "D", "E"]

def grade_test(student_answers):
"""
Grades a test based on the student's answers and an answer key.
Args:
student_answers (list): A list of the student's answers to the test
questions.
Returns:
int: The student's score on the test.
"""
answer_key = ["A", "B", "C", "D", "E"]
score = 0
# Loop through each answer and compare it to the corresponding answer
in the answer key
for i in range(len(student_answers)):
if student_answers[i] == answer_key[i]:
score += 1 # Increment the score if the answer is correct
else:
score += 0 # No change in score if the answer is incorrect
return score
# Grade the test and print the score
score = grade_test(student_answers)
print(score)

Write a function that takes a list of integers as input and returns the sum of the squares of the even
numbers in the list.
def sum_of_even_squares(lst):
"""
Calculates the sum of the squares of all even numbers in a list.
Args:
lst (list): A list of numbers.
Returns:
int: The sum of the squares of all even numbers in the list.
"""
total = 0
for num in lst:
if num % 2 == 0:
total += num ** 2
return total
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_squares_sum = sum_of_even_squares(numbers)
print(even_squares_sum)

Write a function that takes a string as input and returns a new string with all vowels removed.
def remove_vowels(string):
"""
Removes vowels from a given string and returns the new string.
Args:
string (str): The input string.
Returns:
str: The input string with vowels removed.
"""
vowels = "aeiouAEIOU"
new_string = ""
for char in string:
if char not in vowels:
new_string += char
return new_string
my_string = "Hello, World!"
no_vowels_string = remove_vowels(my_string)
print(no_vowels_string)
Write a function that takes a list of strings as input and returns a new list with all strings in uppercase.
def uppercase_list(lst):
"""
Converts all strings in a list to uppercase.
Args:
lst (list): A list of strings.
Returns:
list: A new list with all strings in uppercase.
"""
new_list = []
for string in lst:
new_list.append(string.upper())
return new_list
words = ["apple", "banana", "cherry", "date"]
upper_words = uppercase_list(words)
print(upper_words)

Write a function that takes a dictionary as input and returns a new dictionary with the same keys but
with all values squared.
def square_dict_values(dct):
"""Given a dictionary, returns a new dictionary with the same keys as
the input dictionary, but with the values squared.
Args:
- dct: A dictionary with numeric values.
Returns:
- A new dictionary with the same keys as the input dictionary, but with
the values squared."""
new_dict = {}
for key, value in dct.items():
new_dict[key] = value ** 2
return new_dict
my_dict = {"a": 1, "b": 2, "c": 3, "d": 4}
squared_dict = square_dict_values(my_dict)
print(squared_dict)

Write a function reverse_string that takes a string as input and returns the string reversed.
def reverse_string(string):
"""
Return a reversed version of the input string.
"""
reversed_string = ""
for i in range(len(string)-1, -1, -1): # Loop over the string in
reverse order
reversed_string += string[i] # Add each character to the reversed
string
return reversed_string
my_string = "Hello, World!"
reversed_string = reverse_string(my_string)
print(reversed_string)

Advanced Looping Concepts

Comprehensions are a powerful feature of Python that allow you to create lists, sets, and dictionaries in a
concise and efficient way. They are particularly useful when working with large datasets or performing complex
transformations on data. Comprehensions in Python are a concise way to create data structures, such as lists,
sets, and dictionaries, using a compact syntax. They allow you to iterate over an iterable object, such as a list or
a range, and apply a condition or transformation to each element to generate a new iterable.
There are 3 types of Comprehensions in python:
1. List Comprehension
2. Set Comprehension
3. Dictionary Comprehension

Lambda functions:
A lambda function is a small, anonymous function in Python that can take any number of arguments, but can
only have one expression. Lambda functions are often used as a shortcut for creating simple, one-line functions.
basic syntax of a lambda function in Python:
lambda arguments: expression
The arguments are the inputs to the function, separated by commas. The expression is the output of the function,
which is evaluated and returned when the lambda function is called.
# A lambda function that adds two numbers
add = lambda x, y: x + y
# Call the lambda function
result = add(3, 4)
print(result) #7
This is equivalent to:
def add(x, y):
return x + y
# Call the custom function
result = add(3, 4)
print(result) #7

Write a lambda function that takes in two numbers as input and returns the quotient of the first number divided
by the second number. If the second number is zero, the function should return "undefined".
divide = lambda x, y: "undefined" if y == 0 else x/y
print(divide(10, 2)) # Output: 5.0
print(divide(10, 0)) # Output: undefined

In Python, filter() is a built-in function that allows you to filter elements of an iterable (such as a list, tuple, or
string) based on a certain condition. The filter() function returns an iterator containing only the elements for
which the condition is True.
The syntax for filter() is:
filter(function, iterable)
where function is a function that takes a single argument and returns a Boolean value (i.e., True or False) based
on some condition, and iterable is the iterable object that you want to filter.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # Output: [2, 4, 6, 8, 10]
The lambda function checks if a number is even by checking if its remainder after division by 2 is 0. We then
convert the filtered result to a list and print it.
Map Function:
The map() function in Python applies a given function to each item of an iterable (e.g. a list) and returns a new
iterable with the results. The syntax of the map() function is as follows:
map(function, iterable)
Here, function is the function to be applied to each item in the iterable, and iterable is the list or other iterable to
be operated upon.
# A list of integers
numbers = [1, 2, 3, 4, 5]
# Use map() with a lambda function to calculate squares
squares = list(map(lambda x: x**2, numbers))
# Print the list of squares
print(squares) #1 4 9 16 25

we define a list of numbers and then use the map() function with a lambda function to calculate the square of
each number in the list. The lambda function takes an input x and returns x**2, which is the square of x. The
resulting map object is then converted to a list using the list() function, and the list of squares is printed.
The map() function can be used with any function that takes a single argument and returns a single value, not
just lambda functions.
an example of using the map() function with a built-in function to convert a list of strings to a list of their
lengths:
# A list of strings
words = ["apple", "banana", "cherry"]
# Use map() with len() to calculate lengths
lengths = list(map(len, words))
# Print the list of lengths
print(lengths) [5,6,6]
we use the map() function with the built-in len() function to calculate the length of each string in the words list.
The resulting map object is then converted to a list using the list() function, and the list of lengths is printed.

Lambda functions can be especially useful when you need to perform a simple operation on each item in a list,
such as converting all the items to lowercase or removing any items that don't meet a certain criterion. By using
a lambda function with a built-in function like map() or filter(), you can perform these operations in a concise
and readable way.

Shortcomings of Lambda and Mapping Functions:


Lambda functions:
1. Lack of readability: Lambda functions can be difficult to read and understand, especially for people
who are not familiar with functional programming concepts.
2. Limited functionality: Lambda functions are limited in what they can do compared to regular functions.
They can only contain a single expression, which means they cannot include multiple statements or
complex logic.
3. No documentation: Lambda functions do not support docstrings, which are used to document regular
functions. This can make it difficult for others to understand how the function works and how to use it.
4. Difficult to debug: Because lambda functions are anonymous and often used as arguments to other
functions, it can be difficult to debug them if something goes wrong.
5. Limited scope: Lambda functions are defined within the scope of the function that creates them, which
means they cannot be accessed outside of that scope.
Mapping Functions:
1. Limited flexibility: Mapping functions can only be used to apply a single operation to each element of
an iterable. If you need to perform multiple operations on each element, or if you need to apply a
different operation to each element based on some condition, you may need to use a different approach.
2. No in-place modification: Mapping functions create a new iterable with the transformed elements,
rather than modifying the original iterable in place. This means that if you need to modify the original
iterable, you will need to use a different approach.
3. Performance overhead: Mapping functions can be slower than using a for loop to iterate over an
iterable and perform the transformation. This is because creating a new iterable requires allocating
memory and copying the elements.
4. Limited error handling: Mapping functions can be more difficult to debug than a for loop, as they do
not provide much information about errors that occur during the transformation process. For example,
if a function used in a map raises an exception, it can be difficult to determine which element caused
the exception.
5. Limited support for non-iterable inputs: Mapping functions are designed to work with iterable inputs,
such as lists or tuples. If you need to apply a transformation to a non-iterable input, such as a single
value, you will need to use a different approach.

List Comprehension:
List comprehensions are a concise and powerful way to create lists in Python. They allow you to create a new
list by iterating over an existing iterable object, such as a list, tuple, or string, and applying a condition or
expression to each element.
List comprehensions in Python are great, but mastering them can be tricky because they don’t solve a new
problem: they just provide a new syntax to solve an existing problem.
The basic syntax for a list comprehension is:
new_list = [expression for item in iterable if condition]
Here,
 Expression is the operation you want to perform on each element of the iterable.
 Item is a variable representing each element in the iterable,
 Iterable is the iterable object you want to iterate over, and
 Condition is an optional condition that filters the elements based on a Boolean expression.

You want to create a list of the squares of the first 10 positive integers
squares = []
for i in range(1, 11):
squares.append(i**2)
print(squares)
Using a list comprehension, you could accomplish the same task with less code:
squares = [i**2 for i in range(1, 11)]
print(squares)
The list comprehension consists of three parts:
The Output expression i**2 that calculates the square of each number in the range,
The variable i that represents each number in the range,
And the output predicate, the range itself range(1, 11) that specifies the numbers to be squared.

Nested List Comprehension:


You want to create a list of all pairs of numbers where the first number is even and the second number is
odd.
pairs = [(i, j) for i in range(1, 6) if i % 2 == 0 for j in range(1, 6) if
j % 2 == 1]
print(pairs)
[(2, 1), (2, 3), (2, 5), (4, 1), (4, 3), (4, 5)]

Question 1:
Instructions: Complete the lambda function to multiply each element of the list by 2 using mapping.
numbers = [1, 2, 3, 4, 5]
doubled_numbers = list(map(______, numbers))
Question 2:
Instructions: Complete the lambda function to convert each word in the list to uppercase using mapping.
words = ['hello', 'world', 'python', 'programming']
uppercase_words = list(map(______, words))
Question 3:
Instructions: Fill in the lambda function to calculate the square of each number in the list using mapping.
numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(______, numbers))
Answers:
Question 1: Answer: lambda x: x * 2
Explanation: In this question, the lambda function takes an argument "x" and returns the result of multiplying
"x" by 2. The "map" function applies this lambda function to each element of the "numbers" list, creating a new
list "doubled_numbers" with all elements doubled.
Question 2: Answer: lambda word: word.upper()
Explanation: The lambda function takes a word as input and returns the uppercase version of that word using the
"upper()" method. The "map" function applies this lambda function to each word in the "words" list, creating a
new list "uppercase_words" with all words converted to uppercase.
Question 3: Answer: lambda x: x**2
Explanation: The lambda function takes a number "x" and returns the square of that number using the
expression "x2". The "map" function applies this lambda function to each number in the "numbers" list, creating
a new list "squared_numbers" with the square of each element.

Set Comprehension:
Set comprehensions are similar to list comprehensions, but instead of creating a new list, they create a new set.
The basic syntax for a set comprehension is:
new_set = {expression for item in iterable if condition}
Here,
 Expression is the operation you want to perform on each element of the iterable.
 Item is a variable representing each element in the iterable.
 Iterable is the iterable object you want to iterate over.
 Condition is an optional condition that filters the elements based on a Boolean expression.

You want to create a set of all the vowels in a given string.


vowels = set()
for char in "hello world":
if char in "aeiou":
vowels.add(char)
print(vowels)
Using a set comprehension, you could accomplish the same task with less code:
vowels = {char for char in "hello world" if char in "aeiou"}
print(vowels)

The set comprehension consists of three parts:


The expression char that adds each vowel to the set,
The variable char that represents each character in the string,
And the string "hello world" that specifies the characters to be checked.
Set comprehensions can also include an optional condition, which filters the elements based on a Boolean
expression. For example, if you only want to include unique vowels in your set, you could modify the set
comprehension like so:
unique_vowels = {char for char in "hello world" if char in "aeiou"}
print(unique_vowels)
set comprehension includes only unique vowels because sets cannot contain duplicate elements.

Nested Set Comprehension:


Like list comprehensions, set comprehensions can also be nested to create more complex structures. For
example, suppose you want to create a set of all the unique characters that appear in two strings.
string1 = "hello"
string2 = "world"
unique_chars = {char for char in string1 for char2 in string2 if char ==
char2}
print(unique_chars)
the outer comprehension iterates over the characters in the first string, and the inner comprehension iterates over
the characters in the second string. The result is a set of unique characters that appear in both strings.

You only want to include categories that have at least 10 books in stock. Using set comprehension, create a set
of all the unique book categories that have at least 10 books in stock.
books = [('fiction', 20), ('history', 15), ('biography', 5), ('mystery',
10), ('science', 8), ('fiction', 15), ('history', 10)]
# Using set comprehension to create a set of book categories with at least
10 books in stock
categories = {category for category, stock in books if stock >= 10}
print(categories)
Dictionary Comprehension:
Dictionary comprehension is a concise and efficient way to create a dictionary in Python. It allows you to create
a new dictionary by specifying a set of key-value pairs and an optional condition, all in a single line of code.
The basic syntax of dictionary comprehension is as follows:
{key_expression: value_expression for item in iterable if condition}

Here's a breakdown of each part of the syntax:


key_expression: This is the expression that generates the keys of the dictionary.
value_expression: This is the expression that generates the values of the dictionary.
item: This is the variable that represents each item in the iterable (e.g., list, tuple, set, etc.).
iterable: This is the collection of items that the dictionary comprehension iterates over.
condition: This is an optional expression that filters the items in the iterable based on a certain condition.

For example, let's say you have a list of names and you want to create a dictionary where the keys are the names
and the values are the lengths of the names. You can use dictionary comprehension to achieve this in a single
line of code:
names = ['Alice', 'Bob', 'Charlie', 'Dave']
name_lengths = {name: len(name) for name in names}
print(name_lengths)
{'Alice': 5, 'Bob': 3, 'Charlie': 7, 'Dave': 4}

For example, let's say you have a dictionary of students and their grades, and you want to create a new
dictionary that only includes students who received a passing grade (i.e., grade >= 60). You can use dictionary
comprehension with an if statement to achieve this:
grades = {'Alice': 80, 'Bob': 55, 'Charlie': 75, 'Dave': 90}
passing_grades = {name: grade for name, grade in grades.items() if grade >=
60}
print(passing_grades)
#This will create a new dictionary passing_grades with the following key-
value pairs:
{'Alice': 80, 'Charlie': 75, 'Dave': 90}
Suppose you have a dictionary containing the names and ages of some people. Write a Python program that uses
dictionary comprehension to generate a new dictionary containing the names of people who are over 18 years
old.
people = {'Alice': 25, 'Bob': 17, 'Charlie': 20, 'David': 30, 'Eve': 16}
# Using dictionary comprehension to filter names of people who are over 18
years old
adults = {name: age for name, age in people.items() if age > 18}
print(adults)

1. Which of the following dictionary comprehension statements contains an error?


a) {x: x**2 for x in range(5)}
b) {x: x**2 for x in [1, 2, 3, 4, 5]}
c){x: x**2 for x in [1, 2, 3, 4, 5] if x % 2 == 0}
d){x, x**2 for x in range(5)}
2. Which of the following dictionary comprehension statements is error-free and will create a dictionary
successfully?
a) {1: 'apple', 2: 'banana', 3: 'cherry'}
b) {x: x2 for x in range(1, 5)}
c) {x: x**2 for x in [1, 2, 3, 4, 5] if x % 2 == 0}
d) {x: x**2 for x in [1, 2, 3, 4, 5] if x < 3, y: y*3 for y in
range(3)}
3. Which of the following dictionary comprehension statements contains an error?
a) {x: x**2 for x in [1, 2, 3, 4, 5]}
b) {x: x**2 for x in range(5) if x % 2 == 0 else x**3}
c) {x: x**2 for x in [1, 2, 3, 4, 5] if x % 2 == 0}
d) {x: x**2 for x in [1, 2, 3, 4, 5] for y in range(2)}
4. Which of the following dictionary comprehension statements is correct and will create a dictionary
with even numbers and their squares?
a) {x: x**2 for x in range(1, 10)}
b) {x: x**2 for x in [1, 2, 3, 4, 5]}
c) {x: x**2 for x in range(10) if x % 2 == 0}

Answers:
1. Answer: d) {x, x*2 for x in range(5)}
Explanation: The correct syntax for dictionary comprehension is to use a colon (:) between the key
and value expressions. Option d) lacks the colon, making it incorrect.
2. Answer: c) {x: x**2 for x in [1, 2, 3, 4, 5] if x % 2 == 0}
Explanation: c) {x: x**2 for x in [1, 2, 3, 4, 5] if x % 2 == 0}
This dictionary comprehension is error-free and will create a dictionary successfully. It maps even
numbers to their squares.
3. Answer: b) {x: x**2 for x in range(5) if x % 2 == 0 else x**3}
Explanation: Option b) contains an error because it uses an incorrect syntax for the conditional
expression inside the dictionary comprehension. The correct syntax should use the "if" condition before
the "for" loop, or it should be avoided altogether for clarity. {x: x**2 if x % 2 == 0 else
x**3 for x in range(5)}
4. Answer: c) {x: x*2 for x in range(10) if x % 2 == 0}
Explanation: Option c) is the correct dictionary comprehension to create a dictionary with even
numbers (0, 2, 4, 6, and 8) and their corresponding values multiplied by 2. It uses the correct syntax for
filtering even numbers using the "if" condition.

Enumerate Function:
The "enumerate()" function is a powerful tool that simplifies looping over iterable objects, such as lists or
strings. It returns a tuple containing the index and the corresponding value for each element in the sequence.
This allows you to access both the index and the element during iteration, making code more concise and
readable.
fruits = ['apple', 'banana', 'orange']
for index, fruit in enumerate(fruits):
print(f"Index {index}: {fruit}")

Zip Function:
The "zip()" function is used to combine multiple iterables into a single iterable of tuples. It pairs elements from
different sequences based on their index. This is particularly useful when you want to loop through multiple lists
or strings simultaneously.
names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]
for name, score in zip(names, scores):
print(f"{name}: {score}")

Generator Expressions:
Generator expressions are similar to list comprehensions, but they generate values on-the-fly rather than creating
a new list. This makes them memory-efficient when dealing with large datasets. They are denoted by
parentheses instead of square brackets.
numbers = [1, 2, 3, 4, 5]
squared_numbers = (num**2 for num in numbers)
print(next(squared_numbers)) # Output: 1
print(next(squared_numbers)) # Output: 4

OOPs in Python

OOPs (Object-Oriented Programming) is a programming paradigm that is based on the concept of objects. An
object is an instance of a class, which encapsulates data and functionality. In Python, everything is an object,
and therefore, Python is an object-oriented language.
Object-Oriented Programming is a programming paradigm that revolves around the concept of objects, which
can contain data and code to manipulate that data. In OOPs, programs are organized around objects, which can
communicate with each other to perform tasks. OOPs is used to create reusable code, improve the code's
readability and maintainability, and increase the program's modularity and scalability.
Think of objects as real-world entities that have attributes and behaviors. For example, a car is an object that has
attributes like make, model, and color, and behaviors like accelerating, braking, and turning.
Similarly, in OOPs, we define classes that represent real-world entities. These classes have attributes and
methods that define the characteristics and behaviors of the objects created from them.
For example, consider a class called Car. This class can have attributes like make, model, and color, and
methods like accelerate(), brake(), and turn(). When we create an object of this class, say a Toyota Camry, we
can set its attributes to make='Toyota', model='Camry', and color='Red'. We can then call its methods, such as
accelerate() to make it go faster, or brake() to slow it down.For this lesson we will construct our knowledge of
OOPs in Python by building on the various topics.
The main concept of OOPs is to bind the data and the functions that work on that together as a single
unit.

Class: In Python, a class is a blueprint for creating objects. It defines a set of attributes and methods that objects
of that class will have. A class can be thought of as a template or a blueprint, and an object can be thought of as
an instance of that class.
To define a class in Python, we use the class keyword followed by the name of the class.

class MyClass:
# Class Variables
variable1 = "Hello"
variable2 = "World"
# Class Method
def myMethod(self):
print("MyClass method called.")

# Creating Object of MyClass


myObject = MyClass()

# Accessing Variables
print(myObject.variable1)
print(myObject.variable2)

# Accessing Methods
myObject.myMethod()

In this example, we define a class called MyClass. This class has two class variables (variable1 and variable2)
and a class method (myMethod). We then create an object of this class called myObject and use it to access the
class variables and method.
Note that in Python, the self parameter is used to refer to the current instance of the class. This parameter is
automatically passed to instance methods, and you should include it as the first parameter of any method you
define in a class.
Attributes:
Attributes are variables that hold information about an object. In Python, attributes are defined inside the class
definition.

class Person:
def __init__(self, name, age):
self.name = name
self.age = age

In this example, we've defined a class called Person with two attributes: name and age.
We've also defined a constructor method called __ init__() that takes two arguments, name and age. Inside the
constructor, we use the self keyword to refer to the object being created and set the name and age attributes to
the corresponding arguments.
We can now create an object of the Person class and set its attributes:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("John", 30)
print(person.name)
print(person.age)

Objects:
An object is an instance of a class. We create objects by calling the class as if it were a function, passing in any
necessary arguments. Here's an example:
person = Person()
In this example, we've created an object of the Person class called person. This object doesn't have any attributes
or methods yet because we haven't defined them in the class.
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.is_engine_on = False

def start_engine(self):
self.is_engine_on = True

def stop_engine(self):
self.is_engine_on = False

def display_info(self):
print(f"Make: {self.make}")
print(f"Model: {self.model}")
print(f"Year: {self.year}")
print(f"Engine Status: {'On' if self.is_engine_on else 'Off'}")

# Creating objects (instances) of the Car class


car1 = Car("Toyota", "Corolla", 2022)
car2 = Car("Honda", "Civic", 2023)

# Example of using attributes and methods


car1.start_engine()
car1.display_info()
car2.display_info()

Question 1: In the given Python code, what does the class Car represent?
Question 2: What are the attributes defined in the Car class?
Question 3: What is the purpose of the init method in the Car class?
Question 4: What does the method start_engine do?
Question 5: How can we create multiple instances (objects) of the Car class with different make, model, and
year?
Question 6: What is the significance of the self keyword used in the methods of the Car class?

1: The class Car represents a blueprint or template for creating car objects. It defines the common properties
(attributes) and behaviors (methods) that all car objects will have.
2: The attributes defined in the Car class are make, model, year, and is_engine_on.
3: The init method is a special method called the constructor. It is automatically executed when a new instance
of the Car class is created. It initializes the attributes of the object with the provided values.
4: The method start_engine sets the attribute is_engine_on to True, indicating that the car's engine is turned
on.
5: We can create multiple instances of the Car class by calling the class and passing different values for the
make, model, and year attributes. For example, car1 = Car("Toyota", "Corolla", 2022) and car2 =
Car("Honda", "Civic", 2023) create two instances of the Car class with different attributes.
6: The self keyword refers to the instance of the class itself. It allows the methods of the class to access and
modify the attributes specific to that instance. When calling a method of a class, Python automatically passes the
instance as the first argument (self), allowing the method to work with the object's data.

Methods:
Methods are functions that are defined inside a class and can be called on objects of that class. They are used to
perform operations on the attributes of the object or to return information about the object.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
print("Hello, my name is", self.name)
In this example, we've defined a class called Person with a method called greet(). The method takes no
arguments and prints a greeting message that includes the object's name attribute. We can now call the greet()
method on an object of the Person class:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def greet(self):
print("Hello, my name is", self.name)
person = Person("John", 30)
person.greet()
Hello, my name is John

You create a Calculator class that has methods for each of the basic arithmetic operations (add, subtract,
multiply, divide) as well as a clear method. The class stores a result attribute that holds the current result of the
calculator operations. Students can use this calculator to help them with their math problems, and you can use it
to double-check their answers.
class Calculator:
# Define the constructor method to initialize the class and set the
initial result to 0
def __init__(self):
self.result = 0

# Define a method to add num1 to num2 and store the result


def add(self, num1, num2):
self.result = num1 + num2
return self.result

# Define a method to subtract num2 from num1 and store the result
def subtract(self, num1, num2):
self.result = num1 - num2
return self.result

# Define a method to multiply num1 by num2 and store the result


def multiply(self, num1, num2):
self.result = num1 * num2
return self.result

# Define a method to divide num1 by num2 and store the result. Raise a
ValueError if dividing by 0.
def divide(self, num1, num2):
if num2 == 0:
raise ValueError("Cannot divide by zero.")
self.result = num1 / num2
return self.result

# Define a method to clear the result back to 0


def clear(self):
self.result = 0
return self.result

In this example, we define a class called Calculator. The init method initializes the result attribute to 0. The
class has four methods for performing basic arithmetic operations: add, subtract, multiply, and divide. Each
method takes a single argument num, which is the number to perform the operation with. The divide method
checks for a divide by zero error and raises a ValueError if the input is zero.
The class also has a clear method that sets the result attribute to 0, effectively resetting the calculator.
calc=Calculator()
#calling add method
addition=calc.add(42,6)

#calling subtract method


subtraction=calc.subtract(42,6)

#calling divide method


division=calc.divide(42,6)

#calling multiply method


mulitplication=calc.multiply(42,6)

print("Addition- ",addition)
print("subtraction- ",subtraction)
print("division- ",division)
print("mulitplication- ",mulitplication)

You have been asked to develop a virtual pet program that simulates the experience of owning a pet. The
program should allow users to create a new pet, feed it, play with it, and monitor its health. Each pet should
have a name, age, and initial health level.
To implement this program, you decide to create a class called Pet that represents a virtual pet. The Pet class has
attributes for the pet's name, age, and health level. It also has methods for feeding the pet, playing with the pet,
and checking the pet's health.
class Pet:
# Define the constructor method to initialize the class with name, age and health_level
def __init__(self, name, age, energy_level):
self.name = name
self.age = age
self.energy_level = energy_level

# Define a method to increase health level when feeding the pet


def feed(self):
self.energy_level += 10

# Define a method to decrease health level when playing with the pet
def play(self):
self.energy_level -= 5

# Define a method to check the health of the pet and print a message based on its health level
def check_health(self):
if self.energy_level < 50:
print("Your pet is not feeling well.")
else:
print("Your pet is healthy.")

In this example, we define a class called Pet. The init method initializes the pet's name, age, and health_level
attributes. The class has three methods for interacting with the pet: feed, play, and check_health.
The feed method increases the pet's health level by 10, while the play method decreases the health level by 5.
The check_health method checks the pet's current health level and prints a message indicating whether the pet
is healthy or not.
Create an object of Pet class and demonstrate the usage of it's methods
# Creating Object of Pet Class
my_pet = Pet("Fluffy", 2, 50)
# Feeding Pet
my_pet.feed()
# Playing with Pet
my_pet.play()
# Checking Pet Health
my_pet.check_health()

# Printing Pet Info


print("Name:", my_pet.name)
print("Age:", my_pet.age)
print("Energy Level:", my_pet.energy_level)
Polymorphism is a core concept in object-oriented programming, which allows objects of different classes to be
treated as if they are objects of the same class. This means that different objects can respond to the same
message or method in different ways, depending on their individual implementation of that method.
For example, consider a program that models different types of animals. We can define a base class called
Animal with a method called make_sound(), which simply prints the sound that the animal makes.
class Animal:
def make_sound(self):
print("Unknown sound")

We can then define two subclasses of Animal: Dog and Cat. Both of these subclasses inherit the make_sound()
method from the Animal class, but they provide their own implementation of this method.
class Dog(Animal):
def make_sound(self):
print("Bark")
class Cat(Animal):
def make_sound(self):
print("Meow")

we can create instances of both the Dog and Cat classes and call their make_sound() methods. Even though both
of these methods have the same name, they produce different outputs, depending on the type of animal.
dog = Dog()
cat = Cat()
goat = Animal()
dog.make_sound()
cat.make_sound()
goat.make_sound()

This is an example of polymorphism, as we are able to treat objects of different classes (Dog and Cat) as if they
are objects of the same class (Animal). This allows us to write more flexible and reusable code, as we can create
generic functions that operate on any object that has a make_sound() method, regardless of its specific type.

You are building a shape calculator program that allows users to calculate the area of various shapes, including
squares, rectangles, and circles. Each shape has a different formula for calculating its area and perimeter.
To implement this program, you decide to use polymorphism, where each shape is represented by its own class
with its own methods for calculating area. The program should be able to accept inputs for the dimensions of the
different shapes and calculate their respective areas.
class Shape:
# Define abstract methods to calculate area and perimeter. Will be implemented by child classes.
def area(self):
pass

def perimeter(self):
pass

class Square(Shape):
# Define the constructor method to initialize the class with the length of the side
def __init__(self, side):
self.side = side

# Override the area method to calculate the area of the square


def area(self):
return self.side ** 2

class Rectangle(Shape):
# Define the constructor method to initialize the class with the width and height
def __init__(self, width, height):
self.width = width
self.height = height

# Override the area method to calculate the area of the rectangle


def area(self):
return self.width * self.height

class Circle(Shape):
# Define the constructor method to initialize the class with the radius
def __init__(self, radius):
self.radius = radius

# Override the area method to calculate the area of the circle


def area(self):
return 3.14 * self.radius ** 2

In this example, we define a base class Shape that has two methods, area and perimeter. We then create three
child classes, Square, Rectangle, and Circle, that inherit from the Shape class and override the area and
perimeter methods with their own formulas:
# Creating Objects of Shape Classes
square = Square(5)
rectangle = Rectangle(3, 4)
circle = Circle(2)

# Calculating Area and Perimeter of Shapes


print("Area of Square:", square.area())
print("Area of Rectangle:", rectangle.area())
print("Area of Circle:", circle.area())

Create a Bank Account Management System using Object-Oriented Programming (OOPs) in Python. The
system should allow users to create and manage bank accounts, deposit and withdraw funds, and view account
details.
# Define a BankAccount class
class BankAccount:

# Constructor to initialize account details


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

# Method to deposit funds into the account


def deposit(self, amount):
self.balance += amount
print("Deposit successful. Your new balance is:", self.balance)

# Method to withdraw funds from the account


def withdraw(self, amount):
if amount <= self.balance:
self.balance -= amount
print("Withdrawal successful. Your new balance is:", self.balance)
else:
print("Insufficient balance. Your current balance is:", self.balance)

# Method to view account details


def account_details(self):
print("Name:", self.name)
print("Account Number:", self.account_number)
print("Balance:", self.balance)
# Test Case
# Create a new bank account
account1 = BankAccount("John Doe", "1234567890", 1000)
# Test deposit method
account1.deposit(500)
# Test withdraw method
account1.withdraw(2000)
# Test account details method
account1.account_details()

Inheritance:
Inheritance is a core concept in object-oriented programming, which allows a new class to be based on an
existing class. Inheritance provides a way to create a new class by absorbing the properties and behaviors of an
existing class, while also allowing for the new class to add or modify its own unique properties and behaviors.
Let's consider an example of inheritance using a program that models different types of vehicles. We can define
a base class called Vehicle that contains common properties and methods that are shared by all types of vehicles.
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year

def start_engine(self):
print("Starting engine...")

We can then define a subclass called Car that inherits from the Vehicle class and adds its own unique properties
and methods.
class Car(Vehicle):
def __init__(self, make, model, year, num_doors):
super().__init__(make, model, year)
self.num_doors = num_doors

def drive(self):
print("Driving the car...")

In this example, the Car class inherits from the Vehicle class using the super() function. This means that the Car
class will have access to all the properties and methods of the Vehicle class. The init() method of the Car class
overrides the init() method of the Vehicle class to include an additional parameter for the number of doors. The
drive() method is unique to the Car class and is not present in the Vehicle class.
Now, we can create instances of both the Vehicle and Car classes and call their respective methods.
vehicle = Vehicle("Toyota", "Corolla", 2022)
car = Car("Tesla", "Model S", 2021, 4)

vehicle.start_engine() # prints "Starting engine..."


car.start_engine() # prints "Starting engine..."

#vehicle.drive() # This will produce an error, as the drive() method is not defined for the Vehicle class
car.drive()

As we can see from this example, the Car class inherits properties and methods from the Vehicle class and adds
its own unique functionality. This allows us to write more concise and modular code, as we can define generic
behavior in the base class and specialize that behavior in the subclasses.

Question 1: What is the possible issue with the following code snippet?
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height

def area(self):
return self.width * self.height

class Square(Rectangle):
def __init__(self, side):
self.side = side
super().__init(side, side)

rectangle = Rectangle(5, 3)
square = Square(4)
print(rectangle.area())
print(square.area())
Answer: The error is coming in super().__init(side,side)
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height

def area(self):
return self.width * self.height

class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side) # Call the superclass's __init__ method
self.side = side
rectangle = Rectangle(5, 3)
square = Square(4)
print(rectangle.area())
print(square.area())

Question 2: What is the potential problem with the following code snippet that demonstrates inheritance and
polymorphism in Python?
class Shape:
def area(self):
pass

class Circle(Shape):
def __init__(self, radius):
self.radius = radius

def area(self):
return 3.14 * self.radius * self.radius

class Square(Shape):
def __init__(self, side):
self.side = side

def area(self):
return self.side * self.side

def print_area(shape):
print("Area:", shape.area())

circle = Circle(5)
square = Square(4)
print_area(circle)
print_area(square)

print_area(triangle)
Answer:
The error in that code is we are calling undefined method
class Shape:
def area(self):
pass

class Circle(Shape):
def __init__(self, radius):
self.radius = radius

def area(self):
return 3.14 * self.radius * self.radius

class Square(Shape):
def __init__(self, side):
self.side = side

def area(self):
return self.side * self.side

def print_area(shape):
print("Area:", shape.area())

circle = Circle(5)
square = Square(4)
print_area(circle)
print_area(square)

Encapsulation:
Encapsulation is one of the fundamental concepts in object-oriented programming (OOP), including in Python.
It refers to the bundling of data (attributes) and methods (functions) that operate on the data into a single unit or
class. Encapsulation also restricts direct access to some of an object's components, which is a means of
preventing accidental interference and misuse of the methods and data.
Here's a detailed look at encapsulation in Python:
1. Bundling Data and Methods: In encapsulation, data and the methods that manipulate that data are
kept together in a single entity, typically a class. This makes the code more organized and modular.
Each object created from the class can have its own attributes and methods, providing a clear structure
for organizing functionality.
2. Access Control: Encapsulation also involves controlling access to the internal state of the object. This
is usually achieved through access modifiers, which determine how and from where the class's
attributes and methods can be accessed.
o Public Access: Members (methods or attributes) of a class that are available from outside the
class. In Python, all members are public by default.
o Private Access: Members that are only available within the class itself. You can make a
member private in Python by prefixing its name with double underscores (__). Private
members cannot be accessed directly from outside the class but can be accessed within the
class.
o Protected Access: Members that are accessible within the class and its subclasses. Python
uses a single underscore (_) prefix to denote protected members. However, this is more of a
convention, and Python does not enforce strict access control.

class Car:
def __init__(self, model, year):
self.model = model # Public Attribute
self.__year = year # Private Attribute (with double underscores)

def get_car_year(self): # Public Method


return self.__year

my_car = Car("Toyota", 2021)


print(my_car.model) # Accessible
print(my_car.get_car_year()) # Accessible

# Attempting to directly access or modify the private attribute:


# print(my_car.__year) # Not accessible, will raise an AttributeError

# Attempting to modify the private attribute:


my_car.__year = 2023 # This doesn't actually change the private attribute
print(my_car.get_car_year()) # The original private attribute value is retained
In this example, model is a public attribute, while __year is private. The method get_car_year is public and can
be used to access the private attribute.
Encapsulation in Python promotes a modular approach to development, enhances code security, and prevents
data corruption by restricting direct access to an object's internal state. It's a key aspect of writing maintainable,
structured, and robust code in object-oriented programming.
Attempting to modify the private attribute:
my_car.__year = 2023 # This doesn't actually change the private attribute
print(my_car.get_car_year()) # The original private attribute value is retained
when you attempt to set my_car.year to 2023, it does not change the private attribute __year. Instead,
Python treats my_car.year = 2023 as creating a new public attribute __year on the my_car instance. This
is due to Python's name mangling feature for private attributes. The original private attribute __year
defined inside the class remains unchanged and inaccessible directly from outside the class. As a result,
my_car.get_car_year() will still return the original value (2021), demonstrating the encapsulation
principle.
Data Abstraction:
Data abstraction is a core concept in object-oriented programming that refers to the practice of representing
complex real-world objects using simpler, more abstract models. Abstraction allows us to focus on the essential
features of an object while hiding the irrelevant or complex details.
Abstraction Method:
Abstraction can be achieved in two ways:
1. Abstract Classes: An abstract class is a class that cannot be instantiated and is often used to define a
blueprint for other classes. It can contain abstract methods as well as concrete methods. An abstract
method is declared but not implemented; the derived classes are responsible for providing a specific
implementation of these methods.
2. Interfaces: An interface is similar to an abstract class and is used to specify a set of methods that the
implementing class must implement. However, unlike abstract classes, interfaces cannot contain
concrete methods (methods with an implementation).
Demonstration:
To demonstrate abstraction in Python (which doesn't have interfaces in the same way as languages like Java or
C#), let’s use an abstract class:
Suppose we have a scenario of different types of vehicles where each vehicle has a different way of starting:
from abc import ABC, abstractmethod

class Vehicle(ABC):
@abstractmethod
def start(self):
pass

class Car(Vehicle):
def start(self):
print("Starting car with key")

class Motorcycle(Vehicle):
def start(self):
print("Starting motorcycle with button")

# Instantiate the objects


car = Car()
motorcycle = Motorcycle()
# Use the objects
car.start() # Outputs: Starting car with key
motorcycle.start() # Outputs: Starting motorcycle with button

In this example:
 Vehicle is an abstract class that defines an abstract method start(). This method acts as a template for
the start method that must be implemented in any subclass of Vehicle.
 Car and Motorcycle are concrete classes that inherit from Vehicle and provide specific
implementations of the start method.
 The actual implementation details of starting the vehicle are abstracted away. When you call start() on
a Car or Motorcycle object, you don’t need to know the internal details of how each vehicle starts.
You only need to know that they can start, which is the essence of abstraction.
Key Points:
 Abstraction helps in reducing programming complexity and effort by hiding the unnecessary details
from the user.
 It allows the programmer to focus on interactions at a higher level.
 It is achieved through abstract classes and interfaces in many OOP languages.
 Abstract methods define a common interface for different implementations in the derived classes.

Here's what happens when abstract methods are not defined while creating a child class:
#from abc import ABC, abstractmethod
class Vehicle(ABC):
@abstractmethod
def start(self):
pass

Creating a Child Class Without Implementing the Abstract Method:


class Car(Vehicle):
pass

Here, Car is a child class of Vehicle, but it doesn't implement the start() method.
Instantiating the Child Class:
my_car = Car()
TypeError: Can't instantiate abstract class Car with abstract method start
1. Result:
o When you try to instantiate Car, Python will raise a TypeError because Car has not
implemented the abstract method start() defined in its parent class Vehicle.
o The error message will typically say something like: "Can't instantiate abstract class Car
with abstract methods start".
2. Key Points:
o An abstract class is more like a template for its subclasses. It ensures that certain methods are
implemented in child classes.
o If a child class of an abstract class does not implement all the abstract methods, it cannot be
instantiated. This enforces a contract that the child classes must follow.
o This behavior is crucial for maintaining a consistent interface and ensuring that certain
functionalities are provided by all subclasses.

Exception Handling & Logging

In programming, errors and exceptions refer to problems that occur when a program is running. Error and
exception handling is a technique used to detect and manage these problems, making the program more reliable
and easier to use.Error and exception handling is an important part of programming in Python. It allows you to
handle errors and unexpected situations in a graceful and controlled manner, instead of crashing the program.
Types of Errors:

In Python, an error is a type of issue


that prevents your program from executing successfully. Errors can occur for a variety of reasons, such as syntax
errors, logical errors, or runtime errors. Understanding the different types of errors in Python can help you to
write more effective and efficient code.

Syntax Error:

# 1. Missing Parenthesis
print("Hello, world!"
SyntaxError: unexpected EOF while parsing

# 2. Misspelled Keyword
x=5
prnit(x)
NameError: name 'prnit' is not defined

# 3. Incorrect Indentation
for i in range(10):
print(i)
IndentationError: expected an indented block

# 4. Missing Colon
if x == 5
print("x is equal to 5")
SyntaxError: invalid syntax

Logical Errors:
Logical errors, also known as semantic errors, occur when the code runs without generating any syntax errors
but produces unexpected or incorrect output due to flawed reasoning or faulty assumptions in the code. Logical
errors can be difficult to identify since the program still runs, but the output may not match the intended result.
# 1. Division by zero
a=5
b=0
c = a/b
print(c)
ZeroDivisionError: division by zero

# 2. Incorrect variable types


a=5
b = "10"
c=a+b
print(c)
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Guess the type of error:


Question 1:
num1 = 5
num2 = 0
result = num1 / num2
ZeroDivisionError: division by zero
Question 2:
def calculate_area(length, width):
area = length * width
return area

result = calculate_area(5, 4)
print("The area is: ", result)
print("The perimeter is: ", result * 2)
No Error
Question 3:
name = "John"
age = 25
result = "My name is " + name + " and I am " + age + " years old."
TypeError: can only concatenate str (not "int") to str

1. Answer: B) Logical Error


Explanation: This code will raise a logical error because it attempts to perform division by zero (num1 /
num2) which is not allowed in mathematics. The program logic is flawed, causing a logical error.
2. Answer: C) No Error
Explanation: This code will not raise any errors. It defines a function calculate_area() to calculate the
area of a rectangle based on its length and width. The function is correctly called with arguments 5 and
4 to calculate the area, and the result is printed. The program then calculates and prints the perimeter of
the same rectangle by multiplying the area by 2, which is also correct. There are no syntax or logical
errors in this code.
3. Answer: A) Syntax Error
Explanation: This code will raise a syntax error because it attempts to concatenate a string (name) with
an integer (age) using the + operator. In Python, you cannot directly concatenate different data types
without converting them to the same type first.

Code with bugs:

# 1. Intentional error code


# Intentional error code
# Spot two errors in this code
def calculate_average(numbers)
total = 0
for number in numbers:
total += number
avg = total / len(numbers
print("The average is: ", avg)
calculate_average([2, 4, 6, 8])
Syntax Error: There is a missing colon (:) after the function definition, and there is a missing closing
parenthesis ")" after len(numbers in the line avg = total / len(numbers.
Indentation Error: The function body lacks proper indentation.

# 2. Intentional error code


# Spot one error in this code
def find_max(numbers):
max_num = 0
for num in numbers:
if num > max_num
max_num = num
return max_num
print(find_max([3, 9, 1, 7, 4]))
Syntax Error: There is a missing colon (:) after the if statement in the line if num > max_num.

# Intentional error code


# Spot two errors in this code
def calculate_sum(numbers):
sum = 0
for num in numbers
sum += numbers
print("The sum is: " sum)
calculate_sum([1, 2, 3, 4, 5])
Syntax Error: There is a missing colon (:) after the for statement in the line for num in numbers.
NameError: The variable numbers is used instead of the variable num in the line sum += numbers.

Handling Exceptions in Python:


In Python, an exception is an error that occurs during the execution of a program, interrupting the normal flow
of the program. It happens when a statement or expression in Python code raises an error or an unexpected
condition occurs that the program is not able to handle. When an exception is raised, the program stops
executing and Python interpreter looks for an appropriate exception handler to handle the exception. If no
exception handler is found, the program crashes with an error message.
Let's understand through a scenario.
Suppose you are driving your car on a road, and suddenly a deer jumps in front of your car, and you have to
apply the brakes immediately to avoid hitting the deer. But when you apply the brakes, you realize that they are
not working correctly, and the car does not slow down as expected. In this situation, you have to take immediate
action to avoid the collision, like steering the car to a safer spot or using the handbrake to slow down the car.
This situation is similar to an exception in Python. The deer in front of the car is like an unexpected condition or
error that the program is not able to handle. The faulty brakes are like a bug in the program that causes it to
malfunction. Just like how the driver had to take immediate action to avoid the collision, a program may try to
recover from an exception by taking appropriate actions like logging the error, displaying an error message, or
terminating the program gracefully.
1. Exception Handling:
Python allows you to handle exceptions using the try-except statement. The try block contains the code that may
raise an exception, while the except block contains the code that is executed when an exception is raised.
In Python, exceptions can be handled using the try-except block.
Here's the basic syntax:
try:
# code that may raise an exception
except ExceptionType:
# code that handles the exception

The try block contains the code that may raise an exception, and the except block contains the code that handles
the exception. ExceptionType is the type of exception that you want to handle. You can replace ExceptionType
with specific exception types like ZeroDivisionError, ValueError, etc. to handle those specific exceptions.
Here's an example of how to handle a ZeroDivisionError exception:
try:
x = 10
y=0
z = x/y
except ZeroDivisionError:
print("Error: Cannot divide by zero")

2. Raise Exception:
In Python, you can raise an exception using the raise keyword. When you raise an exception, you are indicating
that an error or exceptional situation has occurred in your program.
Here's the basic syntax for raising an exception:
raise ExceptionType("Error message")
ExceptionType is the type of exception that you want to raise, and "Error message" is the message that you want
to associate with the exception.

When using the "raise" keyword in Python, you can specify different types of errors to be raised. Here are some
examples:
1. SyntaxError: This error occurs when there is a problem with the syntax of your code. For example, if
you forget to close a parenthesis or use an invalid operator, you might see a SyntaxError.
2. ValueError: This error occurs when there is a problem with the value of a variable or argument. For
example, if you try to convert a string to an integer but the string contains non-numeric characters, you
might see a ValueError.
3. TypeError: This error occurs when you try to perform an operation on two objects that are not
compatible. For example, if you try to add a string and an integer, you might see a TypeError.
4. IndexError: This error occurs when you try to access an index that is out of range. For example, if you
try to access the 5th element of a list that only has 3 elements, you might see an IndexError.
5. KeyError: This error occurs when you try to access a dictionary key that does not exist. For example,
if you try to access the value for a key that is not in the dictionary, you might see a KeyError.
By using the "raise" keyword with these error types, you can create custom error messages that help you debug
your code and handle unexpected situations.
3. Finally Block:
In Python, the finally block is used in conjunction with the try and except blocks to provide a cleanup
mechanism for resources that need to be released, regardless of whether an exception was raised or not.
Here's the basic syntax for using the finally block:
try:
# code that may raise an exception
except ExceptionType:
# code that handles the exception
finally:
# code that will always be executed, regardless of whether an exception
was raised or not
The finally block contains code that will always be executed, regardless of whether an exception was raised or
not. This block is useful for releasing resources like file handles, database connections, network sockets, etc.
Here's an example:
In the Code below, we are asking the user to input a number, and then we are dividing 10 by that number. If the
user enters 0, the code will raise a ZeroDivisionError.
We have enclosed the division code in a try block, and in the except block, we are printing an error message if
the user enters 0.
Regardless of whether an exception is raised or not, we want to print a message to thank the user for using the
program. So, we have added a finally block to print the message.

If the user enters a non-zero number, the code in the try block will be executed, and then the finally block will
be executed to print the message.
try:
num = int(input("Enter a number: "))
result = 10 / num
print("The result is:", result)
except ZeroDivisionError:
print("Error: Cannot divide by zero")
finally:
print("Thank you for using the program!")

If the user enters 0, the except block will be executed to print the error message, and then the finally block will
be executed to print the message.
try:
num = int(input("Enter a number: "))
result = 10 / num
print("The result is:", result)
except ZeroDivisionError:
print("Error: Cannot divide by zero")
finally:
print("Thank you for using the program!")

In this example, the program prompts the user to enter two numbers and then divides them. The try block
contains the code that may raise an exception, while the except blocks handle the exceptions that may occur.
The finally block is executed regardless of whether an exception is raised or not and prints a message indicating
that the program has ended.
try:
x = int(input("Enter a number: "))
y = int(input("Enter another number: "))
z = x / y
print("The result is:", z)
except ZeroDivisionError:
print("You cannot divide by zero!")
except ValueError:
print("You must enter a number!")
except Exception as e:
print("An error occurred:", e)
finally:
print("The program has ended.")

Write a program that prompts the user to enter a temperature value in Fahrenheit. The program will then convert
the temperature to Celsius and display the result. If the user enters a non-numeric value, the program will
display an error message.
while True:
try:
fahrenheit = float(input("Enter temperature in Fahrenheit: "))
celsius = (fahrenheit - 32) * 5/9
print("The temperature in Celsius is", round(celsius, 2))
except ValueError:
print("Invalid input. Please enter a numeric value.")

Write a program that prompts the user to enter a song request. If the requested song is in your playlist, the
program will display a confirmation message. If the requested song is not in your playlist, the program will
display an error message.
playlist = ["Wild Thoughts", "All I Do Is Win", "No Brainer", "I'm the
One"]
while True:
song_request = input("Enter your song request (or type 'q' to quit): ")

if song_request.lower() == "q":
print("Exiting the program.")
break

try:
if song_request in playlist:
print(f"Thanks for requesting \"{song_request}\". It's in my
playlist!")
else:
print(f"Sorry, \"{song_request}\" is not in my playlist.")
except Exception as e:
print("An error occurred:", e)
Enter your song request (or type 'q' to quit): Wild Thoughts
Thanks for requesting "Wild Thoughts". It's in my playlist!
Enter your song request (or type 'q' to quit): Another one
Sorry, "Another one" is not in my playlist.
Enter your song request (or type 'q' to quit): q
Exiting the program.
Write a program that takes the customer's order for a taco and sides. If the order is valid, the program will
display a confirmation message and the total cost. If the order is not valid, the program will display an error
message and ask the customer to try again.
Welcome to the Mexican Restaurant!
Please enter your order.

Taco (chicken or vegetable)? chicken


Sides (chips, salsa, or guacamole)? chips, guacamole
Drink (coke, pepsi, or sprite)? coke
Size (small, medium, or large)? medium

Your order:
- Taco: chicken
- Sides: chips, guacamole
- Drink: coke
- Size: medium

Total cost: $9.50

If the user enters an invalid input, such as a misspelled word or an unsupported option, the program should
display an error message and ask the customer to try again.
def validate_order(item, options):
while True:
user_input = input(f"{item} ({', '.join(options)})? ").lower()
if user_input in options:
return user_input
else:
print("Invalid input. Please try again.")

def calculate_total_cost(taco, sides, drink, size):


# Define the prices for each item
taco_prices = {"chicken": 5.50, "vegetable": 4.50}
sides_prices = {"chips": 2.00, "salsa": 1.50, "guacamole": 3.00}
drink_prices = {"coke": 1.50, "pepsi": 1.50, "sprite": 1.50}
size_prices = {"small": 0.00, "medium": 1.00, "large": 2.00}

try:
# Calculate the total cost
total_cost = taco_prices[taco] + sum(sides_prices[side] for side in
sides) + drink_prices[drink] + size_prices[size]
return total_cost
except KeyError as e:
print(f"Error: {e}")
return None

print("Welcome to the Mexican Restaurant!")


print("Please enter your order.")
# Get the customer's order for taco
taco_options = ["chicken", "vegetable"]
taco = validate_order("Taco ", taco_options)

# Get the customer's order for sides


sides_options = ["chips", "salsa", "guacamole"]
sides = validate_order("Sides ", sides_options).split(", ")

# Get the customer's order for drink


drink_options = ["coke", "pepsi", "sprite"]
drink = validate_order("Drink ", drink_options)

# Get the customer's order for size


size_options = ["small", "medium", "large"]
size = validate_order("Size ", size_options)

# Check if all inputs are valid and display the customer's order and total
cost
if all([taco, sides, drink, size]):
print("\nYour order:")
print(f"- Taco: {taco}")
print(f"- Sides: {', '.join(sides)}")
print(f"- Drink: {drink}")
print(f"- Size: {size}\n")
total_cost = calculate_total_cost(taco, sides, drink, size)
if total_cost is not None:
print(f"Total cost: ${total_cost:.2f}")
else:
print("Invalid input. Please try again.")
Welcome to the Mexican Restaurant!
Please enter your order.
Taco (chicken, vegetable)? vegetable
Sides (chips, salsa, guacamole)? salsa
Drink (coke, pepsi, sprite)? sprite
Size (small, medium, large)? medium
Your order:
- Taco: vegetable
- Sides: salsa
- Drink: sprite
- Size: medium
Total cost: $8.50

Question 1:
try:
# Code block where an exception may occur
num1 = int(input("Enter a number: "))
num2 = int(input("Enter another number: "))
result = num1 / num2
except _______:
print("An error occurred while performing the division.")
Question 2:
try:
# Code block where an exception may occur
file = open("myfile.txt", "r")
content = file.read()
file.close()
except _______:
print("An error occurred while reading the file.")
Question 3:
try:
# Code block where an exception may occur
data = [1, 2, 3, 4, 5]
index = int(input("Enter an index: "))
value = data[index]
except _______:
print("An error occurred while accessing the list")

1. Answer: A) ZeroDivisionError
Explanation: In the given code, the except block is catching the ZeroDivisionError exception. This
exception occurs when attempting to divide a number by zero, which can happen if the user enters 0 as
the value for num2.
2. Answer: A) FileNotFoundError
Explanation: In the given code, the except block is catching the FileNotFoundError exception. This
exception occurs when the file specified in the open() function does not exist or cannot be found.
3. Answer: A) IndexError
Explanation: In the given code, the except block is catching the IndexError exception. This exception
occurs when trying to access an index that is out of range in a list. For example, if the user enters an
index greater than the length of the list data, it will raise an IndexError.

Logging and debugging are tools to help you improve the quality of your software and ensure that it runs
smoothly.
Logging allows you to track application behavior, monitor performance, and diagnose problems, while
debugging helps you identify and fix errors in your code. Together, these tools can help you create more reliable
and efficient software that meets the needs of your users.
By using logging and debugging effectively, you can gain greater insight into how your software works, identify
areas for improvement, and resolve issues quickly and efficiently. This can save you time, money, and
headaches in the long run, while also improving the overall user experience of your software.

Logging:
Logging is the process of recording events and messages in software applications to help developers understand
what is happening during program execution. It's like keeping a detailed journal of everything that happens,
from the moment the application starts running to when it stops. Just like a journal helps you remember what
you did and when you did it, logging helps developers understand what the application did and when it did it, as
well as any errors or issues that may have occurred.
Logging can be used to track application behavior, monitor performance, and diagnose problems. Developers
can use logging to understand how the application is being used, what parts of the code are being executed, and
how long each step is taking. This information can be invaluable in identifying bottlenecks and optimizing
performance.
In Python, logging is implemented using the built-in logging module. Here's a basic example of how to use the
logging module to log messages in Python:
import logging # This is how we call premade libraries. You will
learn more.

# Set the logging configuration


logging.basicConfig(filename='example.log', level=logging.DEBUG,
format='%(asctime)s %(levelname)s: %(message)s')

def divide(x, y):


try:
result = x / y
except ZeroDivisionError:
logging.error("Tried to divide by zero. x=%s, y=%s", x, y)
else:
logging.info("Division successful. x=%s, y=%s, result=%s", x, y,
result)
return result

# Test the divide function


print(divide(10, 2)) # Output: 5.0
print(divide(10, 0)) # Output: None
In this example, we use the built-in logging module to log messages to a file named example.log. We set the
logging level to DEBUG, which means that all log messages with severity level DEBUG or higher
(i.e., DEBUG, INFO, WARNING, ERROR, and CRITICAL) will be recorded.
We define a function divide that takes two arguments x and y, and attempts to divide x by y. If y is zero,
a ZeroDivisionError is raised, and we log an error message using the logging.error method. If the division is
successful, we log an info message using the** logging.info** method, and return the result.
We then test the divide function by calling it twice: once with x=10 and y=2, and once with x=10 and y=0. The
first call should return 5.0 and log an info message, while the second call should return None and log an error
message.
In Python's logging module, there are several logging levels that can be used to indicate the severity of a log
message. These levels are used to control which log messages are output by the logger, based on the minimum
logging level that has been set.
Here are the logging levels in Python, in increasing order of severity:
 DEBUG: Detailed information, typically of interest only when diagnosing problems.
 INFO: General information about the program's execution, such as startup and shutdown messages.
 WARNING: An indication that something unexpected or potentially problematic has occurred, but the
program is able to continue running.
 ERROR: An indication that an error has occurred, and the program may not be able to continue
running.
 CRITICAL: A very severe error, indicating that the program may be unable to continue running.
When you set the logging level, only messages with that level or higher will be displayed. For example,
if you set the logging level to INFO, you will see log messages with the levels INFO, WARNING,
ERROR, and CRITICAL, but not DEBUG messages.
You can set the logging level in Python by calling the basicConfig() function from the logging module, like this:
import logging
logging.basicConfig(level=logging.INFO)

Debugging:
Debugging is the process of removing bugs, or errors, from software code. It's like being a detective, where the
code is the crime scene, and the bugs are the clues that lead you to the culprit. Just like a detective must use their
skills and intuition to solve a case, a programmer must use their knowledge of coding languages and debugging
tools to find and fix errors in their code. And just like a detective may have to retrace their steps or gather more
evidence to solve a case, a programmer may have to test different scenarios or gather more data to identify and
resolve a bug.

Debug the program given below and correct the errors. The program should ask the user to enter the grades of
five subjects, calculate the average grade, and print it out.
grades = []
for i in range(5):
grade = int(input("Enter the grade for subject " + str(i+1) + ":"))
grades.append(grade)

average = sum(grades) / len(grades)


print("Your average grade is: " + str(average))
Runtime Errors:
Runtime errors occur when the program runs and encounters an unexpected condition or situation that prevents
it from executing further. These errors occur during the execution of the program and often cause the program to
terminate abruptly or produce unexpected results.
Here are some examples of runtime errors:
# 1. Type errors
a = 5
b = "Hello"
c = a + b
print(c)
TypeError: unsupported operand type(s) for +: 'int' and 'str'

# 2. Name errors
a = 5
print(z)
NameError: name 'z' is not defined

# 3. Index errors
a = [1, 2, 3]
print(a[3])
IndexError: list index out of range

# 4. Assertion errors
x = 5
assert x > 10, "x should be greater than 10"
AssertionError: x should be greater than 10

Coding Best Practices:


Use descriptive variable names
When you're naming your variables, try to make them as descriptive as possible. This will help you and other
developers understand what the variable represents without having to dig into the code. For example, instead of
using x as a variable name, you could use num_of_apples.
Avoid magic numbers
Magic numbers are hard-coded values that are used throughout the code, but don't have any obvious meaning.
Instead of using magic numbers, define constants at the beginning of your code that represent those values. For
example, instead of using if x == 7, you could define SEVEN = 7 at the beginning of your code and use if x ==
SEVEN instead.
Use comments wisely
Comments are a great way to explain what your code is doing and why, but they can also be overused. Try to
limit your comments to areas where the code might be difficult to understand, and make sure they add value. For
example, instead of commenting # increment x, you could use x += 1 which is self-explanatory.
Keep your code organized
Organize your code in a logical way by breaking it up into functions, classes, and modules. This will make it
easier to read, maintain, and test. For example, instead of putting all of your code in one file, you could create a
separate module for each major component of your program.
Use list comprehensions
List comprehensions are a concise way to create lists in Python. They're easy to read and can help you avoid
using for-loops for simple operations. For example, instead of using a for-loop to create a list of squared
numbers, you could use a list comprehension like this:
[x**2 for x in range(10)]
Handle exceptions gracefully
When writing code that might raise an exception, use try-except blocks to handle those exceptions gracefully.
This will help prevent your program from crashing unexpectedly. For example, instead of using print(1/0), you
could use a try-except block like this:

try:
print(1/0)
except ZeroDivisionError:
print("Cannot divide by zero!")

You might also like