Chapter 2_Python
Chapter 2_Python
Scope
- Local Scope: Variables defined inside a function. They are only accessible within
that function.
- Global Scope: Variables defined outside of all functions. They are accessible
anywhere in the code.
Example:
x = "global"
def outer():
x = "outer"
def inner():
print(x)
inner()
outer()
A function defined within another function. It can access variables of the enclosing
function (but not global variables unless specifically declared global).
Example:
def outer_function():
x = "Hello"
def inner_function():
inner_function()
outer_function()
Non-local Statements
The `nonlocal` keyword allows you to modify variables in the nearest enclosing scope
(but not global scope).
Example:
def outer():
x = "outer"
def inner():
x = "inner"
print(x)
inner()
print(x)
outer()
2.2. Built-in Functions
Python provides numerous built-in functions. These are always available for use
without importing any module.
- `map()`, `filter()`: Apply a function to every item in an iterable or filter items based
on a function.
Example:
numbers = [1, 2, 3, 4, 5]
print(sum(numbers)) # Output: 15
def greet(name):
Lambda function can have any number of arguments but can have only one
expression.
double = lambda x: x * 2
print(double(5)) # Output: 10
Lambda functions are often used with functions like `map()`, `filter()`, and `sorted()`.
Example:
numbers = [1, 2, 3, 4, 5]
Summary:
- There are different types of functions, including anonymous functions like `lambda`
that are concise and often used for short operations.
2.4. Decorators and Generators
Decorators
The outer function is called the decorator, which takes the original function as an
argument and returns a modified version of it.
def make_pretty(func):
def inner():
func()
return inner
def ordinary():
print("I am ordinary")
decorated_func = make_pretty(ordinary)
# call the decorated function
decorated_func()
Output
I got decorated
I am ordinary
decorated_func = make_pretty(ordinary)
We are now passing the ordinary() function as the argument to the make_pretty().
The make_pretty() function returns the inner function, and it is now assigned to the
decorated_func variable.
decorated_func()
Here, we are actually calling the inner() function, where we are printing
Instead of assigning the function call to a variable, Python provides a much more
elegant way to achieve this functionality using the @ symbol. For example,
def make_pretty(func):
def inner():
func()
return inner
def ordinary():
print("I am ordinary")
ordinary()
Output
I got decorated
I am ordinary
Here, the ordinary() function is decorated with the make_pretty() decorator using the
@make_pretty syntax, which is equivalent to calling
ordinary = make_pretty(ordinary).
Decorators are commonly used for tasks like logging, authentication, and access
control.
def my_decorator(func):
def wrapper():
func()
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
- Output:
Hello!
If the body of a def contains yield, the function automatically becomes a Python
generator function.
def generator_name(arg):
# statements
yield something
Here, the yield keyword is used to produce a value from the generator.
When the generator function is called, it does not execute the function body
immediately. Instead, it returns a generator object that can be iterated over to
produce the values.
Example: Here's an example of a generator function that produces a sequence
of numbers,
def my_generator(n):
# initialize counter
value = 0
yield value
value += 1
print(value)
Output
2.5. Modules
A module is a Python file that contains Python code (such as functions, classes, or
variables). It helps organize code into separate files and reuse it in different programs.
- Creating a Module: You create a module by saving Python code in a `.py` file.
def greet(name):
age = 25
Importing Modules
You can import a module using the `import` keyword. Once imported, you can access
the functions and variables defined in the module.
Example:
import mymodule
print(mymodule.age) # Output: 25
You can also give the module a different name using the `as` keyword:
import mymodule as mm
import math
You can import specific items from a module using the `from ... import` syntax.
You can import everything from a module using `*`. However, this is not
recommended because it can lead to conflicts between names.
To create your own module, just save a Python file with functions and variables you
want to reuse. You can import it in another script using the same syntax as built-in
modules.
return a + b
def multiply(a, b):
return a * b
- In another Python file, you can import and use the functions:
import my_module
If you have a package (a directory containing multiple Python files), you can organize
modules into submodules.
Example:
- Directory structure:
mypackage/
__init__.py
module1.py
module2.py
You can import functions or variables from different modules in the same program.
1. Example 1:
2. Example 2:
- File: `math_operations.py`
return a + b
- File: `string_operations.py`
def greet(name):
This modularity helps in organizing and maintaining your code in an efficient way,
especially for large projects.
2.7. Python Built-in Modules
Python has a wide array of built-in modules that provide various functions to help
with math, random number generation, date/time manipulation, and much more.
1. `math` Module
Common functions:
Example:
import math
2. `random` Module
Common functions:
Example:
import random
print(random.random()) # Output: Random float between 0 and 1
3. `datetime` Module
The `datetime` module provides classes for manipulating dates and times.
Common functions:
Example:
import datetime
now = datetime.datetime.now()
1. Creating a Package:
- Directory structure:
mypackage/
__init__.py
module1.py
module2.py
- `module1.py`:
def hello():
- `module2.py`:
def greet(name):
2. Importing a Package:
You can also import specific functions or variables from modules in the package:
Example:
- `mypackage/subpackage1/`
- `mypackage/subpackage2/`
Each subpackage can be in different directories, but they can still be imported as part
of the same package.
User-Defined Modules
A module is just a Python file (`.py`) containing Python code like functions, variables,
or classes. You can create a user-defined module by writing your Python code in a file
and importing it into other files.
1. Step 1: Create a Module File
return a + b
return a - b
import my_math
User-Defined Packages
- Directory structure:
mypackage/
my_math.py
my_string.py
- `my_math.py`:
return a * b
- `my_string.py`:
def uppercase(s):
return s.upper()
Using `__init__.py`:
- If you include an `__init__.py` file in the package directory, you can also define the
package's public API or initialization code.
Example `__init__.py`:
import mypackage
In summary:
- Packages allow you to organize multiple related modules into a single directory
structure.
Basic Syntax:
try:
except SomeException as e:
Example:
try:
except ZeroDivisionError:
Without exception handling, the program would crash. With exception handling, it
shows a user-friendly message.
Example:
try:
content = file.read()
except FileNotFoundError:
finally:
try:
except NameError:
In this example, if the file does not exist, the `FileNotFoundError` is caught, and an
appropriate message is shown instead of the program crashing.
Sometimes, different exceptions may occur within the same block of code. You can
handle multiple exceptions by specifying multiple `except` clauses. Additionally,
Python allows you to define your own exceptions by creating custom exception
classes.
try:
result = 10 / x
except ZeroDivisionError:
except ValueError:
User-Defined Exceptions:
You can create a custom exception by subclassing the built-in `Exception` class.
class CustomError(Exception):
pass
def check_positive(number):
if number <= 0:
try:
check_positive(-5)
except CustomError as e:
Providing meaningful error codes and messages can help developers understand what
went wrong. When an exception is raised, you can include information about the error
to assist in debugging.
class InvalidAgeError(Exception):
def __init__(self, age):
self.age = age
def validate_age(age):
raise InvalidAgeError(age)
try:
validate_age(150)
except InvalidAgeError as e:
print(f"Error: {e}")
In this case, the custom `InvalidAgeError` exception includes the invalid age and
provides a clear error message.
Exception handling is critical for writing robust and resilient programs. Here are some
best practices for using exception handling effectively:
1. Use Specific Exceptions: Catch specific exceptions rather than using a general
`except` block. This makes your error handling more precise and avoids masking
unexpected errors.
try:
except ValueError:
try:
result = 10 / 2
except ZeroDivisionError:
else:
3. Use `finally` for Cleanup: The `finally` block is always executed, regardless of
whether an exception occurred. It’s useful for releasing resources like file handles,
network connections, or databases.
try:
content = file.read()
except FileNotFoundError:
finally:
4. Avoid Empty `except` Blocks: Avoid using an empty `except` block that catches all
exceptions without handling them, as it can make debugging harder.
try:
except Exception as e:
if b == 0:
return a / b
- Use `try`, `except` blocks to catch and handle exceptions without breaking the
program.
- Handle multiple exceptions and define custom exceptions for more precise error
handling.
- Provide meaningful error messages and codes to assist developers with debugging.
- Use `finally` to ensure necessary cleanup actions, such as closing files or network
connections.
This structured approach to exception handling helps create resilient and user-friendly
programs.
Lab Assignments:
Problem: Write a Python program demonstrating the use of global, local, and
nonlocal scopes. Also, include a nested function that modifies a variable from the
outer function using the `nonlocal` keyword.
Solution:
# Global scope
x = "global"
def outer():
def inner():
x = "inner"
inner()
outer()
Expected Output:
Problem:
Solution:
numbers = [1, 2, 3, 4, 5]
**Expected Output:**
Max: 5, Min: 1
Problem:
Solution:
def my_decorator(func):
def wrapper():
func()
return wrapper
@my_decorator
def greet():
print("Hello, World!")
greet()
Expected Output:
Hello, World!
Problem:
Write a generator function that yields the first 5 square numbers. Also, use a generator
expression to achieve the same result.
Solution:
# Generator function
def square_numbers():
for i in range(5):
yield i ** 2
gen = square_numbers()
print(num)
# Generator expression
print(num)
Expected Output:
16
Problem:
1. Create a Python module named `mymath.py` with two functions `add()` and
`multiply()`.
Solution:
mymath.py:
return a + b
return a * b
Main Script:
import mymath
print(mymath.add(10, 5)) # Output: 15
Expected Output:
15
50
Problem:
Solution:
def check_positive(number):
if number < 0:
try:
check_positive(num)
except ValueError as e:
print(f"Error: {e}")