Python Unit II
Python Unit II
IF CONDITION
The if condition is considered the simplest of the three and makes a decision based on
whether the condition is true or not. If the condition is true, it prints out the indented expression.
If the condition is false, it skips printing the indented expression.
if condition:
expression
Example of if
Suppose you have a variable z, equal to 4. If the value is 'even', you will print z is 'even'.
You will use modulo operator 2, which will return 0 if z is 'even'. As soon as you run the below
code, Python will check if the condition holds. If True, the corresponding code will be executed.
z=4
if z % 2 == 0: # True
print("z is even")
OUTPUT
z is even
z=4
if z % 2 == 0:
print("checking" + str(z))
print("z is even")
OUTPUT
checking 4
1
z is even
z=5
if z % 2 == 0: # False
print("z is even")
IF-ELSE CONDITION
The if-else condition adds an additional step in the decision-making process compared to
the simple if statement. The beginning of an if-else statement operates similar to a
simple if statement; however, if the condition is false, instead of printing nothing, the indented
expression under else will be printed.
if condition:
expression
else:
expression
Example of if-else
Continuing our previous example, what if you want to print 'z is odd' when
the if condition is false? In this case, you can simply add another condition, which is
the else condition. If you run it with z equal to 5, the condition is not true, so the expression for
the else statement gets printed out.
z=5
if z % 2 == 0:
print("z is even")
else:
2
print("z is odd")
OUTPUT
z is odd
IF-ELIF-ELSE CONDITION
The most complex of these conditions is the if-elif-else condition. When you run into a
situation where you have several conditions, you can place as many elif conditions as necessary
between the if condition and the else condition.
if condition:
expression
elif condition:
expression
else:
expression
Here, since z equals 3, the first condition is False, so it goes over to the next condition.
The next condition does hold True. Hence, the corresponding print statement is executed.
z=3
if z % 2 == 0:
elif z % 3 == 0:
else:
3
OUTPUT
z is divisible by 3
In the first condition, you check if you are looking in the kitchen, elif you are looking in
the bedroom, else you are looking around elsewhere. Depending on the value of
the room variable, the satisfied condition is executed.
Similarly, for the area variable, you write an if and else condition and check whether
the area is greater than 15 or not.
# Define variables
room = "bed"
area = 14.0
if room == "kit":
else:
print("Big place!")
else:
4
print("Pretty small.")
The while loop is used to repeat a block of code as long as a given condition is True. The
loop continues to run until the condition becomes False.
Syntax:
while condition:
# Block of code to execute while the condition is True
How it works:
count = 1
Explanation:
The loop starts with count = 1 and checks whether count <= 5.
If True, it prints the value of count, then increments count by 1.
This process continues until count becomes 6, at which point the condition count <= 5
becomes False, and the loop exits.
Output:
1
2
3
4
5
5
Example: Using a while loop to sum numbers
n=5
total = 0
while n > 0:
total += n # Add the value of n to total
n -= 1 # Decrease n by 1
print("Sum:", total)
Explanation:
Output:
Sum: 15
The for loop is used to iterate over a sequence (like a list, tuple, string, or range) and
execute a block of code for each element in that sequence.
Syntax:
How it works:
6
Explanation:
Output:
apple
banana
cherry
Explanation:
Output:
h
e
l
l
o
while loop: Repeats a block of code while a condition is True. It’s useful when the
number of iterations is not known in advance, or the loop depends on a condition that can
change.
for loop: Iterates over a sequence of elements (such as a list or a range of numbers). It’s
used when you know how many iterations are required.
n=5
while n > 0:
print(n)
7
n -= 1
Output:
5
4
3
2
1
Output:
5
4
3
2
1
Both examples produce the same output but use different loop constructs.
COMPREHENSIONS IN PYTHON
Comprehensions in Python provide a concise and readable way to create sequences (like
lists, sets, and dictionaries) by iterating over a collection or applying a condition.
Comprehensions reduce the need for loops and are often more efficient and readable.
Types of Comprehensions:
1. List Comprehensions
2. Set Comprehensions
3. Dictionary Comprehensions
4. Generator Expressions
1. List Comprehensions
List comprehensions allow you to create a new list by applying an expression to each
item in an existing iterable (like a list or range).
Syntax:
8
[expression for item in iterable if condition]
Output:
[0, 1, 4, 9, 16]
Output:
[0, 2, 4, 6, 8]
2. Set Comprehensions
Set comprehensions are similar to list comprehensions, but they generate a set instead of a list.
Since sets do not allow duplicates, all values will be unique.
Syntax:
Output:
Copy code
9
{0, 1, 64, 36, 4, 9, 16, 49, 81, 25}
The output is a set of squares of numbers from 0 to 9, but in no particular order since sets
are unordered.
3. Dictionary Comprehensions
Syntax:
Output:
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
This creates a dictionary where the keys are numbers, and the values are their squares.
4. Generator Expressions
Syntax:
Output:
[0, 1, 4, 9, 16]
Here, squares_gen is a generator, and converting it to a list shows the values it generates.
10
Examples of Combined Comprehensions:
Output:
Output:
[1, 2, 3, 4, 5, 6, 7, 8]
Comprehensions allow for more concise and readable code when working with sequences,
making it easier to create complex data structures with fewer lines of code. Let me know if you'd
like more detailed examples or have questions!
FUNCTIONS IN PYTHON
Functions in Python allow you to group reusable pieces of code into named blocks. These
blocks can be executed when called, simplifying your code by avoiding repetition. Functions can
take inputs, perform operations, and return results.
1. Defining a Function
A function is defined using the def keyword, followed by the function name and
parentheses. The code inside the function runs only when the function is called.
11
Syntax:
def function_name(parameters):
# code block to execute
return result # (optional)
def greet():
print("Hello, World!")
2. Calling a Function
You call (or execute) a function by using its name followed by parentheses.
3. Parameters in Functions
Functions can take inputs (called parameters) that allow them to operate on different
data. You specify parameters in the parentheses of the function definition.
def greet(name):
print(f"Hello, {name}!")
4. Return Statement
12
The return statement is used to send a result back from the function. Without return, the
function only performs its actions but does not output a result.
result = add(3, 4)
print(result) # Output: 7
Here, the add function takes two arguments (a and b), adds them, and returns the result.
5. Default Parameters
You can assign default values to parameters. If no argument is passed when calling the
function, the default value will be used.
def greet(name="Stranger"):
print(f"Hello, {name}!")
You can call functions with keyword arguments, allowing you to specify arguments by
name, not just by position.
7. Arbitrary Arguments
13
You can use *args to pass a variable number of positional arguments, and **kwargs to
pass a variable number of keyword arguments to a function.
python
Copy code
def add_numbers(*args):
return sum(args)
result = add_numbers(1, 2, 3, 4)
print(result) # Output: 10
Here, *args allows the function to accept any number of arguments, and they are treated as a
tuple.
8. Lambda Functions
A lambda function is an anonymous, short function defined using the lambda keyword.
It can take any number of arguments but has only one expression.
Syntax:
add = lambda x, y: x + y
print(add(3, 5)) # Output: 8
Here, lambda x, y: x + y is equivalent to a normal function that returns the sum of x and y.
9. Docstrings
You can add documentation to a function using a docstring. This is a string written in the
function body to describe what the function does.
def greet(name):
"""
This function greets the person whose name is passed as an argument.
"""
print(f"Hello, {name}!")
14
To access the docstring, you can use the .__doc__ attribute:
print(greet.__doc__)
Output:
Built-in functions: These are functions provided by Python, such as print(), len(), type(),
etc.
User-defined functions: These are functions that you create using the def keyword.
DÉCORATORS IN PYTHON
In Python, decorators are a powerful tool that allow you to modify the behavior of a
function or a class method without changing its actual code. Decorators are functions that wrap
another function to extend or alter its behavior.
Key Concepts:
1. Functions are First-Class Citizens: In Python, functions are treated as first-class objects,
meaning they can be passed as arguments to other functions or returned from other
functions.
2. Higher-Order Functions: A function that accepts another function as an argument or
returns a function is called a higher-order function.
3. Closures: A function that remembers the environment in which it was created, even
when called outside that environment.
A decorator is typically a higher-order function that takes a function as input and returns a new
function.
def decorator_function(original_function):
def wrapper_function():
# Code to modify the behavior of the original function
print("Wrapper function executed before", original_function.__name__)
return original_function()
return wrapper_function
15
@decorator_function
def display():
print("Display function executed")
display = decorator_function(display)
def log_decorator(func):
def wrapper():
print(f"Executing {func.__name__} function...")
result = func() # Call the original function
print(f"Finished executing {func.__name__}")
return result
return wrapper
Output:
If the function you want to decorate accepts arguments, the wrapper function inside the
decorator must also accept arguments.
def log_decorator(func):
def wrapper(*args, **kwargs): # Accepting all arguments
print(f"Executing {func.__name__} with arguments {args}, {kwargs}")
result = func(*args, **kwargs)
16
print(f"Finished executing {func.__name__}")
return result
return wrapper
@log_decorator
def greet_user(name, age):
print(f"Hello, {name}. You are {age} years old.")
Output:
5. Multiple Decorators
You can apply multiple decorators to a single function. The decorators are applied from
the innermost to the outermost.
python
Copy code
def bold_decorator(func):
def wrapper():
return f"<b>{func()}</b>"
return wrapper
def italic_decorator(func):
def wrapper():
return f"<i>{func()}</i>"
return wrapper
@bold_decorator
@italic_decorator
def say_hello():
return "Hello, world!"
Output:
<b><i>Hello, world!</i></b>
17
6. Class-Based Decorators
Decorators can also be implemented as classes, which is useful when you want to
maintain state.
class CountCalls:
def __init__(self, func):
[Link] = func
[Link] = 0
@CountCalls
def greet():
print("Hello!")
Output:
GENERATOR IN PYTHON
A Generator in Python is a function that returns an iterator using the Yield keyword. In
this article, we will discuss how the generator function works in Python.
18
In Python, we can create a generator function by simply using the def keyword and the
yield keyword. The generator has the following syntax in Python:
def function_name():
yield statement
Example:
In this example, we will create a simple generator that will yield three integers. Then
we will print these integers by using Python for loop.
Generator Object
Python Generator functions return a generator object that is iterable, i.e., can be used
as an Iterator. Generator objects are used either by calling the next method of the generator
object or using the generator object in a “for in” loop.
Example:
# A generator function
def simpleGeneratorFun():
yield 1
yield 2
19
yield 3
# x is a generator object
x = simpleGeneratorFun()
# In Python 3, __next__()
print(next(x))
print(next(x))
print(next(x))
Output:
1
2
3
Example:
In this example, we will create two generators for Fibonacci Numbers, first a simple
generator and second generator using a for loop.
def fib(limit):
a, b = 0, 1
while b < limit:
yield b
a, b = b, a + b
Output
1
1
2
3
5
20
8
13
21
34
55
89
144
1. Fibonacci Numbers:
The Fibonacci sequence is a series of numbers where each number is the sum of the two
preceding ones. It starts with 0 and 1, so:
A generator function in Python is defined using def like a normal function, but it contains one or
more yield statements instead of return. Each time yield is called, it provides a value to the caller
and "pauses" the function, retaining its state for the next call.
Code:
def fib(limit):
a, b = 0, 1
while b < limit:
yield b
a, b = b, a + b
After defining the function, a generator object is created by calling the function with a limit.
21
x = fib(200)
This creates a generator x, but it doesn't compute anything until it's iterated over.
for i in x:
print(i)
The for loop calls the generator function and keeps fetching values yielded by the
function until the while condition b < limit becomes false.
Each time yield provides a value, it gets printed.
The function resumes from where it left off after each yield and continues generating the
next Fibonacci number until the loop terminates when b reaches or exceeds 200.
Output:
Copy code
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144
How It Works:
The generator function produces one value at a time (due to yield), which makes it
efficient in terms of memory usage.
Each value is calculated only when needed by the loop, and the function retains its state
between successive calls to yield.
In summary, the generator fib() yields Fibonacci numbers one at a time, and the for loop
fetches and prints each number until the limit (200) is reached.
22
The generator expression in Python has the following Syntax:
(expression for item in iterable)
Example:
In this example, we will create a generator object that will print the multiples of 5
between the range of 0 to 5 which are also divisible by 2.
# generator expression
generator_exp = (i * 5 for i in range(5) if i%2==0)
for i in generator_exp:
print(i)
Output:
0
10
20
NAMESPACES IN PYTHON
A namespace in Python is a system used to ensure that all the names (identifiers) in a
program are unique. It’s like a container or a mapping between names and objects. Python
maintains different namespaces to avoid name conflicts across a program, so the same variable
name can exist in different namespaces without causing confusion.
Types of Namespaces:
1. Built-in Namespace:
o Contains all the built-in objects, functions, and exceptions in Python, such as
print(), len(), int(), etc.
o This namespace is available by default and can be accessed from anywhere in the
code.
Example:
2. Global Namespace:
o Contains all the names defined at the top level of a module or script (outside of
functions or classes).
o Variables and functions defined in this namespace are globally accessible within
that module.
Example:
23
x = 10 # Global variable
def func():
print(x) # Can access global variable
func() # Output: 10
3. Local Namespace:
o Contains the names defined inside a function or method.
o This namespace is created when the function is called and destroyed when the
function finishes execution.
Example:
def func():
y = 5 # Local variable
print(y)
func() # Output: 5
print(y) # Error: y is not defined outside the function
4. Enclosing Namespace:
o This refers to the namespace of an outer function that wraps an inner function. It
occurs in case of nested functions.
o Inner functions can access variables in the enclosing scope (outer function), but
they cannot modify them without using the nonlocal keyword.
Example:
def outer():
a = 10 # Enclosing variable
def inner():
print(a) # Can access enclosing variable
inner()
outer() # Output: 10
SCOPE IN PYTHON
LEGB Rule:
24
1. Local Scope:
o Variables created inside a function or block are in the local scope.
o They can only be accessed within the function in which they are defined.
Example:
def my_function():
x = 5 # Local scope
print(x)
my_function() # Output: 5
2. Enclosing Scope:
o Variables defined in an outer function’s scope can be accessed in a nested (inner)
function.
o Inner functions can read enclosing variables but need the nonlocal keyword to
modify them.
Example:
def outer_function():
x = 10 # Enclosing scope
def inner_function():
print(x) # Can access enclosing variable
inner_function()
outer_function() # Output: 10
3. Global Scope:
o Variables defined outside any function or class are in the global scope.
o Global variables can be accessed from anywhere in the program, but
modifications from inside functions require the global keyword.
Example:
x = 20 # Global scope
def my_function():
global x
x = 30 # Modify global variable
print(x)
my_function() # Output: 30
print(x) # Output: 30
25
4. Built-in Scope:
o This refers to the scope of built-in names in Python, such as len(), range(), and
int().
o These names are always accessible unless shadowed by local or global variables
with the same name.
Example:
x = 50 # Global variable
def outer_function():
x = 30 # Enclosing variable
def inner_function():
x = 20 # Local variable
print(x) # Accesses local variable first
inner_function()
outer_function() # Output: 20
print(x) # Output: 50 (global scope)
global Keyword:
To modify a global variable from inside a function, you must declare it using the global
keyword.
Example:
x = 10 # Global variable
def modify_global():
global x # Declare that we're using the global variable
x = 20 # Modify the global variable
26
modify_global()
print(x) # Output: 20
nonlocal Keyword:
To modify a variable in the enclosing (non-global) scope from inside a nested function,
use the nonlocal keyword.
Example:
def outer_function():
x = 10 # Enclosing variable
def inner_function():
nonlocal x # Modify enclosing variable
x = 20
inner_function()
print(x) # Output: 20
outer_function()
In Python, errors or exceptions can be handled using the try and except blocks. The try
block lets you test a block of code for errors, and the except block lets you handle the error if it
occurs. Using try-except helps to prevent the program from crashing due to unhandled
exceptions and allows you to manage errors more gracefully.
1. Basic Syntax
try:
# Code that might cause an error
except:
# Code to handle the error
If the user enters a zero as the divisor, Python will raise a ZeroDivisionError. We can
handle this error using try and except.
27
try:
numerator = int(input("Enter the numerator: "))
denominator = int(input("Enter the denominator: "))
result = numerator / denominator
print(f"Result: {result}")
except ZeroDivisionError:
print("Error: Division by zero is not allowed.")
except ValueError:
print("Error: Invalid input. Please enter integers only.")
You can catch multiple types of exceptions by specifying different except blocks. Each
block can handle a specific type of exception, as shown above.
Example:
try:
number = int(input("Enter a number: "))
print(f"100 divided by {number} is {100 / number}")
except ZeroDivisionError:
print("Error: You cannot divide by zero.")
except ValueError:
print("Error: You must enter an integer.")
The else block can be used to define code that should run if no exceptions were raised in
the try block.
try:
number = int(input("Enter a number: "))
result = 100 / number
except ZeroDivisionError:
28
print("Error: Division by zero is not allowed.")
except ValueError:
print("Error: Please enter a valid integer.")
else:
print(f"Result: {result}")
Enter a number: 5
Result: 20.0
The finally block is used to define code that should run no matter what, whether an
exception occurred or not. It is usually used for cleanup actions, like closing a file or releasing
resources.
try:
number = int(input("Enter a number: "))
result = 100 / number
except ZeroDivisionError:
print("Error: Division by zero is not allowed.")
except ValueError:
print("Error: Please enter a valid integer.")
else:
print(f"Result: {result}")
finally:
print("This will always execute, no matter what.")
Enter a number: 10
Result: 10.0
This will always execute, no matter what.
Enter a number: 0
Error: Division by zero is not allowed.
This will always execute, no matter what.
You can also raise exceptions manually using the raise keyword if certain conditions are not met.
Example:
29
try:
age = int(input("Enter your age: "))
if age < 0:
raise ValueError("Age cannot be negative.")
except ValueError as ve:
print(f"Error: {ve}")
Summary:
The try block is used to write code that may cause an exception.
The except block is used to handle the exception.
The else block runs if no exceptions are raised.
The finally block runs whether or not an exception occurs.
You can raise exceptions manually using the raise keyword.
User-defined exceptions in Python are custom error classes that you create to handle
specific error conditions in your code. They are derived from the built-in Exception class or any
of its sub classes.
User-defined exceptions provide more precise control over error handling in your application −
Clarity − They provide specific error messages that make it clear what went wrong.
Granularity − They allow you to handle different error conditions separately.
Maintainability − They centralize error handling logic, making your code easier to
maintain.
30
Create a new class that inherits from the built-in "Exception" class or any other
appropriate base class. This new class will serve as your custom exception.
class MyCustomError(Exception):
pass
Explanation
Inheritance − By inheriting from "Exception", your custom exception will have the same
behaviour and attributes as the built-in exceptions.
Class Definition − The class is defined using the standard Python class syntax. For
simple custom exceptions, you can define an empty class body using the "pass" statement.
Implement the "__init__" method to initialize any attributes or provide custom error
messages. This allows you to pass specific information about the error when raising the
exception.
class InvalidAgeError(Exception):
[Link] = age
[Link] = message
super().__init__([Link])
Explanation
Attributes − Define attributes such as "age" and "message" to store information about
the error.
Initialization − The "__init__" method initializes these attributes. The
"super().__init__([Link])" call ensures that the base "Exception" class is properly
initialized with the error message.
Default Message − A default message is provided, but you can override it when raising
the exception.
31
Override the "__str__" or "__repr__" method to provide a custom string representation of
the exception. This is useful for printing or logging the exception.
class InvalidAgeError(Exception):
[Link] = age
[Link] = message
super().__init__([Link])
def __str__(self):
Explanation
__str__ Method − The "__str__" method returns a string representation of the exception.
This is what will be displayed when the exception is printed.
Custom Message − Customize the message to include relevant information, such as the
provided age in this example.
Syntax
raise ExceptionType(args)
Example
In this example, the "set_age" function raises an "InvalidAgeError" if the age is outside the valid
range −
32
def set_age(age):
raise InvalidAgeError(age)
Syntax
try:
except ExceptionType as e:
Example
In the below example, the "try" block calls "set_age" with an invalid age. The "except"
block catches the "InvalidAgeError" and prints the custom error message −
try:
set_age(150)
except InvalidAgeError as e:
33
Complete Example
Combining all the steps, here is a complete example of creating and using a user-defined
exception −
class InvalidAgeError(Exception):
[Link] = age
[Link] = message
super().__init__([Link])
def __str__(self):
def set_age(age):
raise InvalidAgeError(age)
try:
set_age(150)
except InvalidAgeError as e:
34