Introduction to Python
by Faiz ul haque Zeya
CEO Transys and Sr.Assoc. Prof Bahria University
and
Generative AI
Table of Contents
1. Introduction
- Welcome to Python
- History of Python
- Why Python?
2. Getting Started
- Installing Python
- Setting Up Your Environment
- Writing Your First Python Program
3. Basic Concepts
- Syntax and Semantics
- Variables and Data Types
- Operators and Expressions
4. Control Structures
- Conditional Statements
- Loops
- Control Flow Tools
5. Functions
- Defining Functions
- Function Arguments
- Lambda Functions
- Scope and Lifetime of Variables
6. Data Structures
- Lists
- Tuples
- Sets
- Dictionaries
7. Modules and Packages
- Importing Modules
- Standard Library Overview
- Creating Your Own Modules
8. File Handling
- Reading and Writing Files
- Working with CSV and JSON Files
- File and Directory Operations
9. Error and Exception Handling
- Understanding Exceptions
- Handling Exceptions
- Custom Exceptions
10. Object-Oriented Programming
- Classes and Objects
- Inheritance
- Polymorphism
- Encapsulation
11. Advanced Topics
- Decorators
- Generators
- Context Managers
12. Working with Data
- NumPy for Numerical Data
- Pandas for Data Analysis
- Matplotlib for Data Visualization
13. Web Development
- Introduction to Flask
- Building a Simple Web Application
- Working with APIs
14. Testing and Debugging
- Writing Tests with unittest
- Debugging Techniques
- Using pdb for Debugging
15. Project: Building a Real-World Application
- Planning Your Project
- Implementing Features
- Testing and Deployment
Chapter 1: Introduction
Welcome to Python
Overview of Python
Python is a high-level, interpreted programming language known for its readability and versatility. It was created by Guido van Rossum and first released in 1991. Python’s design philosophy emphasizes code readability and simplicity, making it an ideal choice for beginners and experienced developers alike.
Python supports multiple programming paradigms, including procedural, object-oriented, and functional programming. Its extensive standard library and active community have contributed to its popularity and widespread adoption.
Key Features and Advantages
- **Readability**: Python’s syntax is designed to be easy to read and write, which makes it an excellent language for beginners.
- **Versatility**: Python can be used for web development, data analysis, artificial intelligence, scientific computing, and more.
- **Extensive Libraries**: Python has a rich set of libraries and frameworks, such as Django for web development, NumPy and Pandas for data analysis, and TensorFlow for machine learning.
- **Community Support**: Python has a large, active community that contributes to its development and provides extensive documentation and support.
- **Cross-Platform**: Python is available on various operating systems, including Windows, macOS, and Linux.
Comparison with Other Programming Languages
- **Python vs. Java**: Python is dynamically typed and interpreted, making it more flexible and easier to write. Java, being statically typed and compiled, is generally faster and more suitable for large-scale applications.
- **Python vs. C++**: Python’s high-level nature makes it easier to learn and use, while C++ offers greater control over system resources and performance.
- **Python vs. JavaScript**: Python is primarily used for server-side development, whereas JavaScript is essential for client-side scripting in web development.
History of Python
Origin and Development
Guido van Rossum started working on Python in the late 1980s as a successor to the ABC programming language. He aimed to address some of ABC’s shortcomings while retaining its strengths. Python’s name was inspired by the British comedy series “Monty Python’s Flying Circus,” reflecting Van Rossum’s goal of making programming more fun.
Python 1.0 was released in January 1994, featuring many of the core concepts and syntax we see today, such as exception handling, functions, and the core data types (list, dict, str, etc.).
### Major Milestones in Python’s History
- **Python 2.0 (2000)**: Introduced list comprehensions, garbage collection, and Unicode support.
- **Python 3.0 (2008)**: A major revision that improved language consistency and removed redundant features. Not backward-compatible with Python 2.x, leading to a prolonged transition period.
- **Python 3.6 (2016)**: Introduced f-strings for easier string formatting, type hints, and many other enhancements.
- **Python 3.9 (2020)**: Added dictionary union operators, string methods to remove prefixes and suffixes, and other improvements.
Current Status and Future of Python
Python continues to evolve, with regular releases introducing new features and optimizations. It remains one of the most popular programming languages due to its versatility and strong community support.
The future of Python looks promising, with ongoing efforts to improve performance (e.g., the PyPy project), expand its application in fields like data science and machine learning, and enhance developer productivity through better tools and libraries.
Why Python?
Popularity and Community Support
Python’s popularity has grown significantly over the past decade, consistently ranking among the top programming languages in various surveys and indices. Its active community provides a wealth of resources, including tutorials, forums, and third-party libraries, making it easier for newcomers to learn and for experienced developers to find solutions and contribute to the language’s development.
Versatility in Various Domains
- **Web Development**: Frameworks like Django and Flask make Python a powerful tool for building web applications.
- **Data Science**: Libraries such as NumPy, Pandas, and Matplotlib, combined with tools like Jupyter Notebook, make Python the language of choice for data analysis and visualization.
- **Machine Learning and AI**: Python’s simplicity and the availability of libraries like TensorFlow, Keras, and Scikit-learn have made it a popular choice for machine learning and artificial intelligence projects.
- **Scientific Computing**: Python is widely used in academia and research for tasks ranging from simulations to data analysis.
- **Automation and Scripting**: Python’s ease of use and extensive standard library make it ideal for writing scripts to automate repetitive tasks.
Ease of Learning and Use
Python’s straightforward syntax and readability lower the barrier to entry for new programmers. Concepts like indentation to denote code blocks, the use of clear and concise keywords, and the absence of unnecessary punctuation make Python code easy to write and understand.
Practical Example: Writing Your First Python Program
To illustrate Python’s simplicity, let’s write a basic Python program that prints “Hello, World!” to the console:
```python
print("Hello, World!")
```
This single line of code demonstrates Python’s ability to perform a task with minimal syntax. In contrast, achieving the same result in languages like Java or C++ requires more boilerplate code.
### Practical Example: A Simple Calculator
Let’s extend our example to create a simple calculator that adds two numbers provided by the user:
```python
# Simple Calculator in Python
# Get user input
num1 = float(input("Enter first number: "))
num2 = float(input("Enter second number: "))
# Perform addition
result = num1 + num2
# Display the result
print("The sum is:", result)
```
This program showcases Python’s ability to handle user input, perform arithmetic operations, and display output, all with clear and concise syntax.
Absolutely! Here’s a detailed expansion of Chapter 2: Getting Started, aimed at covering 8 to 10 pages in a typical book format.
---
Chapter 2: Getting Started
## Installing Python
### Steps to Install Python on Different Operating Systems
#### Windows
1. **Download the Installer**:
- Go to the [official Python website](https://www.python.org/downloads/) and download the latest version of the Python installer for Windows.
2. **Run the Installer**:
- Locate the downloaded file and double-click to run the installer.
- Ensure that you check the box labeled “Add Python to PATH” before proceeding.
- Select "Install Now" for a default installation or "Customize Installation" for more control over the installation options.
3. **Verify the Installation**:
- Open the Command Prompt and type `python --version` to check if Python is installed correctly.
- You should see the Python version number printed on the screen.
#### macOS
1. **Download the Installer**:
- Visit the [Python downloads page](https://www.python.org/downloads/) and download the latest version of the Python installer for macOS.
2. **Run the Installer**:
- Open the downloaded `.pkg` file to start the installation process.
- Follow the on-screen instructions to complete the installation.
3. **Verify the Installation**:
- Open Terminal and type `python3 --version`.
- The Python version number should be displayed, indicating a successful installation.
#### Linux
Most Linux distributions come with Python pre-installed. However, you can install or update Python using the package manager.
1. **Ubuntu/Debian**:
- Open Terminal and run the following commands:
```bash
sudo apt update
sudo apt install python3
```
- Verify the installation by typing `python3 --version`.
2. **Fedora**:
- Open Terminal and run:
```bash
sudo dnf install python3
```
- Verify with `python3 --version`.
### Setting Up Python on Your System
After installation, setting up your development environment is crucial for efficient coding. Below are some recommended setups for different purposes.
#### Setting Up Virtual Environments
Using virtual environments allows you to manage dependencies for different projects separately.
1. **Create a Virtual Environment**:
- Navigate to your project directory and run:
```bash
python3 -m venv env
```
2. **Activate the Virtual Environment**:
- On Windows:
```bash
.\env\Scripts\activate
```
- On macOS/Linux:
```bash
source env/bin/activate
```
3. **Deactivate the Virtual Environment**:
- To deactivate, simply run:
```bash
deactivate
```
## Setting Up Your Environment
### Integrated Development Environments (IDEs)
#### PyCharm
1. **Installation**:
- Download PyCharm from the [JetBrains website](https://www.jetbrains.com/pycharm/download/).
- Run the installer and follow the setup instructions.
2. **Configuring PyCharm**:
- Open PyCharm and create a new project.
- Set up a virtual environment within PyCharm for your project dependencies.
3. **Features**:
- PyCharm offers intelligent code completion, on-the-fly error checking, and robust refactoring capabilities.
#### VSCode
1. **Installation**:
- Download VSCode from the [official website](https://code.visualstudio.com/).
- Run the installer and follow the setup instructions.
2. **Configuring VSCode**:
- Install the Python extension from the Extensions marketplace.
- Configure VSCode to use a virtual environment by selecting the appropriate interpreter.
3. **Features**:
- VSCode provides extensions for linting, debugging, and code formatting, making it a versatile choice for Python development.
#### Jupyter Notebook
1. **Installation**:
- Install Jupyter Notebook using pip:
```bash
pip install notebook
```
2. **Starting Jupyter Notebook**:
- Run the following command to start Jupyter Notebook:
```bash
jupyter notebook
```
3. **Features**:
- Jupyter Notebook is ideal for data analysis and visualization, allowing you to write and execute Python code in an interactive environment.
### Using Python’s Built-In IDLE
IDLE is Python’s Integrated Development and Learning Environment.
1. **Launching IDLE**:
- Open IDLE from your operating system’s application launcher.
2. **Writing and Running Code**:
- Write your code in the script editor and run it by pressing F5 or selecting “Run” > “Run Module” from the menu.
3. **Features**:
- IDLE offers a simple interface with syntax highlighting, auto-completion, and a built-in debugger.
## Writing Your First Python Program
### Creating and Running a Simple Python Script
Let's create a simple Python script that prints "Hello, World!".
1. **Open Your IDE**:
- Open your preferred IDE or text editor.
2. **Write the Code**:
- Type the following code:
```python
print("Hello, World!")
```
3. **Save the File**:
- Save the file with a `.py` extension, for example, `hello.py`.
4. **Run the Script**:
- In the terminal or command prompt, navigate to the directory containing your script and run:
```bash
python hello.py
```
### Understanding the Output
When you run the script, you should see the output `Hello, World!` printed in the terminal. This simple program demonstrates how to use the `print` function in Python to display text.
### Common Errors and Troubleshooting
- **SyntaxError**: This error occurs when there is a mistake in the syntax of your code.
- Example:
```python
print("Hello, World!"
```
- Missing closing parenthesis will raise a `SyntaxError`.
- **IndentationError**: Python relies on indentation to define code blocks. Incorrect indentation will raise an `IndentationError`.
- Example:
```python
if True:
print("Hello, World!")
```
- The print statement must be indented to be part of the if block.
- **NameError**: This error occurs when a variable or function name is not defined.
- Example:
```python
pritn("Hello, World!")
```
- A typo in the function name `print` will raise a `NameError`.
### Practical Example: A Simple Temperature Converter
Let's expand our understanding by creating a temperature converter that converts Celsius to Fahrenheit.
1. **Write the Code**:
- Type the following code:
```python
# Temperature Converter
# Get temperature in Celsius from user
celsius = float(input("Enter temperature in Celsius: "))
# Convert Celsius to Fahrenheit
fahrenheit = (celsius * 9/5) + 32
# Display the result
print(f"{celsius}°C is equal to {fahrenheit}°F")
```
2. **Save and Run the Script**:
- Save the file as `converter.py` and run it in the terminal:
```bash
python converter.py
```
3. **Expected Output**:
- Enter a temperature in Celsius, and the program will display the equivalent temperature in Fahrenheit.
### Practical Example: A Simple To-Do List
Let's create a basic to-do list application that allows users to add and view tasks.
1. **Write the Code**:
- Type the following code:
```python
# Simple To-Do List
# Initialize an empty list to store tasks
tasks = []
while True:
# Display menu options
print("\n1. Add Task")
print("2. View Tasks")
print("3. Exit")
# Get user choice
choice = input("Enter your choice: ")
if choice == '1':
# Add a new task
task = input("Enter a new task: ")
tasks.append(task)
print("Task added!")
elif choice == '2':
# View all tasks
print("\nTo-Do List:")
for idx, task in enumerate(tasks, start=1):
print(f"{idx}. {task}")
elif choice == '3':
# Exit the application
print("Goodbye!")
break
else:
print("Invalid choice! Please try again.")
```
2. **Save and Run the Script**:
- Save the file as `todo.py` and run it in the terminal:
```bash
python todo.py
```
3. **Expected Output**:
- The program will display a menu with options to add tasks, view tasks, or exit. Users can interact with the menu to manage their to-do list.
Chapter 3: Basic Concepts
## Syntax and Semantics
### Basic Syntax Rules
Python’s syntax is designed to be readable and straightforward. Here are some fundamental syntax rules:
- **Indentation**: Python uses indentation to define blocks of code. Consistent indentation is crucial.
```python
if True:
print("This is indented")
```
- **Comments**: Use the `#` symbol to write comments in your code. Comments are not executed.
```python
# This is a comment
print("Hello, World!") # This prints a message
```
- **Line Continuation**: Use the backslash `\` to continue a line of code onto the next line.
```python
total = 1 + 2 + 3 + \
4 + 5 + 6
```
### Structure of a Python Program
A typical Python program consists of the following components:
- **Imports**: Import necessary modules at the beginning of your script.
```python
import math
```
- **Functions and Classes**: Define your functions and classes.
```python
def greet(name):
return f"Hello, {name}!"
class Person:
def __init__(self, name):
self.name = name
```
- **Main Block**: Include a main block to execute the main logic of your program.
```python
if __name__ == "__main__":
print(greet("Alice"))
```
### Understanding Indentation and Its Importance
Indentation is used to define the scope of loops, functions, classes, and other control structures. Consistent indentation is mandatory in Python.
```python
for i in range(5):
print(i)
if i % 2 == 0:
print("Even")
else:
print("Odd")
```
Inconsistent indentation will result in an `IndentationError`.
## Variables and Data Types
### Declaring and Using Variables
Variables are used to store data that can be referenced and manipulated in your program.
- **Assignment**: Use the `=` operator to assign a value to a variable.
```python
x = 10
name = "Alice"
```
- **Updating Variables**: Variables can be updated by reassigning a new value.
```python
x = 5
x = x + 2 # x now holds the value 7
```
### Common Data Types
Python provides several built-in data types:
- **Integer (`int`)**: Whole numbers.
```python
age = 30
```
- **Float (`float`)**: Numbers with decimal points.
```python
pi = 3.14
```
- **String (`str`)**: Sequence of characters.
```python
message = "Hello, World!"
```
- **Boolean (`bool`)**: Represents `True` or `False`.
```python
is_active = True
```
- **List (`list`)**: Ordered collection of items.
```python
numbers = [1, 2, 3, 4, 5]
```
- **Tuple (`tuple`)**: Immutable ordered collection of items.
```python
point = (10, 20)
```
- **Dictionary (`dict`)**: Collection of key-value pairs.
```python
person = {"name": "Alice", "age": 30}
```
### Type Conversion
Python allows you to convert data from one type to another using built-in functions.
- **Convert to Integer**:
```python
num = int("10") # num is 10
```
- **Convert to Float**:
```python
num = float("3.14") # num is 3.14
```
- **Convert to String**:
```python
text = str(123) # text is "123"
```
- **Convert to Boolean**:
```python
flag = bool(1) # flag is True
```
## Operators and Expressions
### Arithmetic Operators
Python supports basic arithmetic operations.
- **Addition (`+`)**:
```python
result = 5 + 3 # result is 8
```
- **Subtraction (`-`)**:
```python
result = 5 - 3 # result is 2
```
- **Multiplication (`*`)**:
```python
result = 5 * 3 # result is 15
```
- **Division (`/`)**:
```python
result = 5 / 3 # result is 1.6666666666666667
```
- **Floor Division (`//`)**:
```python
result = 5 // 3 # result is 1
```
- **Modulus (`%`)**:
```python
result = 5 % 3 # result is 2
```
- **Exponentiation (`**`)**:
```python
result = 5 ** 3 # result is 125
```
### Comparison Operators
Comparison operators are used to compare values.
- **Equal (`==`)**:
```python
is_equal = (5 == 5) # is_equal is True
```
- **Not Equal (`!=`)**:
```python
is_not_equal = (5 != 3) # is_not_equal is True
```
- **Greater Than (`>`)**:
```python
is_greater = (5 > 3) # is_greater is True
```
- **Less Than (`<`)**:
```python
is_less = (5 < 3) # is_less is False
```
- **Greater Than or Equal To (`>=`)**:
```python
is_greater_equal = (5 >= 3) # is_greater_equal is True
```
- **Less Than or Equal To (`<=`)**:
```python
is_less_equal = (5 <= 3) # is_less_equal is False
```
### Logical Operators
Logical operators are used to combine conditional statements.
- **And (`and`)**:
```python
result = (5 > 3) and (4 > 2) # result is True
```
- **Or (`or`)**:
```python
result = (5 > 3) or (4 < 2) # result is True
```
- **Not (`not`)**:
```python
result = not (5 > 3) # result is False
```
Operator Precedence
Python follows a specific order of operations, known as precedence, to evaluate expressions.
- **Parentheses**: `()`
- **Exponentiation**: `**`
- **Unary Plus, Unary Minus, Bitwise NOT**: `+x`, `-x`, `~x`
- **Multiplication, Division, Floor Division, Modulus**: `*`, `/`, `//`, `%`
- **Addition, Subtraction**: `+`, `-`
- **Bitwise Shifts**: `<<`, `>>`
- **Bitwise AND**: `&`
- **Bitwise XOR**: `^`
- **Bitwise OR**: `|`
- **Comparison**: `==`, `!=`, `>`, `<`, `>=`, `<=`
- **Logical NOT**: `not`
- **Logical AND**: `and`
- **Logical OR**: `or`
### Creating Expressions
Expressions are combinations of values, variables, and operators that Python evaluates to produce a result.
- **Simple Expression**:
```python
result = 5 + 3 * 2 # result is 11
```
- **Using Parentheses**:
```python
result = (5 + 3) * 2 # result is 16
```
- **Complex Expression**:
```python
result = (5 + 3) * 2 - (4 / 2) # result is 14.0
```
## Practical Examples
### Example 1: Calculating the Area of a Circle
Let's write a Python program to calculate the area of a circle given its radius.
```python
# Import the math module to access mathematical functions
import math
# Function to calculate the area of a circle
def calculate_area(radius):
return math.pi * radius ** 2
# Input the radius
radius = float(input("Enter the radius of the circle: "))
# Calculate the area
area = calculate_area(radius)
# Display the result
print(f"The area of the circle with radius {radius} is {area:.2f}")
```
### Example 2: Checking If a Number is Even or Odd
Let's write a Python program to check if a number is even or odd.
```python
# Function to check if a number is even or odd
def check_even_odd(number):
if number % 2 == 0:
return "Even"
else:
return "Odd"
# Input a number
number = int(input("Enter a number: "))
# Check if the number is even or odd
result = check_even_odd(number)
# Display the result
print(f"The number {number} is {result}.")
```
### Example 3
: Basic Calculator
Let's write a Python program that functions as a basic calculator.
```python
# Function to perform basic arithmetic operations
def calculator(num1, num2, operation):
if operation == 'add':
return num1 + num2
elif operation == 'subtract':
return num1 - num2
elif operation == 'multiply':
return num1 * num2
elif operation == 'divide':
return num1 / num2
else:
return "Invalid operation"
# Input two numbers and the operation
num1 = float(input("Enter the first number: "))
num2 = float(input("Enter the second number: "))
operation = input("Enter the operation (add, subtract, multiply, divide): ")
# Perform the calculation
result = calculator(num1, num2, operation)
# Display the result
print(f"The result of {operation}ing {num1} and {num2} is {result}.")
```
Chapter 4: Control Structures
## Conditional Statements
### Using `if`, `elif`, and `else` Statements
Conditional statements allow you to execute different blocks of code based on certain conditions. The `if` statement is the most basic form of conditional execution.
#### `if` Statement
The `if` statement evaluates a condition. If the condition is true, the block of code within the `if` statement is executed.
```python
age = 18
if age >= 18:
print("You are eligible to vote.")
```
#### `elif` Statement
The `elif` (short for "else if") statement allows you to check multiple conditions. If the first condition is false, the next `elif` condition is evaluated.
```python
score = 85
if score >= 90:
print("Grade: A")
elif score >= 80:
print("Grade: B")
elif score >= 70:
print("Grade: C")
else:
print("Grade: F")
```
#### `else` Statement
The `else` statement catches anything which isn't caught by the preceding conditions.
```python
temperature = 25
if temperature > 30:
print("It's a hot day.")
elif temperature > 20:
print("It's a warm day.")
else:
print("It's a cold day.")
```
### Nested Conditions
You can nest `if`, `elif`, and `else` statements within each other to create more complex conditions.
```python
num = 15
if num > 10:
if num % 2 == 0:
print("The number is greater than 10 and even.")
else:
print("The number is greater than 10 and odd.")
else:
print("The number is 10 or less.")
```
### Practical Examples
#### Example 1: Checking for Leap Year
```python
year = int(input("Enter a year: "))
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
print(f"{year} is a leap year.")
else:
print(f"{year} is not a leap year.")
```
#### Example 2: Grading System
```python
marks = int(input("Enter your marks: "))
if marks >= 90:
print("Grade: A")
elif marks >= 80:
print("Grade: B")
elif marks >= 70:
print("Grade: C")
elif marks >= 60:
print("Grade: D")
else:
print("Grade: F")
```
## Loops
### Introduction to Loops
Loops are used to execute a block of code repeatedly until a certain condition is met. Python provides two types of loops: `for` and `while`.
#### `for` Loop
The `for` loop is used to iterate over a sequence (such as a list, tuple, dictionary, set, or string).
```python
# Example 1: Iterating over a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# Example 2: Iterating over a string
for char in "Python":
print(char)
```
#### `while` Loop
The `while` loop executes as long as the condition remains true.
```python
# Example 1: Using while loop
count = 0
while count < 5:
print(count)
count += 1
# Example 2: Infinite loop (use with caution)
# while True:
# print("This will run forever.")
```
### Loop Control Statements
#### `break` Statement
The `break` statement is used to exit the loop prematurely.
```python
for i in range(10):
if i == 5:
break
print(i)
```
#### `continue` Statement
The `continue` statement is used to skip the rest of the code inside the loop for the current iteration only.
```python
for i in range(10):
if i % 2 == 0:
continue
print(i)
```
#### `pass` Statement
The `pass` statement is a null operation; nothing happens when it is executed. It is useful as a placeholder.
```python
for i in range(5):
if i == 3:
pass
else:
print(i)
```
### Nested Loops
Loops can be nested within other loops to perform complex tasks.
```python
for i in range(3):
for j in range(2):
print(f"i: {i}, j: {j}")
```
### Practical Examples
#### Example 1: Multiplication Table
```python
num = int(input("Enter a number: "))
for i in range(1, 11):
print(f"{num} x {i} = {num * i}")
```
#### Example 2: Finding Prime Numbers
```python
start = int(input("Enter the start of the range: "))
end = int(input("Enter the end of the range: "))
for num in range(start, end + 1):
if num > 1:
for i in range(2, num):
if num % i == 0:
break
else:
print(num)
```
## Control Flow Tools
### Using the `range()` Function
The `range()` function generates a sequence of numbers, which is useful for iterating with `for` loops.
```python
# Example 1: Using range() in a for loop
for i in range(5):
print(i)
# Example 2: Specifying start, stop, and step
for i in range(1, 10, 2):
print(i)
```
### List Comprehensions
List comprehensions provide a concise way to create lists.
```python
# Example 1: Basic list comprehension
squares = [x ** 2 for x in range(10)]
print(squares)
# Example 2: List comprehension with condition
even_squares = [x ** 2 for x in range(10) if x % 2 == 0]
print(even_squares)
```
### The `enumerate()` Function
The `enumerate()` function adds a counter to an iterable and returns it as an enumerate object.
```python
fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
print(f"Index: {index}, Fruit: {fruit}")
```
### The `zip()` Function
The `zip()` function combines several iterables (such as lists) element-wise.
```python
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
for name, score in zip(names, scores):
print(f"{name} scored {score}")
```
### Practical Examples
#### Example 1: Creating a List of Even Numbers
```python
even_numbers = [x for x in range(20) if x % 2 == 0]
print(even_numbers)
```
#### Example 2: Creating a Dictionary from Two Lists
```python
keys = ["name", "age", "city"]
values = ["Alice", 30, "New York"]
person = dict(zip(keys, values))
print(person)
```
#### Example 3: Enumerating Over a List
```python
items = ["pen", "notebook", "eraser"]
for index, item in enumerate(items, start=1):
print(f"{index}: {item}")
```
Chapter 5: Functions
## Defining Functions
### Syntax of Function Definition
A function in Python is defined using the `def` keyword, followed by the function name, parentheses `()`, and a colon `:`. The function body is indented.
```python
def greet():
print("Hello, World!")
```
### Calling Functions
To execute a function, you call it by its name followed by parentheses.
```python
greet() # Output: Hello, World!
```
### The `return` Statement
Functions can return a value using the `return` statement. Once `return` is executed, the function terminates.
```python
def add(a, b):
return a + b
result = add(3, 4)
print(result) # Output: 7
```
### Function Documentation
You can add documentation to functions using docstrings, which are enclosed in triple quotes `"""`.
```python
def greet():
"""This function prints a greeting message."""
print("Hello, World!")
print(greet.__doc__) # Output: This function prints a greeting message.
```
## Function Arguments
### Positional Arguments
Positional arguments are the most straightforward way to pass values to a function.
```python
def add(a, b):
return a + b
result = add(5, 7)
print(result) # Output: 12
```
### Keyword Arguments
Keyword arguments allow you to specify the parameter names along with their values, providing more clarity and flexibility.
```python
def greet(name, message):
print(f"{message}, {name}!")
greet(name="Alice", message="Good morning") # Output: Good morning, Alice!
```
### Default Values
You can provide default values for parameters. If an argument is not passed, the default value is used.
```python
def greet(name, message="Hello"):
print(f"{message}, {name}!")
greet("Alice") # Output: Hello, Alice!
greet("Bob", "Good evening") # Output: Good evening, Bob!
```
### Variable-Length Arguments
Variable-length arguments allow you to pass a variable number of arguments to a function. There are two types: `*args` and `**kwargs`.
#### `*args`
The `*args` syntax allows a function to accept any number of positional arguments, which are accessible as a tuple.
```python
def add(*args):
return sum(args)
result = add(1, 2, 3, 4)
print(result) # Output: 10
```
#### `**kwargs`
The `**kwargs` syntax allows a function to accept any number of keyword arguments, which are accessible as a dictionary.
```python
def print_details(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_details(name="Alice", age=30, city="New York")
# Output:
# name: Alice
# age: 30
# city: New York
```
Lambda Functions
Introduction to Lambda Functions
Lambda functions are small anonymous functions defined using the `lambda` keyword. They are useful for short, throwaway functions.
### Syntax and Use Cases
The syntax of a lambda function is `lambda arguments: expression`. Lambda functions can have any number of arguments but only one expression.
```python
add = lambda x, y: x + y
print(add(5, 3)) # Output: 8
```
### Comparing Lambda with Regular Functions
Lambda functions are often used in situations where a simple function is required temporarily.
#### Example with `map()`
```python
numbers = [1, 2, 3, 4]
squared = map(lambda x: x ** 2, numbers)
print(list(squared)) # Output: [1, 4, 9, 16]
```
#### Example with `filter()`
```python
numbers = [1, 2, 3, 4, 5, 6]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens)) # Output: [2, 4, 6]
```
## Scope and Lifetime of Variables
### Understanding Scope
Scope refers to the region of a program where a variable is accessible. There are two types of scope: local and global.
#### Local Scope
Variables declared inside a function are local to that function and cannot be accessed outside.
```python
def greet():
message = "Hello, World!"
print(message)
greet() # Output: Hello, World!
print(message) # Error: NameError: name 'message' is not defined
```
#### Global Scope
Variables declared outside of functions are global and can be accessed throughout the program.
```python
message = "Hello, World!"
def greet():
print(message)
greet() # Output: Hello, World!
print(message) # Output: Hello, World!
```
### Lifetime of Variables
The lifetime of a variable refers to the period during which the variable exists in memory. Local variables are created when a function is called and destroyed when the function exits. Global variables exist for the lifetime of the program.
### Using `global` and `nonlocal` Keywords
#### `global` Keyword
The `global` keyword allows you to modify a global variable inside a function.
```python
count = 0
def increment():
global count
count += 1
increment()
print(count) # Output: 1
```
#### `nonlocal` Keyword
The `nonlocal` keyword is used to work with variables inside nested functions, indicating that the variable is not local to the nested function but is not global either.
```python
def outer_function():
x = "local"
def inner_function():
nonlocal x
x = "nonlocal"
print("inner:", x)
inner_function()
print("outer:", x)
outer_function()
# Output:
# inner: nonlocal
# outer: nonlocal
```
## Practical Examples
### Example 1: Calculating Factorial Using Recursion
```python
def factorial(n):
"""Calculate the factorial of n using recursion."""
if n == 1:
return 1
else:
return n * factorial(n - 1)
num = 5
print(f"Factorial of {num} is {factorial(num)}") # Output: 120
```
### Example 2: Fibonacci Sequence Using Recursion
```python
def fibonacci(n):
"""Return the n-th Fibonacci number."""
if n <= 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
num = 10
for i in range(num):
print(fibonacci(i), end=" ")
# Output: 0 1 1 2 3 5 8 13 21 34
```
### Example 3: Using `*args` and `**kwargs` in Functions
```python
def process_data(*args, **kwargs):
"""Process and print data using *args and **kwargs."""
print("Positional arguments:", args)
print("Keyword arguments:", kwargs)
process_data(1, 2, 3, name="Alice", age=30)
# Output:
# Positional arguments: (1, 2, 3)
# Keyword arguments: {'name': 'Alice', 'age': 30}
```
### Example 4: Lambda Function in Sorting
```python
students = [
{"name": "John", "grade": 88},
{"name": "Jane", "grade": 92},
{"name": "Dave", "grade": 85},
]
sorted_students = sorted(students, key=lambda student: student["grade"])
print(sorted_students)
# Output: [{'name': 'Dave', 'grade': 85}, {'name': 'John', 'grade': 88}, {'name': 'Jane', 'grade': 92}]
```
### Example 5: Global and Local Variables
```python
count = 0
def outer():
count = 1
def inner():
nonlocal count
count = 2
print("inner:", count)
inner()
print("outer:", count)
outer()
print("global:", count)
# Output:
# inner: 2
# outer: 2
# global: 0
```
Chapter 6: Data Structures
## Lists
### Creating and Modifying Lists
Lists are ordered collections of items. You can create a list by placing items inside square brackets `[]`, separated by commas.
```python
# Creating a list
fruits = ["apple", "banana", "cherry"]
print(fruits) # Output: ['apple', 'banana', 'cherry']
# Modifying a list
fruits[1] = "blueberry"
print(fruits) # Output: ['apple', 'blueberry', 'cherry']
```
### Common List Operations and Methods
Lists come with several built-in methods that make them versatile and easy to use.
#### Adding Items
- **append()**: Adds an item to the end of the list.
```python
fruits.append("orange")
print(fruits) # Output: ['apple', 'blueberry', 'cherry', 'orange']
```
- **insert()**: Adds an item at a specified position.
```python
fruits.insert(1, "kiwi")
print(fruits) # Output: ['apple', 'kiwi', 'blueberry', 'cherry', 'orange']
```
#### Removing Items
- **remove()**: Removes the first occurrence of a specified item.
```python
fruits.remove("blueberry")
print(fruits) # Output: ['apple', 'kiwi', 'cherry', 'orange']
```
- **pop()**: Removes and returns an item at a specified position (default is the last item).
```python
removed_fruit = fruits.pop(2)
print(removed_fruit) # Output: cherry
print(fruits) # Output: ['apple', 'kiwi', 'orange']
```
#### Other Useful Methods
- **sort()**: Sorts the list in ascending order.
```python
fruits.sort()
print(fruits) # Output: ['apple', 'kiwi', 'orange']
```
- **reverse()**: Reverses the order of the list.
```python
fruits.reverse()
print(fruits) # Output: ['orange', 'kiwi', 'apple']
```
- **len()**: Returns the number of items in the list.
```python
print(len(fruits)) # Output: 3
```
### List Slicing and Comprehensions
List slicing allows you to access a subset of the list. List comprehensions provide a concise way to create lists.
#### Slicing
```python
# List slicing
numbers = [1, 2, 3, 4, 5]
print(numbers[1:4]) # Output: [2, 3, 4]
print(numbers[:3]) # Output: [1, 2, 3]
print(numbers[3:]) # Output: [4, 5]
print(numbers[-3:]) # Output: [3, 4, 5]
```
#### Comprehensions
```python
# List comprehension
squares = [x**2 for x in range(6)]
print(squares) # Output: [0, 1, 4, 9, 16, 25]
# List comprehension with condition
evens = [x for x in range(10) if x % 2 == 0]
print(evens) # Output: [0, 2, 4, 6, 8]
```
## Tuples
### Creating and Using Tuples
Tuples are similar to lists, but they are immutable, meaning they cannot be changed after creation. Tuples are created by placing items inside parentheses `()`.
```python
# Creating a tuple
coordinates = (10, 20)
print(coordinates) # Output: (10, 20)
# Accessing tuple items
print(coordinates[0]) # Output: 10
print(coordinates[1]) # Output: 20
```
### Tuple Operations and Methods
Tuples support a few operations and methods.
#### Concatenation and Repetition
```python
# Concatenation
tuple1 = (1, 2)
tuple2 = (3, 4)
result = tuple1 + tuple2
print(result) # Output: (1, 2, 3, 4)
# Repetition
result = tuple1 * 3
print(result) # Output: (1, 2, 1, 2, 1, 2)
```
#### Methods
- **count()**: Returns the number of times a specified value occurs in a tuple.
```python
example = (1, 2, 2, 3)
print(example.count(2)) # Output: 2
```
- **index()**: Returns the index of the first occurrence of a specified value.
```python
print(example.index(2)) # Output: 1
```
### When to Use Tuples Over Lists
Tuples are useful when you need to ensure that the data cannot be modified. They are also more memory-efficient than lists.
```python
# Example of using tuples
months = ("January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December")
```
## Sets
### Creating and Modifying Sets
Sets are unordered collections of unique items. They are created using curly braces `{}` or the `set()` function.
```python
# Creating a set
fruits = {"apple", "banana", "cherry"}
print(fruits) # Output: {'banana', 'apple', 'cherry'}
# Adding an item to a set
fruits.add("orange")
print(fruits) # Output: {'orange', 'banana', 'apple', 'cherry'}
# Removing an item from a set
fruits.remove("banana")
print(fruits) # Output: {'orange', 'apple', 'cherry'}
```
### Set Operations (Union, Intersection, Difference)
Sets support various mathematical operations.
#### Union
Combines all unique items from both sets.
```python
set1 = {1, 2, 3}
set2 = {3, 4, 5}
union_set = set1.union(set2)
print(union_set) # Output: {1, 2, 3, 4, 5}
```
#### Intersection
Finds common items between sets.
```python
intersection_set = set1.intersection(set2)
print(intersection_set) # Output: {3}
```
#### Difference
Finds items that are in the first set but not in the second.
```python
difference_set = set1.difference(set2)
print(difference_set) # Output: {1, 2}
```
### Set Methods and Use Cases
Sets provide methods for checking membership and ensuring data uniqueness.
#### Checking Membership
```python
fruits = {"apple", "banana", "cherry"}
print("apple" in fruits) # Output: True
print("kiwi" in fruits) # Output: False
```
#### Use Cases
Sets are ideal for membership tests, eliminating duplicates from a sequence, and performing mathematical operations like union and intersection.
## Dictionaries
### Creating and Using Dictionaries
Dictionaries are collections of key-value pairs. They are created using curly braces `{}` with keys and values separated by colons `:`.
```python
# Creating a dictionary
person = {"name": "Alice", "age": 30, "city": "New York"}
print(person) # Output: {'name': 'Alice', 'age': 30, 'city': 'New York'}
# Accessing dictionary items
print(person["name"]) # Output: Alice
```
### Dictionary Operations and Methods
#### Adding and Modifying Items
```python
# Adding a new key-value pair
person["email"] = "alice@example.com"
print(person) # Output: {'name': 'Alice', 'age': 30, 'city': 'New York', 'email': 'alice@example.com'}
# Modifying an existing value
person["age"] = 31
print(person) # Output: {'name': 'Alice', 'age': 31, 'city': 'New York', 'email': 'alice@example.com'}
```
#### Removing Items
- **pop()**: Removes a key-value pair and returns the value.
```python
age = person.pop("age")
print(age) # Output: 31
print(person) # Output: {'name': 'Alice', 'city': 'New York', 'email': 'alice@example.com'}
```
- **del**: Deletes a key-value pair.
```python
del person["city"]
print(person) # Output: {'name': 'Alice', 'email': 'alice@example.com'}
```
#### Other Useful Methods
- **keys()**: Returns a list of keys.
```python
print(person.keys()) # Output: dict_keys(['name', 'email'])
```
- **values()**: Returns a list of values.
```python
print(person.values()) # Output: dict_values(['Alice', 'alice@example.com'])
```
- **items()**: Returns a list of key-value pairs.
```python
print(person.items
()) # Output: dict_items([('name', 'Alice'), ('email', 'alice@example.com')])
```
### Practical Examples
#### Example 1: Counting Word Frequency
```python
text = "hello world hello"
words = text.split()
word_count = {}
for word in words:
if word in word_count:
word_count[word] += 1
else:
word_count[word] = 1
print(word_count) # Output: {'hello': 2, 'world': 1}
```
#### Example 2: Storing User Information
```python
users = [
{"username": "alice", "email": "alice@example.com", "age": 25},
{"username": "bob", "email": "bob@example.com", "age": 30},
]
for user in users:
print(f"Username: {user['username']}, Email: {user['email']}, Age: {user['age']}")
# Output:
# Username: alice, Email: alice@example.com, Age: 25
# Username: bob, Email: bob@example.com, Age: 30
```
Chapter 7: Modules and Packages
## Importing Modules
### Syntax for Importing Modules
Python provides several ways to import modules. Modules are files containing Python code that can define functions, classes, and variables.
#### Importing the Entire Module
The most common way to import a module is by using the `import` statement.
```python
import math
print(math.sqrt(16)) # Output: 4.0
```
#### Importing Specific Functions or Variables
You can import specific attributes from a module using the `from` keyword.
```python
from math import sqrt
print(sqrt(25)) # Output: 5.0
```
#### Importing All Attributes
You can import all attributes from a module using the `*` symbol. However, this practice is generally discouraged because it can lead to namespace conflicts.
```python
from math import *
print(sin(0)) # Output: 0.0
```
#### Using Aliases
You can use aliases to give a module or attribute a different name.
```python
import math as m
print(m.sqrt(36)) # Output: 6.0
from math import sqrt as square_root
print(square_root(49)) # Output: 7.0
```
## Standard Library Overview
### Overview of Python’s Standard Library
Python’s standard library is a collection of modules that provide a wide range of functionalities, from file I/O to web services. The standard library is included with every Python installation.
#### Commonly Used Modules
##### `math` Module
The `math` module provides mathematical functions.
```python
import math
print(math.pi) # Output: 3.141592653589793
print(math.e) # Output: 2.718281828459045
print(math.sqrt(16)) # Output: 4.0
```
##### `datetime` Module
The `datetime` module supplies classes for manipulating dates and times.
```python
from datetime import datetime
now = datetime.now()
print(now) # Output: Current date and time
# Formatting dates
formatted_date = now.strftime("%Y-%m-%d %H:%M:%S")
print(formatted_date) # Output: Formatted date and time
```
##### `os` Module
The `os` module provides a way to use operating system-dependent functionality.
```python
import os
print(os.name) # Output: Name of the OS (e.g., 'posix', 'nt')
# Working with directories
current_dir = os.getcwd()
print(current_dir) # Output: Current working directory
os.mkdir("new_folder")
os.rmdir("new_folder")
```
##### `sys` Module
The `sys` module provides access to some variables used or maintained by the interpreter and functions that interact with the interpreter.
```python
import sys
print(sys.version) # Output: Python version
```
##### `random` Module
The `random` module implements pseudo-random number generators for various distributions.
```python
import random
print(random.randint(1, 10)) # Output: Random integer between 1 and 10
# Random choice from a list
choices = ['apple', 'banana', 'cherry']
print(random.choice(choices)) # Output: Random choice from the list
```
## Creating Your Own Modules
### Writing and Using Custom Modules
Creating your own module is straightforward. Simply write the Python code in a file with a `.py` extension and import it in your script.
#### Creating a Module
Create a file named `mymodule.py`.
```python
# mymodule.py
def greet(name):
return f"Hello, {name}!"
def add(a, b):
return a + b
```
#### Importing and Using the Custom Module
You can import your custom module and use its functions.
```python
# main.py
import mymodule
print(mymodule.greet("Alice")) # Output: Hello, Alice!
print(mymodule.add(3, 4)) # Output: 7
```
### Best Practices for Module Creation
- **Use Descriptive Names**: Choose module names that clearly describe their purpose.
- **Avoid Global Variables**: Minimize the use of global variables to prevent conflicts.
- **Document Your Code**: Use docstrings to document functions and classes.
- **Group Related Functions**: Group related functions and classes together in a module.
## Packages
### Introduction to Packages
A package is a way of organizing related modules into a directory hierarchy. A package is simply a directory that contains a special file named `__init__.py` and can contain multiple modules.
### Creating a Package
#### Creating the Directory Structure
Create a directory structure for your package.
```
mypackage/
__init__.py
module1.py
module2.py
```
#### Writing the Modules
Write the modules inside the package.
```python
# mypackage/module1.py
def function1():
return "This is function1 from module1"
```
```python
# mypackage/module2.py
def function2():
return "This is function2 from module2"
```
### Importing and Using a Package
You can import and use the modules from the package.
```python
# main.py
from mypackage import module1, module2
print(module1.function1()) # Output: This is function1 from module1
print(module2.function2()) # Output: This is function2 from module2
```
### Importing Specific Functions
You can also import specific functions from the modules.
```python
from mypackage.module1 import function1
from mypackage.module2 import function2
print(function1()) # Output: This is function1 from module1
print(function2()) # Output: This is function2 from module2
```
### Using Aliases with Packages
You can use aliases to shorten the names of the modules or functions when importing.
```python
import mypackage.module1 as m1
import mypackage.module2 as m2
print(m1.function1()) # Output: This is function1 from module1
print(m2.function2()) # Output: This is function2 from module2
```
### Nested Packages
Packages can be nested to create a hierarchical structure.
```
mypackage/
__init__.py
subpackage/
__init__.py
module3.py
```
```python
# mypackage/subpackage/module3.py
def function3():
return "This is function3 from module3"
```
### Importing from Nested Packages
You can import modules from nested packages.
```python
from mypackage.subpackage import module3
print(module3.function3()) # Output: This is function3 from module3
```
## Practical Examples
### Example 1: Creating a Math Package
Create a package named `mymath` with modules for basic mathematical operations.
#### Directory Structure
```
mymath/
__init__.py
addition.py
subtraction.py
```
#### Writing the Modules
```python
# mymath/addition.py
def add(a, b):
return a + b
```
```python
# mymath/subtraction.py
def subtract(a, b):
return a - b
```
#### Using the Math Package
```python
# main.py
from mymath import addition, subtraction
print(addition.add(10, 5)) # Output: 15
print(subtraction.subtract(10, 5)) # Output: 5
```
### Example 2: Creating a Utility Package
Create a package named `utility` with modules for string and file operations.
#### Directory Structure
```
utility/
__init__.py
string_utils.py
file_utils.py
```
#### Writing the Modules
```python
# utility/string_utils.py
def to_uppercase(s):
return s.upper()
def to_lowercase(s):
return s.lower()
```
```python
# utility/file_utils.py
def read_file(filename):
with open(filename, 'r') as file:
return file.read()
def write_file(filename, content):
with open(filename, 'w') as file:
file.write(content)
```
#### Using the Utility Package
```python
# main.py
from utility import string_utils, file_utils
# String operations
print(string_utils.to_uppercase("hello")) # Output: HELLO
print(string_utils.to_lowercase("WORLD")) # Output: world
# File operations
file_utils.write_file("test.txt", "This is a test.")
content = file_utils.read_file("test.txt")
print(content) # Output: This is a test.
```
Chapter 8: File Handling
## Reading and Writing Files
### Opening Files
Python provides built-in functions to open, read, write, and close files. The `open()` function is used to open a file, and it returns a file object.
```python
file = open("example.txt", "r") # Opens the file in read mode
```
#### File Modes
- **`"r"`**: Read mode (default). Opens a file for reading.
- **`"w"`**: Write mode. Opens a file for writing (creates a new file if it doesn't exist or truncates the file if it exists).
- **`"a"`**: Append mode. Opens a file for appending (creates a new file if it doesn't exist).
- **`"b"`**: Binary mode. Opens a file in binary mode.
- **`"t"`**: Text mode (default). Opens a file in text mode.
- **`"x"`**: Exclusive creation mode. Creates a new file, failing if the file already exists.
```python
file = open("example.txt", "w") # Opens the file in write mode
```
### Reading Files
You can read the contents of a file using various methods.
#### `read()`
Reads the entire file content.
```python
file = open("example.txt", "r")
content = file.read()
print(content)
file.close()
```
#### `readline()`
Reads a single line from the file.
```python
file = open("example.txt", "r")
line = file.readline()
print(line)
file.close()
```
#### `readlines()`
Reads all the lines from the file and returns them as a list of strings.
```python
file = open("example.txt", "r")
lines = file.readlines()
for line in lines:
print(line.strip()) # .strip() removes the newline character
file.close()
```
### Writing Files
You can write content to a file using the `write()` and `writelines()` methods.
#### `write()`
Writes a string to the file.
```python
file = open("example.txt", "w")
file.write("Hello, World!\n")
file.write("This is a test file.\n")
file.close()
```
#### `writelines()`
Writes a list of strings to the file.
```python
lines = ["Hello, World!\n", "This is a test file.\n"]
file = open("example.txt", "w")
file.writelines(lines)
file.close()
```
### Closing Files
Always close a file after you are done using it to free up system resources.
```python
file = open("example.txt", "r")
# Perform file operations
file.close()
```
### Using `with` Statement
The `with` statement provides a cleaner way to open and close files automatically.
```python
with open("example.txt", "r") as file:
content = file.read()
print(content)
# No need to explicitly close the file; it's done automatically
```
## Working with CSV and JSON Files
### Reading and Writing CSV Files
CSV (Comma Separated Values) files are commonly used to store tabular data.
#### Using `csv` Module
Python’s built-in `csv` module provides functionalities to read from and write to CSV files.
#### Reading CSV Files
```python
import csv
with open("data.csv", "r") as file:
reader = csv.reader(file)
for row in reader:
print(row)
```
#### Writing CSV Files
```python
import csv
data = [
["Name", "Age", "City"],
["Alice", 30, "New York"],
["Bob", 25, "Los Angeles"]
]
with open("data.csv", "w", newline='') as file:
writer = csv.writer(file)
writer.writerows(data)
```
#### Using `DictReader` and `DictWriter`
The `DictReader` and `DictWriter` classes allow you to work with CSV files using dictionaries.
```python
# Reading CSV with DictReader
import csv
with open("data.csv", "r") as file:
reader = csv.DictReader(file)
for row in reader:
print(row)
# Writing CSV with DictWriter
import csv
data = [
{"Name": "Alice", "Age": 30, "City": "New York"},
{"Name": "Bob", "Age": 25, "City": "Los Angeles"}
]
with open("data.csv", "w", newline='') as file:
fieldnames = ["Name", "Age", "City"]
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(data)
```
### Reading and Writing JSON Files
JSON (JavaScript Object Notation) is a popular format for data interchange.
#### Using `json` Module
Python’s built-in `json` module provides functionalities to read from and write to JSON files.
#### Reading JSON Files
```python
import json
with open("data.json", "r") as file:
data = json.load(file)
print(data)
```
#### Writing JSON Files
```python
import json
data = {
"name": "Alice",
"age": 30,
"city": "New York"
}
with open("data.json", "w") as file:
json.dump(data, file, indent=4)
```
#### Working with JSON Strings
You can also work with JSON data in string format.
```python
import json
json_string = '{"name": "Alice", "age": 30, "city": "New York"}'
data = json.loads(json_string)
print(data) # Output: {'name': 'Alice', 'age': 30, 'city': 'New York'}
data_string = json.dumps(data, indent=4)
print(data_string)
```
## File and Directory Operations
### Working with File Paths
Python’s `os` module and `pathlib` module provide functionalities to work with file paths.
#### Using `os.path`
```python
import os
# Get the current working directory
current_dir = os.getcwd()
print(current_dir)
# Join paths
file_path = os.path.join(current_dir, "example.txt")
print(file_path)
# Check if a path exists
print(os.path.exists(file_path)) # Output: True or False
# Check if it's a file or directory
print(os.path.isfile(file_path)) # Output: True or False
print(os.path.isdir(current_dir)) # Output: True or False
```
#### Using `pathlib`
The `pathlib` module provides an object-oriented approach to handle file paths.
```python
from pathlib import Path
# Get the current working directory
current_dir = Path.cwd()
print(current_dir)
# Join paths
file_path = current_dir / "example.txt"
print(file_path)
# Check if a path exists
print(file_path.exists()) # Output: True or False
# Check if it's a file or directory
print(file_path.is_file()) # Output: True or False
print(current_dir.is_dir()) # Output: True or False
```
### Directory Operations
You can create, remove, and list directories using `os` and `pathlib`.
#### Using `os`
```python
import os
# Create a new directory
os.mkdir("new_folder")
# Remove a directory
os.rmdir("new_folder")
# List contents of a directory
print(os.listdir(current_dir))
```
#### Using `pathlib`
```python
from pathlib import Path
# Create a new directory
new_folder = current_dir / "new_folder"
new_folder.mkdir()
# Remove a directory
new_folder.rmdir()
# List contents of a directory
print(list(current_dir.iterdir()))
```
### Practical Examples
#### Example 1: Reading a Large File
Reading a file line by line is memory efficient, especially for large files.
```python
with open("large_file.txt", "r") as file:
for line in file:
print(line.strip())
```
#### Example 2: Writing Log Entries to a File
Appending log entries to a file using the `a` mode.
```python
import datetime
def log_entry(message):
with open("logfile.txt", "a") as file:
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
file.write(f"{timestamp} - {message}\n")
log_entry("This is a log message.")
```
#### Example 3: Parsing a Config File
Reading and parsing a simple configuration file.
```python
# config.txt
# username=admin
# password=1234
def read_config(file_path):
config = {}
with open(file_path, "r") as file:
for line in file:
key, value = line.strip().split("=")
config[key] = value
return config
config = read_config("config.txt")
print(config)
```
Chapter 9: Error and Exception Handling
## Understanding Exceptions
### What are Exceptions?
Exceptions are errors that occur during the execution of a program. When an exception is raised, the normal flow of the program is interrupted, and the program looks for a way to handle the exception. If the exception is not handled, the program terminates with an error message.
### Common Built-in Exceptions
Python has several built-in exceptions that are raised for common errors.
- **`SyntaxError`**: Raised when there is an error in the syntax.
```python
# SyntaxError Example
print("Hello, World" # Missing closing parenthesis
```
- **`TypeError`**: Raised when an operation or function is applied to an object of inappropriate type.
```python
# TypeError Example
result = '2' + 2 # Cannot add a string and an integer
```
- **`ValueError`**: Raised when a function receives an argument of the right type but an inappropriate value.
```python
# ValueError Example
num = int("abc") # Cannot convert string to integer
```
- **`IndexError`**: Raised when a sequence subscript is out of range.
```python
# IndexError Example
numbers = [1, 2, 3]
print(numbers[5]) # Index out of range
```
- **`KeyError`**: Raised when a dictionary key is not found.
```python
# KeyError Example
my_dict = {"name": "Alice"}
print(my_dict["age"]) # Key 'age' not found
```
### The Exception Hierarchy
Python’s exceptions are organized in a hierarchy. The base class for all exceptions is `BaseException`. Most user-defined exceptions are derived from the `Exception` class.
```python
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- Exception
+-- StopIteration
+-- ArithmeticError
| +-- OverflowError
| +-- ZeroDivisionError
| +-- FloatingPointError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- ...
```
## Handling Exceptions
### Using `try`, `except` Blocks
To handle exceptions, you use the `try` block to write the code that might raise an exception and the `except` block to handle the exception.
```python
try:
# Code that might raise an exception
result = 10 / 0
except ZeroDivisionError:
# Code to handle the exception
print("Cannot divide by zero.")
```
### Multiple Exceptions
You can handle multiple exceptions by specifying multiple `except` blocks.
```python
try:
# Code that might raise multiple exceptions
num = int(input("Enter a number: "))
result = 10 / num
except ValueError:
print("Invalid input. Please enter a valid number.")
except ZeroDivisionError:
print("Cannot divide by zero.")
```
### Finally Block
The `finally` block contains code that will run no matter whether an exception occurs or not. It is typically used for cleanup actions, like closing files or releasing resources.
```python
try:
file = open("example.txt", "r")
# Code that might raise an exception
except FileNotFoundError:
print("File not found.")
finally:
file.close()
print("File closed.")
```
### Else Block
The `else` block executes if the `try` block does not raise an exception.
```python
try:
num = int(input("Enter a number: "))
except ValueError:
print("Invalid input.")
else:
print(f"You entered: {num}")
```
## Raising Exceptions
### Raising Built-in Exceptions
You can raise exceptions using the `raise` statement.
```python
def divide(a, b):
if b == 0:
raise ZeroDivisionError("Cannot divide by zero.")
return a / b
try:
result = divide(10, 0)
except ZeroDivisionError as e:
print(e)
```
### Creating Custom Exceptions
You can create custom exceptions by defining a new exception class that inherits from the `Exception` class.
```python
class CustomError(Exception):
"""Custom exception class."""
def __init__(self, message):
self.message = message
try:
raise CustomError("This is a custom error message.")
except CustomError as e:
print(e.message)
```
## Best Practices for Exception Handling
### Be Specific with Exception Handling
Catch specific exceptions rather than a generic `Exception` to ensure that you are handling known issues correctly.
```python
try:
# Code that might raise an exception
except ValueError:
# Handle ValueError
except IndexError:
# Handle IndexError
```
### Avoid Using Bare `except`
Using a bare `except` clause is discouraged because it can catch unexpected exceptions and hide programming errors.
```python
try:
# Code that might raise an exception
except Exception as e:
print(f"An error occurred: {e}")
```
### Clean Up Resources in `finally`
Use the `finally` block to clean up resources like closing files or network connections.
```python
try:
file = open("example.txt", "r")
# Code that might raise an exception
except FileNotFoundError:
print("File not found.")
finally:
file.close()
print("File closed.")
```
### Use `else` for Code That Should Run If No Exception Occurs
Use the `else` block for code that should run only if the `try` block does not raise an exception.
```python
try:
num = int(input("Enter a number: "))
except ValueError:
print("Invalid input.")
else:
print(f"You entered: {num}")
```
### Logging Exceptions
Consider logging exceptions to help with debugging and maintaining logs of issues.
```python
import logging
logging.basicConfig(filename='app.log', level=logging.ERROR)
try:
# Code that might raise an exception
result = 10 / 0
except ZeroDivisionError as e:
logging.error(f"Error occurred: {e}")
```
## Practical Examples
### Example 1: Validating User Input
A program that validates user input and handles exceptions gracefully.
```python
def get_integer(prompt):
while True:
try:
num = int(input(prompt))
return num
except ValueError:
print("Invalid input. Please enter an integer.")
number = get_integer("Enter a number: ")
print(f"You entered: {number}")
```
### Example 2: Reading a File Safely
A program that reads a file and handles exceptions if the file is not found or cannot be read.
```python
def read_file(filename):
try:
with open(filename, 'r') as file:
return file.read()
except FileNotFoundError:
return "File not found."
except IOError:
return "Error reading file."
content = read_file("example.txt")
print(content)
```
### Example 3: Network Request with Error Handling
A program that makes a network request and handles exceptions related to connectivity issues.
```python
import requests
def fetch_data(url):
try:
response = requests.get(url)
response.raise_for_status() # Raise HTTPError for bad responses
return response.text
except requests.exceptions.HTTPError as http_err:
return f"HTTP error occurred: {http_err}"
except requests.exceptions.ConnectionError as conn_err:
return f"Connection error occurred: {conn_err}"
except Exception as err:
return f"An error occurred: {err}"
url = "https://jsonplaceholder.typicode.com/posts"
data = fetch_data(url)
print(data)
```
Chapter 10: Object-Oriented Programming
## Classes and Objects
### Introduction to OOP
Object-Oriented Programming (OOP) is a programming paradigm based on the concept of objects. Objects are instances of classes, which can contain data (attributes) and code (methods). OOP provides a clear structure for programs and allows for code reusability through inheritance and polymorphism.
### Creating and Using Classes and Objects
A class is defined using the `class` keyword, followed by the class name and a colon. Methods are defined inside a class using the `def` keyword.
```python
class Person:
# Constructor method
def __init__(self, name, age):
self.name = name
self.age = age
# Method to display person details
def display(self):
print(f"Name: {self.name}, Age: {self.age}")
# Creating objects
person1 = Person("Alice", 30)
person2 = Person("Bob", 25)
# Accessing attributes and methods
person1.display() # Output: Name: Alice, Age: 30
person2.display() # Output: Name: Bob, Age: 25
```
### Class Attributes and Methods
Class attributes are shared among all instances of a class, while instance attributes are unique to each instance. Methods within a class can operate on both class attributes and instance attributes.
#### Class Attributes
```python
class Circle:
pi = 3.14159 # Class attribute
def __init__(self, radius):
self.radius = radius # Instance attribute
def area(self):
return Circle.pi * (self.radius ** 2)
circle1 = Circle(5)
circle2 = Circle(10)
print(circle1.area()) # Output: 78.53975
print(circle2.area()) # Output: 314.159
```
#### Instance Methods
Instance methods are functions defined inside a class that operate on instances of the class.
```python
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(f"{self.name} says Woof!")
dog1 = Dog("Buddy")
dog2 = Dog("Max")
dog1.bark() # Output: Buddy says Woof!
dog2.bark() # Output: Max says Woof!
```
## Inheritance
### Understanding Inheritance
Inheritance allows a class (child class) to inherit attributes and methods from another class (parent class). This promotes code reuse and establishes a hierarchical relationship between classes.
### Creating Subclasses
A subclass is created by specifying the parent class in parentheses after the subclass name.
```python
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement this method")
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Output: Buddy says Woof!
print(cat.speak()) # Output: Whiskers says Meow!
```
### Overriding Methods and Using `super()`
A subclass can override methods from its parent class. The `super()` function allows you to call methods from the parent class within the subclass.
```python
class Vehicle:
def __init__(self, make, model):
self.make = make
self.model = model
def start_engine(self):
return "Engine started"
class Car(Vehicle):
def __init__(self, make, model, num_doors):
super().__init__(make, model)
self.num_doors = num_doors
def start_engine(self):
parent_message = super().start_engine()
return f"{parent_message} in the car"
car = Car("Toyota", "Corolla", 4)
print(car.start_engine()) # Output: Engine started in the car
```
## Polymorphism
### Introduction to Polymorphism
Polymorphism allows objects of different classes to be treated as objects of a common superclass. It is achieved through method overriding and interfaces.
### Method Overloading and Overriding
#### Method Overloading
Python does not support method overloading by default. However, you can achieve similar functionality using default arguments.
```python
class Math:
def add(self, a, b, c=0):
return a + b + c
math = Math()
print(math.add(2, 3)) # Output: 5
print(math.add(2, 3, 4)) # Output: 9
```
#### Method Overriding
Method overriding allows a subclass to provide a specific implementation of a method already defined in its superclass.
```python
class Shape:
def area(self):
return 0
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
square = Square(4)
print(square.area()) # Output: 16
```
### Practical Examples of Polymorphism
Polymorphism can be achieved by defining methods in the superclass and overriding them in subclasses.
```python
class Bird:
def fly(self):
print("Birds can fly")
class Sparrow(Bird):
def fly(self):
print("Sparrows can fly")
class Ostrich(Bird):
def fly(self):
print("Ostriches cannot fly")
def show_flight(bird):
bird.fly()
sparrow = Sparrow()
ostrich = Ostrich()
show_flight(sparrow) # Output: Sparrows can fly
show_flight(ostrich) # Output: Ostriches cannot fly
```
## Encapsulation
### Understanding Encapsulation
Encapsulation is the practice of bundling data (attributes) and methods (functions) that operate on the data into a single unit (class). It also restricts direct access to some of the object's components, which is a way of preventing accidental interference and misuse.
### Using Private and Protected Members
In Python, there is no strict access control, but by convention, a leading underscore (`_`) denotes a protected member, and a double underscore (`__`) denotes a private member.
#### Protected Members
```python
class Employee:
def __init__(self, name, salary):
self._name = name
self._salary = salary
def display(self):
print(f"Name: {self._name}, Salary: {self._salary}")
employee = Employee("Alice", 50000)
print(employee._name) # Output: Alice (not recommended to access directly)
employee.display() # Output: Name: Alice, Salary: 50000
```
#### Private Members
```python
class Employee:
def __init__(self, name, salary):
self.__name = name
self.__salary = salary
def display(self):
print(f"Name: {self.__name}, Salary: {self.__salary}")
employee = Employee("Alice", 50000)
# print(employee.__name) # Raises AttributeError
employee.display() # Output: Name: Alice, Salary: 50000
```
### Getters and Setters
Getters and setters are methods used to access and modify private attributes.
```python
class Employee:
def __init__(self, name, salary):
self.__name = name
self.__salary = salary
def get_name(self):
return self.__name
def set_name(self, name):
self.__name = name
def get_salary(self):
return self.__salary
def set_salary(self, salary):
self.__salary = salary
employee = Employee("Alice", 50000)
print(employee.get_name()) # Output: Alice
employee.set_salary(55000)
print(employee.get_salary()) # Output: 55000
```
## Practical Examples
### Example 1: Library Management System
A simple example of a library management system using classes and objects.
```python
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def display(self):
print(f"Title: {self.title}, Author: {self.author}")
class Library:
def __init__(self):
self.books = []
def add_book(self, book):
self.books.append(book)
def show_books(self):
for book in self.books:
book.display()
book1 = Book("The Great Gatsby", "F. Scott Fitzgerald")
book2 = Book("1984", "George Orwell")
library = Library()
library.add_book(book1)
library.add_book(book2)
library.show_books()
```
### Example 2: Banking System
A simple example of a banking system using inheritance and encapsulation.
```python
class Account:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance
def deposit(self, amount):
self.__balance += amount
print(f"Deposited {amount}. New balance is {self.__balance}")
def withdraw(self, amount):
if amount > self.__balance:
print("Insufficient funds")
else:
self.__balance -= amount
print(f"Withdrew {amount}. New balance is {self.__balance}")
def get_balance(self):
return self.__balance
class Savings
Account(Account):
def __init__(self, owner, balance=0, interest_rate=0.02):
super().__init__(owner, balance)
self.interest_rate = interest_rate
def add_interest(self):
interest = self.get_balance() * self.interest_rate
self.deposit(interest)
savings = SavingsAccount("Alice", 1000)
savings.deposit(500)
savings.withdraw(200)
savings.add_interest()
print(f"Balance after interest: {savings.get_balance()}")
```
Chapter 11: Advanced Topics
## Decorators
### Introduction to Decorators
Decorators are a powerful and useful tool in Python that allows programmers to modify the behavior of a function or class. A decorator is a function that takes another function as an argument and extends or alters its behavior.
### Creating and Using Decorators
#### Basic Syntax
To create a decorator, you define a function that returns another function.
```python
def decorator_function(original_function):
def wrapper_function(*args, **kwargs):
print(f"Wrapper executed this before {original_function.__name__}")
return original_function(*args, **kwargs)
return wrapper_function
```
#### Applying Decorators
You apply a decorator to a function using the `@` symbol above the function definition.
```python
@decorator_function
def display():
print("Display function ran")
display()
```
#### Decorator with Arguments
Decorators can also accept arguments.
```python
def decorator_function_with_args(original_function):
def wrapper_function(*args, **kwargs):
print(f"Wrapper executed this before {original_function.__name__}")
return original_function(*args, **kwargs)
return wrapper_function
@decorator_function_with_args
def display_info(name, age):
print(f"Display info ran with arguments ({name}, {age})")
display_info("Alice", 30)
```
### Practical Use Cases for Decorators
#### Logging
Logging the execution of functions.
```python
def my_logger(orig_func):
import logging
logging.basicConfig(filename='{}.log'.format(orig_func.__name__), level=logging.INFO)
def wrapper(*args, **kwargs):
logging.info('Ran with args: {}, and kwargs: {}'.format(args, kwargs))
return orig_func(*args, **kwargs)
return wrapper
@my_logger
def display_info(name, age):
print(f"display_info ran with arguments ({name}, {age})")
display_info("Alice", 30)
```
#### Timing
Measuring the execution time of functions.
```python
def my_timer(orig_func):
import time
def wrapper(*args, **kwargs):
start = time.time()
result = orig_func(*args, **kwargs)
end = time.time()
print(f'{orig_func.__name__} ran in: {end-start} sec')
return result
return wrapper
@my_timer
def display_info(name, age):
print(f"display_info ran with arguments ({name}, {age})")
display_info("Alice", 30)
```
## Generators
### Understanding Generators
Generators are a special class of functions that simplify the task of writing iterators. A generator function allows you to declare a function that behaves like an iterator. They are written using the `yield` statement instead of `return`.
### Creating and Using Generators
#### Basic Generator Example
```python
def simple_generator():
yield 1
yield 2
yield 3
gen = simple_generator()
print(next(gen)) # Output: 1
print(next(gen)) # Output: 2
print(next(gen)) # Output: 3
```
#### Generator Expressions
Similar to list comprehensions, but they return a generator instead of a list.
```python
gen_exp = (x * x for x in range(3))
print(next(gen_exp)) # Output: 0
print(next(gen_exp)) # Output: 1
print(next(gen_exp)) # Output: 4
```
### Practical Use Cases for Generators
#### Reading Large Files
Reading large files line by line without loading the entire file into memory.
```python
def read_large_file(file_path):
with open(file_path) as file:
while True:
line = file.readline()
if not line:
break
yield line
for line in read_large_file("large_file.txt"):
print(line)
```
#### Generating Infinite Sequences
Creating an infinite sequence of numbers.
```python
def infinite_sequence():
num = 0
while True:
yield num
num += 1
gen = infinite_sequence()
print(next(gen)) # Output: 0
print(next(gen)) # Output: 1
print(next(gen)) # Output: 2
```
## Context Managers
### Introduction to Context Managers
Context managers allow you to allocate and release resources precisely when you want to. The most common use of context managers is with the `with` statement.
### Using the `with` Statement
The `with` statement simplifies exception handling by encapsulating common preparation and cleanup tasks in so-called context managers.
#### Basic Example
```python
with open("example.txt", "r") as file:
content = file.read()
print(content)
# No need to explicitly close the file; it's done automatically
```
### Creating Custom Context Managers
#### Using the `contextlib` Module
You can create a context manager using the `contextlib` module.
```python
from contextlib import contextmanager
@contextmanager
def open_file(file, mode):
f = open(file, mode)
yield f
f.close()
with open_file("example.txt", "w") as f:
f.write("Hello, World!")
```
#### Using a Class-based Approach
You can also create a context manager using a class with `__enter__` and `__exit__` methods.
```python
class OpenFile:
def __init__(self, file, mode):
self.file = file
self.mode = mode
def __enter__(self):
self.f = open(self.file, self.mode)
return self.f
def __exit__(self, exc_type, exc_val, exc_tb):
self.f.close()
with OpenFile("example.txt", "w") as f:
f.write("Hello, World!")
```
### Practical Use Cases for Context Managers
#### Database Connections
Managing database connections.
```python
import sqlite3
from contextlib import contextmanager
@contextmanager
def open_db(db_name):
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
try:
yield cursor
finally:
conn.commit()
conn.close()
with open_db("example.db") as cursor:
cursor.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)")
cursor.execute("INSERT INTO users (name) VALUES ('Alice')")
cursor.execute("SELECT * FROM users")
users = cursor.fetchall()
print(users)
```
#### Temporary Files
Creating and managing temporary files.
```python
import tempfile
from contextlib import contextmanager
@contextmanager
def temporary_file(suffix):
f = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
try:
yield f
finally:
f.close()
with temporary_file(".txt") as temp_file:
temp_file.write(b"Temporary file content")
print(temp_file.name)
```
## Practical Examples
### Example 1: A Decorator for Function Execution Time
Measuring the execution time of functions using a decorator.
```python
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Function {func.__name__} took {end_time - start_time} seconds to execute.")
return result
return wrapper
@timer
def compute_square(numbers):
return [x**2 for x in numbers]
numbers = range(1, 10000)
squares = compute_square(numbers)
```
### Example 2: A Generator for Fibonacci Sequence
Generating the Fibonacci sequence using a generator.
```python
def fibonacci_sequence(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
fib_gen = fibonacci_sequence(10)
for num in fib_gen:
print(num)
```
### Example 3: A Custom Context Manager for Logging
Creating a context manager for logging function execution.
```python
import logging
from contextlib import contextmanager
logging.basicConfig(filename='logfile.log', level=logging.INFO)
@contextmanager
def log_execution(func_name):
logging.info(f"Executing {func_name}")
yield
logging.info(f"Finished executing {func_name}")
def process_data(data):
with log_execution("process_data"):
result = [d * 2 for d in data]
return result
data = [1, 2, 3, 4]
result = process_data(data)
print(result)
```
-
Chapter 12: Working with Data
## NumPy for Numerical Data
### Introduction to NumPy
NumPy (Numerical Python) is a fundamental package for scientific computing in Python. It provides support for arrays, matrices, and many mathematical functions to operate on these data structures.
### Creating and Using Arrays
NumPy arrays are more efficient and convenient than Python lists for numerical operations.
#### Creating Arrays
You can create NumPy arrays from Python lists using the `numpy.array()` function.
```python
import numpy as np
# Creating a 1D array
arr1 = np.array([1, 2, 3, 4, 5])
print(arr1)
# Creating a 2D array
arr2 = np.array([[1, 2, 3], [4, 5, 6]])
print(arr2)
```
#### Array Attributes
NumPy arrays have various attributes that provide information about the array.
```python
print(arr1.ndim) # Number of dimensions
print(arr2.shape) # Shape of the array
print(arr1.size) # Total number of elements
print(arr1.dtype) # Data type of the elements
```
### Common Operations and Methods
NumPy provides a range of operations and methods for array manipulation and mathematical calculations.
#### Arithmetic Operations
Arithmetic operations can be performed element-wise on NumPy arrays.
```python
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
print(arr1 + arr2) # Output: [5 7 9]
print(arr1 - arr2) # Output: [-3 -3 -3]
print(arr1 * arr2) # Output: [4 10 18]
print(arr1 / arr2) # Output: [0.25 0.4 0.5]
```
#### Universal Functions (ufuncs)
NumPy provides universal functions for element-wise operations.
```python
arr = np.array([1, 4, 9, 16])
print(np.sqrt(arr)) # Output: [1. 2. 3. 4.]
print(np.exp(arr)) # Output: [2.71828183e+00 5.45981500e+01 8.10308393e+03 8.88611052e+06]
```
#### Slicing and Indexing
NumPy arrays can be sliced and indexed similarly to Python lists.
```python
arr = np.array([1, 2, 3, 4, 5])
print(arr[1:4]) # Output: [2 3 4]
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr2d[1, :]) # Output: [4 5 6]
print(arr2d[:, 1]) # Output: [2 5 8]
```
## Pandas for Data Analysis
### Introduction to Pandas
Pandas is a powerful data analysis library built on top of NumPy. It provides data structures like Series and DataFrame for data manipulation and analysis.
### Creating and Using DataFrames
DataFrames are 2-dimensional labeled data structures with columns of potentially different types.
#### Creating DataFrames
You can create DataFrames from various data sources, including lists, dictionaries, and files.
```python
import pandas as pd
# Creating a DataFrame from a dictionary
data = {
"Name": ["Alice", "Bob", "Charlie"],
"Age": [25, 30, 35],
"City": ["New York", "Los Angeles", "Chicago"]
}
df = pd.DataFrame(data)
print(df)
```
#### Reading Data from Files
Pandas provides functions to read data from various file formats, such as CSV, Excel, and SQL databases.
```python
# Reading a CSV file
df = pd.read_csv("data.csv")
print(df.head())
# Reading an Excel file
df = pd.read_excel("data.xlsx")
print(df.head())
```
### Data Manipulation and Analysis
#### Selecting and Filtering Data
You can select and filter data in a DataFrame using various methods.
```python
# Selecting a single column
print(df["Name"])
# Selecting multiple columns
print(df[["Name", "Age"]])
# Filtering rows based on a condition
print(df[df["Age"] > 30])
```
#### Adding and Removing Columns
You can add or remove columns in a DataFrame.
```python
# Adding a new column
df["Salary"] = [50000, 60000, 70000]
print(df)
# Removing a column
df = df.drop("Salary", axis=1)
print(df)
```
#### Handling Missing Data
Pandas provides methods to handle missing data.
```python
# Checking for missing values
print(df.isnull())
# Filling missing values
df.fillna(0, inplace=True)
# Dropping rows with missing values
df.dropna(inplace=True)
```
#### Grouping and Aggregating Data
You can group and aggregate data using the `groupby` method.
```python
grouped = df.groupby("City")
print(grouped["Age"].mean())
```
## Matplotlib for Data Visualization
### Introduction to Matplotlib
Matplotlib is a plotting library for creating static, animated, and interactive visualizations in Python.
### Creating Basic Plots
You can create various types of plots using Matplotlib.
#### Line Plot
```python
import matplotlib.pyplot as plt
x = [1, 2, 3, 4]
y = [10, 20, 25, 30]
plt.plot(x, y)
plt.title("Line Plot")
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
plt.show()
```
#### Bar Plot
```python
categories = ["A", "B", "C", "D"]
values = [4, 7, 1, 8]
plt.bar(categories, values)
plt.title("Bar Plot")
plt.xlabel("Categories")
plt.ylabel("Values")
plt.show()
```
#### Scatter Plot
```python
x = [1, 2, 3, 4, 5]
y = [2, 3, 5, 7, 11]
plt.scatter(x, y)
plt.title("Scatter Plot")
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
plt.show()
```
### Customizing and Saving Plots
You can customize plots by adding titles, labels, and legends, and you can save them to files.
```python
x = [1, 2, 3, 4]
y1 = [1, 4, 9, 16]
y2 = [2, 4, 6, 8]
plt.plot(x, y1, label="y1")
plt.plot(x, y2, label="y2")
plt.title("Custom Plot")
plt.xlabel("X-axis")
plt.ylabel("Y-axis")
plt.legend()
plt.savefig("plot.png")
plt.show()
```
## Practical Examples
### Example 1: Analyzing Sales Data with Pandas
Analyzing sales data to find the total sales by product and visualize the results.
```python
import pandas as pd
import matplotlib.pyplot as plt
# Reading sales data from a CSV file
df = pd.read_csv("sales_data.csv")
# Grouping data by product and calculating total sales
total_sales = df.groupby("Product")["Sales"].sum()
# Plotting the total sales by product
total_sales.plot(kind="bar")
plt.title("Total Sales by Product")
plt.xlabel("Product")
plt.ylabel("Total Sales")
plt.show()
```
### Example 2: Visualizing Temperature Trends with Matplotlib
Creating a line plot to visualize temperature trends over a year.
```python
import pandas as pd
import matplotlib.pyplot as plt
# Reading temperature data from a CSV file
df = pd.read_csv("temperature_data.csv")
# Creating a line plot of temperature trends
plt.plot(df["Month"], df["Temperature"])
plt.title("Temperature Trends Over a Year")
plt.xlabel("Month")
plt.ylabel("Temperature")
plt.show()
```
### Example 3: Calculating Statistical Measures with NumPy
Using NumPy to calculate statistical measures such as mean, median, and standard deviation.
```python
import numpy as np
data = [10, 20, 30, 40, 50]
mean = np.mean(data)
median = np.median(data)
std_dev = np.std(data)
print(f"Mean: {mean}")
print(f"Median: {median}")
print(f"Standard Deviation: {std_dev}")
```
Chapter 13: Web Development
## Introduction to Flask
### Overview of Web Development
Web development involves creating websites or web applications that run in a browser. It consists of two main parts:
- **Frontend Development**: Involves creating the user interface using HTML, CSS, and JavaScript.
- **Backend Development**: Involves creating the server-side logic, managing databases, and ensuring security.
### Setting Up Flask
Flask is a lightweight web framework for Python that allows you to build web applications quickly and with minimal overhead.
#### Installing Flask
You can install Flask using `pip`.
```bash
pip install flask
```
#### Creating a Simple Flask Application
A basic Flask application consists of creating an instance of the Flask class and defining routes.
```python
from flask import Flask
app = Flask(__name__)
@app.route("/")
def home():
return "Hello, World!"
if __name__ == "__main__":
app.run(debug=True)
```
### Running the Flask Application
To run the application, save the code in a file (e.g., `app.py`) and run it.
```bash
python app.py
```
Open your browser and navigate to `http://127.0.0.1:5000/` to see the output.
## Building a Simple Web Application
### Understanding Routes and Views
Routes are used to map URLs to functions in your Flask application. Each route is associated with a specific URL pattern and a view function that returns the response.
```python
@app.route("/about")
def about():
return "This is the About page."
```
### Handling Forms and User Input
Flask provides functionalities to handle user input through forms.
#### Creating a Form
Create an HTML form to collect user input.
```html
<!-- templates/form.html -->
<!DOCTYPE html>
<html>
<head>
<title>Form</title>
</head>
<body>
<form action="/submit" method="POST">
<label for="name">Name:</label>
<input type="text" id="name" name="name">
<button type="submit">Submit</button>
</form>
</body>
</html>
```
#### Handling Form Submission
Create a route to handle the form submission.
```python
from flask import request, render_template
@app.route("/form")
def form():
return render_template("form.html")
@app.route("/submit", methods=["POST"])
def submit():
name = request.form["name"]
return f"Hello, {name}!"
```
### Using Templates
Flask uses Jinja2 as its template engine. Templates help separate the presentation layer from the application logic.
#### Creating Templates
Create a directory named `templates` and add your HTML files there.
```html
<!-- templates/home.html -->
<!DOCTYPE html>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1>Welcome to Flask</h1>
<p>This is the home page.</p>
</body>
</html>
```
#### Rendering Templates
Use the `render_template` function to render templates.
```python
@app.route("/")
def home():
return render_template("home.html")
```
### Practical Examples
#### Example 1: Simple Blog Application
Create a simple blog application with Flask.
##### Directory Structure
```
blog/
app.py
templates/
home.html
post.html
```
##### app.py
```python
from flask import Flask, render_template
app = Flask(__name__)
posts = [
{"title": "Post 1", "content": "Content of post 1"},
{"title": "Post 2", "content": "Content of post 2"},
]
@app.route("/")
def home():
return render_template("home.html", posts=posts)
@app.route("/post/<int:post_id>")
def post(post_id):
post = posts[post_id - 1]
return render_template("post.html", post=post)
if __name__ == "__main__":
app.run(debug=True)
```
##### home.html
```html
<!DOCTYPE html>
<html>
<head>
<title>Blog</title>
</head>
<body>
<h1>Blog</h1>
<ul>
{% for post in posts %}
<li><a href="/post/{{ loop.index }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
</body>
</html>
```
##### post.html
```html
<!DOCTYPE html>
<html>
<head>
<title>{{ post.title }}</title>
</head>
<body>
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
<a href="/">Back to Home</a>
</body>
</html>
```
## Working with APIs
### Introduction to APIs
APIs (Application Programming Interfaces) allow different software systems to communicate with each other. They can be used to access web services, databases, or other resources.
### Making API Requests
You can use the `requests` library to make HTTP requests in Python.
#### Installing Requests
Install the `requests` library using `pip`.
```bash
pip install requests
```
#### Making GET Requests
You can make a GET request to fetch data from an API.
```python
import requests
response = requests.get("https://jsonplaceholder.typicode.com/posts")
posts = response.json()
for post in posts:
print(post["title"])
```
#### Making POST Requests
You can make a POST request to send data to an API.
```python
import requests
data = {"title": "New Post", "body": "This is a new post", "userId": 1}
response = requests.post("https://jsonplaceholder.typicode.com/posts", json=data)
print(response.json())
```
### Handling JSON Responses
APIs often return data in JSON format. You can use the `json` method of the response object to parse JSON data.
```python
import requests
response = requests.get("https://jsonplaceholder.typicode.com/users")
users = response.json()
for user in users:
print(user["name"])
```
### Practical Examples
#### Example 1: Weather Application
Create a simple weather application using Flask and an external weather API.
##### Directory Structure
```
weather_app/
app.py
templates/
home.html
weather.html
```
##### app.py
```python
from flask import Flask, request, render_template
import requests
app = Flask(__name__)
@app.route("/")
def home():
return render_template("home.html")
@app.route("/weather", methods=["POST"])
def weather():
city = request.form["city"]
api_key = "YOUR_API_KEY"
response = requests.get(f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric")
weather_data = response.json()
return render_template("weather.html", weather_data=weather_data)
if __name__ == "__main__":
app.run(debug=True)
```
##### home.html
```html
<!DOCTYPE html>
<html>
<head>
<title>Weather App</title>
</head>
<body>
<h1>Weather App</h1>
<form action="/weather" method="POST">
<label for="city">City:</label>
<input type="text" id="city" name="city" required>
<button type="submit">Get Weather</button>
</form>
</body>
</html>
```
##### weather.html
```html
<!DOCTYPE html>
<html>
<head>
<title>Weather in {{ weather_data['name'] }}</title>
</head>
<body>
<h1>Weather in {{ weather_data['name'] }}</h1>
<p>Temperature: {{ weather_data['main']['temp'] }} °C</p>
<p>Weather: {{ weather_data['weather'][0]['description'] }}</p>
<a href="/">Back to Home</a>
</body>
</html>
```
Chapter 14: Testing and Debugging
## Writing Tests with `unittest`
### Introduction to Unit Testing
Unit testing is the process of testing individual units or components of a program to ensure they work as expected. Python’s `unittest` module, inspired by Java's JUnit, provides a framework for creating and running tests.
### Setting Up `unittest`
#### Creating a Test Case
A test case is a single unit of testing. It checks for a specific response to a set of inputs.
```python
import unittest
class TestMathOperations(unittest.TestCase):
def test_addition(self):
self.assertEqual(1 + 1, 2)
if __name__ == "__main__":
unittest.main()
```
### Writing and Running Tests
#### Common Assertions
`unittest` provides several assertion methods to check for various conditions.
- **`assertEqual(a, b)`**: Checks if `a` equals `b`.
- **`assertNotEqual(a, b)`**: Checks if `a` does not equal `b`.
- **`assertTrue(x)`**: Checks if `x` is `True`.
- **`assertFalse(x)`**: Checks if `x` is `False`.
- **`assertIsNone(x)`**: Checks if `x` is `None`.
- **`assertIsNotNone(x)`**: Checks if `x` is not `None`.
```python
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual("foo".upper(), "FOO")
def test_isupper(self):
self.assertTrue("FOO".isupper())
self.assertFalse("Foo".isupper())
def test_split(self):
s = "hello world"
self.assertEqual(s.split(), ["hello", "world"])
with self.assertRaises(TypeError):
s.split(2)
if __name__ == "__main__":
unittest.main()
```
### Using Setup and Teardown Methods
Setup and teardown methods are used to prepare the environment before and clean up after each test method runs.
- **`setUp()`**: This method is run before each test method.
- **`tearDown()`**: This method is run after each test method.
```python
import unittest
class TestMathOperations(unittest.TestCase):
def setUp(self):
self.a = 10
self.b = 20
def tearDown(self):
pass
def test_addition(self):
result = self.a + self.b
self.assertEqual(result, 30)
if __name__ == "__main__":
unittest.main()
```
## Debugging Techniques
### Common Debugging Techniques
Debugging is the process of finding and fixing errors in a program. Here are some common techniques:
#### Print Statements
Using print statements is the simplest form of debugging.
```python
def add(a, b):
result = a + b
print(f"Adding {a} and {b} to get {result}")
return result
add(2, 3)
```
#### Logging
The `logging` module provides a flexible framework for emitting log messages from Python programs.
```python
import logging
logging.basicConfig(level=logging.DEBUG)
def add(a, b):
logging.debug(f"Adding {a} and {b}")
return a + b
add(2, 3)
```
#### Using Assertions
Assertions are statements that assert a condition is `True`. If the condition is `False`, the program will raise an `AssertionError`.
```python
def divide(a, b):
assert b != 0, "Division by zero!"
return a / b
print(divide(10, 2)) # Works fine
print(divide(10, 0)) # Raises AssertionError
```
### Using Debugging Tools
#### Using PDB for Debugging
The Python Debugger (`pdb`) is a powerful tool for debugging Python programs. It allows you to set breakpoints, step through code, and inspect variables.
##### Basic Commands
- **`break` or `b`**: Set a breakpoint.
- **`continue` or `c`**: Continue execution until the next breakpoint.
- **`step` or `s`**: Step into a function.
- **`next` or `n`**: Execute the next line.
- **`list` or `l`**: List the source code around the current line.
- **`print` or `p`**: Print the value of an expression.
```python
import pdb
def add(a, b):
pdb.set_trace()
return a + b
print(add(2, 3))
```
#### Using Debugging Tools in IDEs
Integrated Development Environments (IDEs) like PyCharm, VSCode, and Jupyter Notebooks come with built-in debugging tools that provide a graphical interface for debugging.
##### PyCharm Debugger
- **Setting Breakpoints**: Click in the gutter next to the line number to set a breakpoint.
- **Running the Debugger**: Click the debug icon or press `Shift+F9`.
- **Step Over/Into**: Use the toolbar buttons or keyboard shortcuts to step over (`F8`) or step into (`F7`) code.
##### VSCode Debugger
- **Setting Breakpoints**: Click in the gutter next to the line number to set a breakpoint.
- **Running the Debugger**: Press `F5` or click the debug icon in the sidebar.
- **Step Over/Into**: Use the toolbar buttons or keyboard shortcuts to step over (`F10`) or step into (`F11`) code.
## Practical Examples
### Example 1: Testing a Calculator Module
Create a simple calculator module and write unit tests for it.
#### calculator.py
```python
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero!")
return a / b
```
#### test_calculator.py
```python
import unittest
import calculator
class TestCalculator(unittest.TestCase):
def test_add(self):
self.assertEqual(calculator.add(2, 3), 5)
def test_subtract(self):
self.assertEqual(calculator.subtract(5, 3), 2)
def test_multiply(self):
self.assertEqual(calculator.multiply(2, 3), 6)
def test_divide(self):
self.assertEqual(calculator.divide(6, 3), 2)
with self.assertRaises(ValueError):
calculator.divide(6, 0)
if __name__ == "__main__":
unittest.main()
```
### Example 2: Debugging a Sorting Function
Debug a sorting function that is not working as expected.
#### sort.py
```python
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
arr = [64, 34, 25, 12, 22, 11, 90]
print("Sorted array:", bubble_sort(arr))
```
#### Debugging with PDB
```python
import pdb
def bubble_sort(arr):
pdb.set_trace()
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
arr = [64, 34, 25, 12, 22, 11, 90]
print("Sorted array:", bubble_sort(arr))
```
### Example 3: Logging in a Web Application
Add logging to a Flask web application to debug issues.
#### app.py
```python
from flask import Flask, request, jsonify
import logging
app = Flask(__name__)
logging.basicConfig(filename='app.log', level=logging.DEBUG)
@app.route('/divide', methods=['POST'])
def divide():
data = request.get_json()
try:
numerator = data['numerator']
denominator = data['denominator']
result = numerator / denominator
return jsonify({'result': result})
except ZeroDivisionError as e:
logging.error(f"ZeroDivisionError: {e}")
return jsonify({'error': 'Cannot divide by zero'}), 400
except Exception as e:
logging.error(f"Error: {e}")
return jsonify({'error': 'An error occurred'}), 500
if __name__ == '__main__':
app.run(debug=True)
```
Chapter 15: Project: Building a Real-World Application
## Planning Your Project
### Defining the Project Scope
The first step in building a real-world application is to define its scope. Clearly outline the purpose, goals, and functionalities of the project.
#### Example: Task Management Application
Let's build a simple task management application where users can:
- Add new tasks
- View existing tasks
- Update tasks
- Delete tasks
### Designing the Architecture
Design the architecture of your application, including the front-end and back-end components, data storage, and any third-party services.
#### Example Architecture
- **Frontend**: HTML, CSS, JavaScript (possibly with a framework like React or Vue.js)
- **Backend**: Flask (Python)
- **Database**: SQLite (for simplicity)
### Setting Up Version Control
Using version control is essential for managing changes to your codebase. Git is a popular version control system.
#### Initializing a Git Repository
```bash
git init
```
#### Creating a `.gitignore` File
Create a `.gitignore` file to exclude unnecessary files from being tracked by Git.
```
__pycache__/
*.pyc
instance/
.env
```
## Implementing Features
### Setting Up the Flask Application
Start by setting up a basic Flask application.
#### Directory Structure
```
task_manager/
app.py
templates/
base.html
index.html
add_task.html
edit_task.html
static/
style.css
instance/
config.py
```
#### app.py
```python
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class Task(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(200), nullable=False)
done = db.Column(db.Boolean, default=False)
@app.route('/')
def index():
tasks = Task.query.all()
return render_template('index.html', tasks=tasks)
@app.route('/add', methods=['GET', 'POST'])
def add_task():
if request.method == 'POST':
title = request.form['title']
description = request.form['description']
new_task = Task(title=title, description=description)
db.session.add(new_task)
db.session.commit()
return redirect(url_for('index'))
return render_template('add_task.html')
@app.route('/edit/<int:id>', methods=['GET', 'POST'])
def edit_task(id):
task = Task.query.get_or_404(id)
if request.method == 'POST':
task.title = request.form['title']
task.description = request.form['description']
task.done = 'done' in request.form
db.session.commit()
return redirect(url_for('index'))
return render_template('edit_task.html', task=task)
@app.route('/delete/<int:id>')
def delete_task(id):
task = Task.query.get_or_404(id)
db.session.delete(task)
db.session.commit()
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)
```
#### Database Setup
Create the database and tables.
```python
from app import db
db.create_all()
```
### Creating the Frontend
#### base.html
```html
<!DOCTYPE html>
<html>
<head>
<title>Task Manager</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<header>
<h1>Task Manager</h1>
<nav>
<a href="{{ url_for('index') }}">Home</a>
<a href="{{ url_for('add_task') }}">Add Task</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
```
#### index.html
```html
{% extends 'base.html' %}
{% block content %}
<h2>Tasks</h2>
<ul>
{% for task in tasks %}
<li>
<strong>{{ task.title }}</strong> - {{ task.description }}
<a href="{{ url_for('edit_task', id=task.id) }}">Edit</a>
<a href="{{ url_for('delete_task', id=task.id) }}">Delete</a>
</li>
{% endfor %}
</ul>
{% endblock %}
```
#### add_task.html
```html
{% extends 'base.html' %}
{% block content %}
<h2>Add Task</h2>
<form method="post">
<label for="title">Title:</label>
<input type="text" id="title" name="title" required>
<label for="description">Description:</label>
<textarea id="description" name="description" required></textarea>
<button type="submit">Add Task</button>
</form>
{% endblock %}
```
#### edit_task.html
```html
{% extends 'base.html' %}
{% block content %}
<h2>Edit Task</h2>
<form method="post">
<label for="title">Title:</label>
<input type="text" id="title" name="title" value="{{ task.title }}" required>
<label for="description">Description:</label>
<textarea id="description" name="description" required>{{ task.description }}</textarea>
<label for="done">Done:</label>
<input type="checkbox" id="done" name="done" {% if task.done %}checked{% endif %}>
<button type="submit">Save Changes</button>
</form>
{% endblock %}
```
### Styling the Application
Add some basic CSS to style the application.
#### style.css
```css
body {
font-family: Arial, sans-serif;
background-color: #f8f8f8;
margin: 0;
padding: 0;
}
header {
background-color: #333;
color: white;
padding: 1em 0;
text-align: center;
}
nav a {
color: white;
margin: 0 1em;
text-decoration: none;
}
main {
padding: 2em;
}
form {
display: flex;
flex-direction: column;
}
label {
margin-top: 1em;
}
input, textarea, button {
margin-top: 0.5em;
padding: 0.5em;
font-size: 1em;
}
button {
background-color: #333;
color: white;
border: none;
cursor: pointer;
margin-top: 1em;
}
button:hover {
background-color: #555;
}
```
## Testing and Deployment
### Final Testing
Before deploying your application, thoroughly test all functionalities to ensure everything works as expected.
#### Testing Checklist
- Add new tasks
- View tasks
- Edit tasks
- Delete tasks
- Check for validation errors
- Test edge cases (e.g., very long task titles or descriptions)
### Deploying the Application
Deploying a Flask application can be done on various platforms such as Heroku, AWS, or Google Cloud.
#### Example: Deploying to Heroku
1. **Install Heroku CLI**:
```bash
brew tap heroku/brew && brew install heroku
```
2. **Login to Heroku**:
```bash
heroku login
```
3. **Create a Heroku App**:
```bash
heroku create
```
4. **Prepare the Application for Deployment**:
- **Procfile**: Create a `Procfile` to specify the commands that are executed by the app on startup.
```
web: gunicorn app:app
```
- **requirements.txt**: List the dependencies of your project.
```txt
Flask==2.0.1
Flask-SQLAlchemy==2.5.1
gunicorn==20.1.0
```
5. **Deploy the Application**:
```bash
git add .
git commit -m "Initial commit"
git push heroku main
```
6. **Open the Application**:
```bash
heroku open
```
### Continuous Integration and Deployment
Set up continuous integration and deployment (CI/CD) pipelines using tools like GitHub Actions, Travis CI, or CircleCI to automate testing and deployment.
#### Example: GitHub Actions Workflow
Create a `.github/workflows/main.yml` file to define the CI/CD workflow.
```yaml
name: Flask CI/CD
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: |
python -m unittest discover
- name: Deploy to Heroku
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: |
heroku git:remote -a <your-heroku-app-name>
git push heroku main
```