---
# System prepended metadata

title: Notes on Programming & Python

---


# Notes on Programming & Python

## 1. What is a Programming Language?

* A **programming language** is a set of instructions, rules, and syntax that allows humans to communicate with computers.
* It is used to write programs that tell the computer what to do.
* Examples: Python, C, Java, JavaScript.

---

## 2. Types of Programming Languages

### (a) Low-Level Languages

* Closer to machine code (binary).
* Harder for humans to understand but very fast for computers.
* Examples:

  * **Machine Language** (direct 0s and 1s).
  * **Assembly Language** (uses short codes like `MOV`, `ADD`).
* Advantages: High speed, efficient use of memory.
* Disadvantages: Difficult to write and understand.

### (b) High-Level Languages

* Closer to human language, easier to write and understand.
* Uses English-like syntax.
* Examples: Python, Java, C++, JavaScript.
* Advantages: Easy to learn, portable across platforms.
* Disadvantages: Slightly slower than low-level languages (because they need translation).

---

## 3. What is Python?

* Python is a **high-level, interpreted, and general-purpose programming language**.
* Created by **Guido van Rossum** in **1991**.
* Known for being **simple, readable, and versatile**.
* Used in: Web development, Data Science, Machine Learning, Artificial Intelligence, Automation, etc.

---

## 4. Python Installation and VS Code

### Installing Python

1. Go to [python.org](https://www.python.org/downloads/).
2. Download the latest version for your OS (Windows/Mac/Linux).
3. Install and make sure to check **“Add Python to PATH”**.

### Installing VS Code

1. Download from [code.visualstudio.com](https://code.visualstudio.com/).
2. Install and open.
3. Install the **Python extension** from VS Code marketplace.
4. Set interpreter: `Ctrl + Shift + P` → "Python: Select Interpreter".

---

## 5. Python Execution Process

Python code can be executed in **two main ways**:

1. **Interactive Mode**

   * Open terminal and type `python` or `python3`.
   * Write code line by line and get instant output.
   * Example:

     ```python
     >>> print("Hello, World!")
     Hello, World!
     ```

2. **Script Mode**

   * Write code in a `.py` file (example: `program.py`).
   * Run it in terminal with:

     ```bash
     python program.py
     ```

**Execution Process Behind the Scenes**:

* Python code → **Python Interpreter** → **Bytecode** → **Python Virtual Machine (PVM)** → Output.

![Python Execution Process](https://hackmd.io/_uploads/BkNeLvJKlg.png)


---

## 5. Variables in Python

* A **variable** is a name that stores a value in memory.
* It acts like a container for data.
* Example:

  ```python
  x = 10        # integer
  name = "Ram"  # string
  temperature = 28.6     # float
  ```

---

## 6. Variable Naming Rules

* Variable names must follow these rules:

  1. Can only contain letters (a-z, A-Z), digits (0-9), and underscores `_`.
  2. Must **start with a letter or underscore**, not a digit.
  3. Case-sensitive (`age`, `Age`, and `AGE` are different).
  4. Cannot use Python **keywords** (like `for`, `if`, `while`, `class`).

✅ Valid examples:

```python
student_name = "Hari"
_age = 25
number1 = 100
```

❌ Invalid examples:

```python
1number = 50        # cannot start with digit
student-name = "Hari" # cannot use '-'
for = 10            # keyword not allowed
```

---

## 7. Snake Case Naming Convention

* In Python, variable names usually follow **snake\_case** style.
* Words are written in **lowercase** and separated by underscores `_`.
* Example:

  ```python
  student_age = 20
  total_marks = 480
  average_score = 96
  ```

---

## 8. Constants in Python

* A **constant** is a variable whose value should not change throughout the program.
* Python does not have true constants, but by **naming convention** we use **UPPERCASE letters**.
* Example:

  ```python
  PI = 3.14159
  GRAVITY = 9.8
  MAX_USERS = 100
  ```

---

## 10. Comments in Python
- **Comments** are notes inside the code that Python ignores during execution.
- Used to explain code and make it easier to understand.

### Types of Comments
1. **Single-line Comment**
   ```python
   # This is a single-line comment
   print("Hello, World!")

1. **Multi-line Comment**
   ```python
   '''
   This is a
   multi-line comment
   '''

---

## 11. Advantages of Comments

* Makes code easier to read and understand.
* Helps other programmers (or future you) know what the code does.
* Useful for debugging (can “comment out” code temporarily).
* Acts as documentation for projects.

---

## 12. Data Types in Python

### Need of Data Types

* Define **what kind of value** is stored in a variable.
* Help Python decide:

  1. How much memory is required.
  2. What operations are allowed.
  3. How the value is represented internally.

**Example**

* `10 + 20 = 30` → numbers are added
* `"10" + "20" = "1020"` → strings are concatenated

---

### Classification of Data Types

1. **Primitive Data Types** (basic types)
2. **Non-Primitive Data Types** (collections, user-defined) → *Will study later*

---

### Primitive Data Types in Python

* **Integer (`int`)** → whole numbers

  ```python
  age = 25
  ```

* **Float (`float`)** → decimal numbers

  ```python
  PI = 3.14
  ```

* **Complex (`complex`)** → real + imaginary

  ```python
  z = 2 + 3j
  ```

* **String (`str`)** → text data

  ```python
  name = "Ram"
  ```

* **Boolean (`bool`)** → truth values

  ```python
  is_student = True
  ```

* **NoneType (`None`)** → represents no value

  ```python
  x = None
  ```



---

# Python Primitive Data Types

## 13. Integer (`int`)

* **Definition:** Whole numbers without decimal points.
* **Usage:** Counting, indexing, arithmetic calculations.
* **Examples:**

```python
age = 25
year = 2025
balance = -100
```

* **Operations:**

  1. Addition: `+` → `5 + 3 = 8`
  2. Subtraction: `-` → `5 - 3 = 2`
  3. Multiplication: `*` → `5 * 3 = 15`
  4. Division: `/` → `5 / 2 = 2.5` (returns float)
  5. Floor division: `//` → `5 // 2 = 2` (integer result)
  6. Modulus: `%` → `5 % 2 = 1` (remainder)
  7. Exponent: `**` → `5 ** 2 = 25`

---

## 14. Floating Point (`float`)

* **Definition:** Numbers with decimal points.
* **Usage:** Measurements, money, scientific calculations.
* **Examples:**

```python
price = 99.99
temperature = -4.5
PI = 3.14159
```

* **Operations:** Same as integers, results may include decimal points.

```python
a = 5.0
b = 2.0
print(a / b)   # 2.5
print(a // b)  # 2.0
```

* **Note:** Division `/` always returns a float.


---

## 15. Boolean (`bool`)

* **Definition:** Data type with only **two values**: `True` or `False`.
* **Usage:** Represent conditions, control flow, and logical operations.
* **Examples:**

```python
is_active = True
is_logged_in = False
```

### 15.1 Boolean Operations

1. `and` → True if **both True**
2. `or` → True if **at least one True**
3. `not` → Reverses the value

```python
a = True
b = False
print(a and b)  # False
print(a or b)   # True
print(not a)    # False
```

### 15.2 Logical Operator Precedence


* **Logical Operator Precedence** in Python (from highest to lowest):

  1. `not` → evaluated first
  2. `and` → evaluated second
  3. `or` → evaluated last

* **Example:**

```python
a = True
b = False
c = True

result = a or b and not c
# Step 1: not c → not True → False
# Step 2: b and False → False and False → False
# Step 3: a or False → True or False → True

print(result)  # True
```

---

## 16. Operators in Python

Operators are symbols that **perform operations** on values or variables. Python supports several types of operators.

---

### 16.1 Arithmetic Operators

* Used to perform **mathematical calculations**.

| Operator | Description         | Example       |
| -------- | ------------------- | ------------- |
| `+`      | Addition            | `5 + 3 = 8`   |
| `-`      | Subtraction         | `5 - 3 = 2`   |
| `*`      | Multiplication      | `5 * 3 = 15`  |
| `/`      | Division            | `5 / 2 = 2.5` |
| `//`     | Floor Division      | `5 // 2 = 2`  |
| `%`      | Modulus (remainder) | `5 % 2 = 1`   |
| `**`     | Exponentiation      | `5 ** 2 = 25` |

```python
a = 7
b = 3
print(a + b)  # 10
print(a ** b) # 343
```

---

### 16.2 Comparison Operators

* Used to **compare values**. Returns **Boolean** (`True` or `False`).

| Operator | Description      | Example          |
| -------- | ---------------- | ---------------- |
| `==`     | Equal            | `5 == 3 → False` |
| `!=`     | Not equal        | `5 != 3 → True`  |
| `>`      | Greater than     | `5 > 3 → True`   |
| `<`      | Less than        | `5 < 3 → False`  |
| `>=`     | Greater or equal | `5 >= 5 → True`  |
| `<=`     | Less or equal    | `3 <= 5 → True`  |

```python
x = 10
y = 20
print(x < y)   # True
print(x == y)  # False
```

---

### 16.3 Assignment Operators

* Used to **assign values** to variables.

| Operator | Description             | Example                |
| -------- | ----------------------- | ---------------------- |
| `=`      | Assign                  | `x = 5`                |
| `+=`     | Add and assign          | `x += 3 → x = x + 3`   |
| `-=`     | Subtract and assign     | `x -= 2 → x = x - 2`   |
| `*=`     | Multiply and assign     | `x *= 2 → x = x * 2`   |
| `/=`     | Divide and assign       | `x /= 2 → x = x / 2`   |
| `//=`    | Floor divide and assign | `x //= 3 → x = x // 3` |
| `%=`     | Modulus and assign      | `x %= 2 → x = x % 2`   |
| `**=`    | Exponent and assign     | `x **= 3 → x = x ** 3` |

```python
x = 5
x += 3
print(x)  # 8
```

---

### 16.4 Logical Operators

* Used to **combine Boolean values** or expressions.

| Operator | Description                        | Example                  |
| -------- | ---------------------------------- | ------------------------ |
| `and`    | True if **both** operands are True | `True and False → False` |
| `or`     | True if **at least one** is True   | `True or False → True`   |
| `not`    | Inverts the Boolean value          | `not True → False`       |

```python
a = True
b = False
print(a and b)  # False
print(a or b)   # True
print(not a)    # False
```

* **Operator Precedence (highest → lowest):**

  1. `()` → Parentheses
  2. Arithmetic (`**, *, /, //, %, +, -`)
  3. Comparison (`==, !=, >, <, >=, <=`)
  4. Logical `not`
  5. Logical `and`
  6. Logical `or`

```python
result = True or False and not False
# Step 1: not False → True
# Step 2: False and True → False
# Step 3: True or False → True
print(result)  # True
```


---

## 17. String (`str`)

* **Definition:** A **string** is a **sequence of characters** enclosed in **single quotes `' '`** or **double quotes `" "`**.
* **Usage:** Used to store text, messages, names, or any sequence of characters.

```python
name = "Ram"
greeting = 'Hello'
message = "Python is fun!"
```

---

### 17.1 String Creation

* Using **single quotes** `'...'`
* Using **double quotes** `"..."`
* Using **triple quotes** `'''...'''` or `"""..."""` for **multiline strings**

```python
single = 'Hello'
double = "World"
multiline = """This is
a multiline
string"""
print(multiline)
```

---

### 17.2 String Indexing

* Each character in a string has an **index** starting from **0**.
* **Negative indexing** starts from the **end** of the string (`-1` = last character).

```python
text = "Python"
print(text[0])   # P
print(text[3])   # h
print(text[-1])  # n
print(text[-2])  # o
```

---

### 17.3 String Slicing

* Extract a **substring** using **start\:stop\:step** format:

```python
text = "Python"
print(text[0:4])    # Pyth (index 0 to 3)
print(text[2:])     # thon (from index 2 to end)
print(text[:4])     # Pyth (from start to index 3)
print(text[::2])    # Pto (every 2nd character)
print(text[::-1])   # nohtyP (reversed string)
```

---

### 17.4 String Operations

1. **Concatenation (`+`)** → Combine strings

```python
first = "Hello"
second = "World"
result = first + " " + second
print(result)  # Hello World
```

2. **Repetition (`*`)** → Repeat strings

```python
text = "Ha"
print(text * 3)  # HaHaHa
```

3. **Length (`len()`)** → Number of characters

```python
text = "Python"
print(len(text))  # 6
```

---

### 17.5 String Methods

| Method             | Description                                           | Example                                               |
| ------------------ | ----------------------------------------------------- | ----------------------------------------------------- |
| `upper()`          | Converts string to uppercase                          | `"hi".upper() → "HI"`                                 |
| `lower()`          | Converts string to lowercase                          | `"HELLO".lower() → "hello"`                           |
| `capitalize()`     | Capitalizes first character                           | `"hello".capitalize() → "Hello"`                      |
| `title()`          | Capitalizes first letter of each word                 | `"python programming".title() → "Python Programming"` |
| `strip()`          | Removes leading and trailing spaces                   | `"  hi  ".strip() → "hi"`                             |
| `replace(old,new)` | Replaces substring                                    | `"Hello".replace("H","J") → "Jello"`                  |
| `split(separator)` | Splits string into list                               | `"a,b,c".split(",") → ["a","b","c"]`                  |
| `join(iterable)`   | Joins iterable elements into string                   | `"-".join(["a","b","c"]) → "a-b-c"`                   |
| `find(sub)`        | Returns first index of substring (or -1 if not found) | `"Python".find("t") → 3`                              |

```python
text = " Python "
print(text.strip())           # "Python"
print("Hello World".upper())  # "HELLO WORLD"
print("a,b,c".split(","))     # ['a', 'b', 'c']
```

---

### 17.6 String Formatting

1. **Using f-strings (Python 3.6+)**

```python
name = "Ram"
age = 30
print(f"My name is {name} and I am {age} years old.")
```

2. **Using `format()` method**

```python
print("My name is {} and I am {} years old.".format(name, age))
```


---

### 17.7 String Escape Characters

* Special characters to represent certain actions:

| Escape | Meaning      |
| ------ | ------------ |
| `\n`   | Newline      |
| `\t`   | Tab          |
| `\\`   | Backslash    |
| `\'`   | Single quote |
| `\"`   | Double quote |

```python
print("Hello\nWorld")
print("Hello\tWorld")
print("I am 5\'10\" tall")
```

---

### 17.8 String Immutability

* Strings are **immutable**, meaning you **cannot change a character** directly.
* To modify a string, create a **new string**.

```python
text = "Python"
# text[0] = "J"  # ❌ Error
new_text = "J" + text[1:]
print(new_text)   # Jython
```

---

### 17.9 Summary

* Strings are sequences of characters.
* Support **indexing, slicing, concatenation, repetition**.
* Provide **many built-in methods** for manipulation.
* Are **immutable**, but can be transformed to create new strings.


---

## 18. Type Casting in Python

**Type casting** is the process of **converting a value from one data type to another**. Python provides built-in functions to perform type casting.

---

### 18.1 Casting to Integer (`int`)

* Converts a value to **integer**.
* **Note:** Floats are truncated (decimal part removed). Boolean `True` → `1`, `False` → `0`. Strings must represent a number.

```python
# From float to int
num = 12.75
print(int(num))  # 12

# From boolean to int
b1 = True
b2 = False
print(int(b1))   # 1
print(int(b2))   # 0

# From string to int
s = "50"
print(int(s))    # 50
```

* **Invalid conversion**:

```python
s = "hello"
# int(s)  # ❌ Error, cannot convert non-numeric string
```

---

### 18.2 Casting to Float (`float`)

* Converts a value to **float**.
* Boolean `True` → `1.0`, `False` → `0.0`. Strings must represent a number.

```python
# From int to float
x = 10
print(float(x))   # 10.0

# From boolean to float
b = True
print(float(b))   # 1.0

# From string to float
s = "12.5"
print(float(s))   # 12.5
```

* **Invalid conversion**:

```python
s = "Python"
# float(s)  # ❌ Error
```

---

### 18.3 Casting to Boolean (`bool`)

* Converts a value to **Boolean** (`True` or `False`).
* **Falsy values → False:** `0, 0.0, "", [], {}, (), None`
* **All other values → True**

```python
# From int to bool
print(bool(0))    # False
print(bool(10))   # True

# From float to bool
print(bool(0.0))  # False
print(bool(3.14)) # True

# From string to bool
print(bool(""))    # False
print(bool("Hi"))  # True
```

---

### 18.4 Casting to String (`str`)

* Converts a value to **string**.
* Can convert int, float, boolean, or other types to a string.

```python
# From int to string
x = 100
print(str(x))     # "100"

# From float to string
f = 12.34
print(str(f))     # "12.34"

# From boolean to string
b = True
print(str(b))     # "True"
```

---

### 18.5 Summary of Type Casting

| From Type | To Type | Example         | Result     |
| --------- | ------- | --------------- | ---------- |
| float     | int     | `int(12.7)`     | 12         |
| boolean   | int     | `int(True)`     | 1          |
| string    | int     | `int("50")`     | 50         |
| int       | float   | `float(10)`     | 10.0       |
| string    | float   | `float("12.5")` | 12.5       |
| int/float | string  | `str(100)`      | "100"      |
| any       | bool    | `bool(value)`   | True/False |

* **Important Notes:**

  1. Strings must represent a valid number to convert to `int` or `float`.
  2. Boolean conversion follows Python’s **truthy/falsy rules**.


---

## 19. Non-Primitive Data Types in Python

**Non-primitive data types** are used to store **multiple values** or **collections of values**.

* Unlike primitive data types, they can hold **more than one item**.
* Common non-primitive data types: **List, Tuple, Set, Dictionary**.
* Here, we focus on **List** and **Tuple**.

---

### 19.1 List (`list`)

* **Definition:** A **list** is an **ordered, mutable collection** of items.
* **Usage:** Store multiple values of any type in a single variable.
* **Syntax:** `[item1, item2, item3, ...]`

#### Creating a List

```python
# Non-empty list
fruits = ["apple", "banana", "cherry"]

# Empty list
empty_list = []
print(empty_list)  # []
```

#### Features:

1. **Ordered:** Elements maintain their position.
2. **Mutable:** Can **change, add, or remove items**.
3. **Indexing & Slicing:** Access items by index.

```python
fruits = ["apple", "banana", "cherry"]
print(fruits[0])   # apple
print(fruits[-1])  # cherry
print(fruits[0:2]) # ['apple', 'banana']
```

---

#### 19.1.1 Add Values to a List

| Method                | Description                              | Example                             |
| --------------------- | ---------------------------------------- | ----------------------------------- |
| `append(item)`        | Add an item at the end                   | `fruits.append("orange")`           |
| `insert(index, item)` | Insert item at specific index            | `fruits.insert(1, "mango")`         |
| `extend(iterable)`    | Add multiple items from another iterable | `fruits.extend(["kiwi", "grapes"])` |

```python
fruits = ["apple", "banana"]
fruits.append("orange")            # ["apple", "banana", "orange"]
fruits.insert(1, "mango")          # ["apple", "mango", "banana", "orange"]
fruits.extend(["kiwi", "grapes"])  # ["apple", "mango", "banana", "orange", "kiwi", "grapes"]
```

---

#### 19.1.2 Remove Values from a List

| Method         | Description                         | Example                           |
| -------------- | ----------------------------------- | --------------------------------- |
| `remove(item)` | Remove first occurrence of item     | `fruits.remove("banana")`         |
| `pop(index)`   | Remove item at index (default last) | `fruits.pop()` or `fruits.pop(1)` |
| `clear()`      | Remove all items from the list      | `fruits.clear()`                  |

```python
fruits = ["apple", "banana", "cherry"]
fruits.remove("banana")  # ["apple", "cherry"]
fruits.pop()             # ["apple", "cherry"] → removes last item
fruits.clear()           # []
```

---

#### 19.1.3 Other Useful List Methods

| Method        | Description                       | Example                  |
| ------------- | --------------------------------- | ------------------------ |
| `len(list)`   | Returns number of elements        | `len(fruits)`            |
| `sort()`      | Sorts the list in ascending order | `numbers.sort()`         |
| `reverse()`   | Reverses the list                 | `numbers.reverse()`      |
| `count(item)` | Count occurrences of an item      | `fruits.count("apple")`  |
| `index(item)` | Returns first index of item       | `fruits.index("cherry")` |

```python
fruits = ["apple", "banana", "apple"]
print(len(fruits))            # 3
print(fruits.count("apple"))  # 2
print(fruits.index("banana")) # 1
```

---

### 19.2 Tuple (`tuple`)

* **Definition:** A **tuple** is an **ordered, immutable collection** of items.
* **Usage:** Store multiple values that **should not change**.
* **Syntax:** `(item1, item2, item3, ...)`

#### Creating a Tuple

```python
# Non-empty tuple
numbers = (1, 2, 3, 4)
fruits = ("apple", "banana", "cherry")

# Empty tuple
empty_tuple = ()
print(empty_tuple)  # ()
```

#### Features:

1. **Ordered:** Elements maintain their position.
2. **Immutable:** Cannot **change, add, or remove items** after creation.
3. **Indexing & Slicing:** Access items by index.

```python
fruits = ("apple", "banana", "cherry")
print(fruits[0])   # apple
print(fruits[-1])  # cherry
print(fruits[0:2]) # ('apple', 'banana')
```

#### Tuple Operations:

* You **cannot** modify items.
* You can **concatenate** or **repeat** tuples:

```python
t1 = (1, 2)
t2 = (3, 4)
t3 = t1 + t2     # (1, 2, 3, 4)
t4 = t1 * 3      # (1, 2, 1, 2, 1, 2)
print(t3)
print(t4)
```

#### Tuple Methods:

| Method        | Description                  | Example            |
| ------------- | ---------------------------- | ------------------ |
| `len(tuple)`  | Returns number of elements   | `len(numbers)`     |
| `count(item)` | Count occurrences of an item | `numbers.count(2)` |
| `index(item)` | Returns first index of item  | `numbers.index(3)` |

---

### 19.3 Summary

| Feature  | List (`list`)    | Tuple (`tuple`)  |
| -------- | ---------------- | ---------------- |
| Ordered  | ✅ Yes            | ✅ Yes            |
| Mutable  | ✅ Can change     | ❌ Cannot change  |
| Syntax   | `[item1, item2]` | `(item1, item2)` |
| Empty    | `[]`             | `()`             |
| Use Case | Changing data    | Fixed data       |

* **Lists** are used when **data can change**.
* **Tuples** are used when **data should remain constant**.


---

## 20. Set and Dictionary in Python

### 20.1 Set (`set`)

* **Definition:** A **set** is an **unordered collection of unique items**.
* **Usage:** Store multiple values without duplicates.
* **Syntax:** `{item1, item2, item3, ...}`

#### Creating a Set

```python
# Non-empty set
fruits = {"apple", "banana", "cherry"}

# Empty set
empty_set = set()
print(empty_set)  # set()
```

#### Features:

1. **Unordered:** No index, elements may appear in any order.
2. **Unique:** Duplicate values are automatically removed.
3. **Mutable:** Items can be added or removed.

```python
fruits = {"apple", "banana", "cherry", "apple"}  
print(fruits)  # {'apple', 'banana', 'cherry'} → duplicates removed
```

#### 20.1.1 Add Values

| Method             | Description        | Example                             |
| ------------------ | ------------------ | ----------------------------------- |
| `add(item)`        | Add single item    | `fruits.add("orange")`              |
| `update(iterable)` | Add multiple items | `fruits.update(["kiwi", "grapes"])` |

#### 20.1.2 Remove Values

| Method          | Description                        | Example                   |
| --------------- | ---------------------------------- | ------------------------- |
| `remove(item)`  | Remove item, error if not found    | `fruits.remove("banana")` |
| `discard(item)` | Remove item, no error if not found | `fruits.discard("mango")` |
| `pop()`         | Remove arbitrary item              | `fruits.pop()`            |
| `clear()`       | Remove all items                   | `fruits.clear()`          |

#### 20.1.3 Other Useful Operations

```python
# Set operations
A = {1, 2, 3}
B = {3, 4, 5}
print(A.union(B))        # {1, 2, 3, 4, 5}
print(A.intersection(B)) # {3}
print(A.difference(B))   # {1, 2}
print(A.symmetric_difference(B)) # {1, 2, 4, 5}
```

---

### 20.2 Dictionary (`dict`)

* **Definition:** A **dictionary** is an **unordered collection of key-value pairs**.
* **Usage:** Store data where each value is accessed by a **unique key**.
* **Syntax:** `{key1: value1, key2: value2, ...}`

#### Creating a Dictionary

```python
# Non-empty dictionary
person = {"name": "Ram", "age": 30, "city": "Kathmandu"}

# Empty dictionary
empty_dict = {}
print(empty_dict)  # {}
```

#### Features:

1. **Unordered:** Items have no fixed order (Python 3.7+ maintains insertion order).
2. **Keys Unique:** Keys must be unique, values can repeat.
3. **Mutable:** Keys and values can be changed.

#### 20.2.1 Accessing Values

```python
print(person["name"])       # Ram
print(person.get("age"))    # 30
```

#### 20.2.2 Add/Update Values

```python
person["profession"] = "Engineer"   # Add new key-value
person["age"] = 31                  # Update value
```

#### 20.2.3 Remove Values

| Method      | Description               | Example              |
| ----------- | ------------------------- | -------------------- |
| `pop(key)`  | Remove key-value by key   | `person.pop("city")` |
| `popitem()` | Remove last inserted item | `person.popitem()`   |
| `clear()`   | Remove all items          | `person.clear()`     |

#### 20.2.4 Other Useful Methods

| Method     | Description             | Example           |
| ---------- | ----------------------- | ----------------- |
| `keys()`   | Get all keys            | `person.keys()`   |
| `values()` | Get all values          | `person.values()` |
| `items()`  | Get all key-value pairs | `person.items()`  |

```python
print(person.keys())    # dict_keys(['name', 'age', 'profession'])
print(person.values())  # dict_values(['Ram', 31, 'Engineer'])
print(person.items())   # dict_items([('name', 'Ram'), ('age', 31), ('profession', 'Engineer')])
```

---

### 20.3 Summary

| Feature      | Set (`set`)       | Dictionary (`dict`)                |
| ------------ | ----------------- | ---------------------------------- |
| Ordered      | ❌ Unordered       | ❌ Unordered (3.7+ insertion order) |
| Mutable      | ✅ Can change      | ✅ Can change                       |
| Unique Items | ✅ Yes             | Keys ✅, Values ❌                   |
| Indexing     | ❌ No              | ❌ No, use keys                     |
| Empty        | `set()`           | `{}`                               |
| Use Case     | Unique collection | Key-value mapping                  |

* **Sets** are used for **unique items and mathematical operations**.
* **Dictionaries** are used for **storing data with key-value relationships**.


---

## 21. Conditional Statements in Python

Conditional statements allow your program to **execute different code blocks based on certain conditions**.
They are used to make **decisions** in Python.

---

### 21.1 `if` Statement

* Executes a block of code **only if a condition is True**.
* **Syntax:**

```python
if condition:
    # code to execute if condition is True
```

**Example:**

```python
age = 18
if age >= 18:
    print("You are eligible to vote")
```

*Output:*

```
You are eligible to vote
```

---

### 21.2 `if-else` Statement

* Executes **one block if the condition is True**, and **another block if False**.
* **Syntax:**

```python
if condition:
    # code if True
else:
    # code if False
```

**Example:**

```python
age = 16
if age >= 18:
    print("Eligible to vote")
else:
    print("Not eligible to vote")
```

*Output:*

```
Not eligible to vote
```

---

### 21.3 `if-elif-else` Statement

* Used when there are **multiple conditions**.
* **Syntax:**

```python
if condition1:
    # code if condition1 is True
elif condition2:
    # code if condition2 is True
else:
    # code if all conditions are False
```

**Example:**

```python
marks = 85
if marks >= 90:
    print("Grade A")
elif marks >= 75:
    print("Grade B")
elif marks >= 50:
    print("Grade C")
else:
    print("Fail")
```

*Output:*

```
Grade B
```

---

### 21.4 Nested `if` Statements

* An `if` or `if-else` statement **inside another `if` statement**.
* Used for **more complex decisions**.

**Example:**

```python
age = 20
citizenship = "Nepal"
if age >= 18:
    if citizenship == "Nepal":
        print("Eligible to vote")
    else:
        print("Not eligible due to citizenship")
else:
    print("Not eligible due to age")
```

*Output:*

```
Eligible to vote
```

---

### 21.5 Short-Hand (Ternary) `if`

* A **compact way** to write `if-else` in a single line.
* **Syntax:**

```python
value_if_true if condition else value_if_false
```

**Example:**

```python
age = 18
status = "Eligible" if age >= 18 else "Not Eligible"
print(status)
```

*Output:*

```
Eligible
```

---

### 21.6 `pass` Statement

* `pass` is a **placeholder statement** that does **nothing**.
* Used when a statement is syntactically required but you don’t want any action.

**Example:**

```python
age = 18
if age >= 18:
    pass   # Will do nothing for now
else:
    print("Not eligible")
```

*Output:* *(No output because `pass` does nothing)*

* Commonly used in:

  * Empty functions
  * Empty loops
  * Empty condition blocks

```python
def my_function():
    pass   # Function not implemented yet
```

---

### 21.7 Comparison Operators Used in Conditionals

* `==` → equal
* `!=` → not equal
* `>`  → greater than
* `<`  → less than
* `>=` → greater or equal
* `<=` → less or equal

---

### 21.8 Logical Operators in Conditionals

* **`and`** → True if **both conditions** are True
* **`or`** → True if **at least one condition** is True
* **`not`** → Inverts the condition

**Example:**

```python
age = 20
citizenship = "Nepal"

if age >= 18 and citizenship == "Nepal":
    print("Eligible to vote")
```

---

### 21.9 Summary

| Statement Type | Use Case                                   |
| -------------- | ------------------------------------------ |
| `if`           | Execute code if a single condition is True |
| `if-else`      | Choose between two paths                   |
| `if-elif-else` | Multiple conditions                        |
| Nested `if`    | Complex conditions with sub-conditions     |
| Ternary `if`   | Short-hand `if-else` in one line           |
| `pass`         | Placeholder for empty blocks               |

* Conditional statements allow your program to **make decisions** and **control flow**.
* Logical and comparison operators are used **inside the condition** to evaluate True/False.


---

## 22. Loops in Python

Loops are used to **execute a block of code repeatedly** until a certain condition is met.
They help **automate repetitive tasks** and **reduce code duplication**.

---

### 22.1 Introduction to Loops

* Loops **repeat code** multiple times based on a **condition** or **sequence**.
* Two main types in Python:

  1. **`for` loop** – iterates over a sequence (like list, tuple, string).
  2. **`while` loop** – executes as long as a condition is True.
* Optional **loop control statements**:

  * `break` → exit the loop immediately
  * `continue` → skip the current iteration and continue
  * `pass` → placeholder that does nothing

---

### 22.2 `for` Loop

* Used to **iterate over a sequence** (list, tuple, string, range, etc.).
* **Syntax:**

```python
for variable in sequence:
    # code to execute
```

**Example 1 – Iterating over a list:**

```python
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)
```

*Output:*

```
apple
banana
cherry
```

**Example 2 – Using `range()`:**

```python
for i in range(5):  # 0 to 4
    print(i)
```

*Output:*

```
0
1
2
3
4
```

**Example 3 – Iterating over a string:**

```python
for letter in "Python":
    print(letter)
```

---

### 22.3 `while` Loop

* Repeats a block of code **as long as a condition is True**.
* **Syntax:**

```python
while condition:
    # code to execute
```

**Example:**

```python
count = 0
while count < 5:
    print(count)
    count += 1
```

*Output:*

```
0
1
2
3
4
```

---

### 22.4 Nested Loops

* **Loop inside another loop**.
* Can be used with `for` or `while`.

**Example – Nested `for` loop:**

```python
for i in range(1, 4):
    for j in range(1, 4):
        print(i, j)
```

*Output:*

```
1 1
1 2
1 3
2 1
2 2
2 3
3 1
3 2
3 3
```

---

### 22.5 Loop Control Statements

#### 22.5.1 `break`

* **Exits the loop immediately**, even if the condition is True.

```python
for i in range(5):
    if i == 3:
        break
    print(i)
```

*Output:*

```
0
1
2
```

#### 22.5.2 `continue`

* **Skips the current iteration** and moves to the next iteration.

```python
for i in range(5):
    if i == 3:
        continue
    print(i)
```

*Output:*

```
0
1
2
4
```

#### 22.5.3 `pass`

* **Placeholder statement**; does nothing.
* Useful in **empty loops**.

```python
for i in range(5):
    if i < 3:
        pass
    else:
        print(i)
```

*Output:*

```
3
4
```

---

### 22.6 `else` with Loops

* Python allows an optional **`else` block** with loops.
* Executes **only if the loop completes normally** (no `break`).

**Example:**

```python
for i in range(3):
    print(i)
else:
    print("Loop finished without break")
```

*Output:*

```
0
1
2
Loop finished without break
```

**Example with `break`:**

```python
for i in range(3):
    if i == 1:
        break
    print(i)
else:
    print("Loop finished without break")
```

*Output:*

```
0
```

*(The `else` block is skipped because of `break`.)*

---

### 22.7 Summary

| Loop Type        | Use Case                           | Key Points                            |
| ---------------- | ---------------------------------- | ------------------------------------- |
| `for`            | Iterate over sequence              | Works with list, tuple, string, range |
| `while`          | Repeat until condition is False    | Condition-controlled loop             |
| Nested Loops     | Loop inside loop                   | Can combine `for` and `while`         |
| `break`          | Exit loop immediately              | Stops the loop                        |
| `continue`       | Skip current iteration             | Moves to next iteration               |
| `pass`           | Placeholder                        | Does nothing, useful in empty blocks  |
| `else` with loop | Executes if loop finishes normally | Skipped if `break` occurs             |

* Loops are essential for **automation, repetition, and complex logic** in Python.

---


## 23. Functions in Python

A **function** is a **block of reusable code** that performs a **specific task**.
Functions help to:

* Avoid code repetition
* Organize code into **logical blocks**
* Make code **readable and maintainable**

---

### 23.1 Introduction to Functions

* Functions can **take inputs (parameters)** and **return outputs (values)**.
* **Syntax of a function:**

```python
def function_name(parameters):
    """
    Optional docstring to describe the function
    """
    # code block
    return value   # optional
```

**Example – Simple Function:**

```python
def greet():
    print("Hello, World!")

greet()  # Call the function
```

*Output:*

```
Hello, World!
```

---

### 23.2 Function Parameters (Arguments)

Functions can accept **data from the caller** using parameters.

#### 23.2.1 Positional Arguments

* Values are assigned **based on the order** of parameters.

```python
def greet(name, age):
    print(f"Hello {name}, you are {age} years old")

greet("Ram", 30)  # name="Ram", age=30
```

---

#### 23.2.2 Keyword Arguments

* Values are assigned **using parameter names**, order does not matter.

```python
greet(age=30, name="Ram")
```

*Output:*

```
Hello Ram, you are 30 years old
```

---

#### 23.2.3 Default Arguments

* Assign **default values** to parameters.
* Used when the caller **does not provide a value**.

```python
def greet(name="Guest"):
    print(f"Hello {name}")

greet()        # Uses default value
greet("Ram")   # Overrides default value
```

*Output:*

```
Hello Guest
Hello Ram
```

---

#### 23.2.4 Variable-Length Arguments

* Sometimes we do not know **how many arguments will be passed**.
* Python provides:

  * **`*args`** → accepts multiple **positional arguments** as a tuple
  * **`**kwargs`** → accepts multiple **keyword arguments** as a dictionary

**Example – `*args`:**

```python
def add_numbers(*args):
    return sum(args)

print(add_numbers(1, 2, 3, 4))  # 10
```

**Example – `**kwargs`:**

```python
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Ram", age=30, city="Kathmandu")
```

*Output:*

```
name: Ram
age: 30
city: Kathmandu
```

---

### 23.3 Return Statement

* Functions can **return values** to the caller using `return`.
* If no return is used, function returns **`None`**.

```python
def add(a, b):
    return a + b

result = add(5, 3)
print(result)  # 8
```

---

### 23.4 Anonymous Functions (`lambda`)

* Small **single-expression functions**.
* Syntax: `lambda parameters: expression`

```python
square = lambda x: x**2
print(square(5))  # 25
```

* Often used with `map()`, `filter()`, and `reduce()`.

---

### 23.5 Scope of Variables

* **Local variables:** Defined inside a function; accessible **only inside**.
* **Global variables:** Defined outside; accessible **anywhere**.

```python
x = 10  # global

def func():
    y = 5  # local
    print(x, y)

func()
print(x)  # 10
# print(y) -> Error, y is local
```

---

### 23.6 Summary

| Concept                   | Description                                    |
| ------------------------- | ---------------------------------------------- |
| Function Definition       | `def function_name(parameters):`               |
| Positional Arguments      | Assigned based on order                        |
| Keyword Arguments         | Assigned using parameter names                 |
| Default Arguments         | Parameters with default values                 |
| Variable-Length Arguments | `*args` for positional, `**kwargs` for keyword |
| Return Statement          | Sends value back to caller                     |
| Anonymous Function        | `lambda` for single-expression functions       |
| Scope                     | Local vs Global variables                      |

* Functions **improve code reusability, readability, and maintainability**.


---

## 24. Exception Handling in Python (`try-except`)

In Python, **errors** that occur during program execution are called **exceptions**.
Without handling, exceptions **stop the program abruptly**.
Exception handling allows the program to **continue running safely**.

---

### 24.1 Types of Errors in Python

1. **Syntax Error**

   * Occurs when Python **cannot understand the code** due to incorrect syntax.
   * Example:

   ```python
   if True
       print("Hello")
   ```

   *Output:*

   ```
   SyntaxError: expected ':'
   ```

   * **Note:** Syntax errors are **detected before program execution**.

2. **Logical Error**

   * Occurs when the program runs **without crashing**, but the **output is incorrect**.
   * Example:

   ```python
   x = 10
   y = 5
   result = x - y  # Intended to add
   print(result)
   ```

   *Output:*

   ```
   5
   ```

   * **Note:** Logical errors **do not raise exceptions**; careful debugging is needed.

3. **Runtime Error (Exceptions)**

   * Occurs **during program execution** when Python encounters an unexpected situation.
   * Example:

   ```python
   x = 10 / 0
   ```

   *Output:*

   ```
   ZeroDivisionError: division by zero
   ```

---

### 24.2 Introduction to `try-except`

* **Exceptions** are handled using `try` and `except` blocks.
* Allows **graceful error handling** without crashing the program.

**Basic Syntax:**

```python
try:
    # Code that may raise an exception
except:
    # Code to handle the exception
```

**Example:**

```python
try:
    x = 10 / 0
except:
    print("Error occurred: Division by zero")
```

---

### 24.3 Catching Specific Exceptions

```python
try:
    x = int("abc")
except ValueError:
    print("Invalid input!")
```

* You can catch **multiple exception types**:

```python
try:
    num = int(input("Enter a number: "))
    result = 10 / num
except (ValueError, ZeroDivisionError) as e:
    print("An error occurred:", e)
```

---

### 24.4 Catching Multiple Exceptions in One Line

* Python allows **grouping multiple exception types** in one `except` block.

```python
try:
    num = int(input("Enter a number: "))
    result = 10 / num
except (ValueError, ZeroDivisionError) as e:
    print("Error:", e)
```

---

### 24.5 `else` Block

* Executes **only if no exception occurs**.

```python
try:
    result = 10 / 2
except ZeroDivisionError:
    print("Cannot divide by zero")
else:
    print("Result is:", result)
```

---

### 24.6 `finally` Block

* Executes **always**, even if an exception occurs.
* Commonly used to **release resources**.

```python
try:
    f = open("data.txt")
    content = f.read()
except FileNotFoundError:
    print("File not found")
finally:
    print("This always executes")
    try:
        f.close()
    except:
        pass
```

---

### 24.7 Raising Exceptions (`raise`)

* You can **manually raise exceptions** using `raise`.

```python
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

try:
    result = divide(10, 0)
except ValueError as e:
    print("Error:", e)
```

---

### 24.8 Error Chaining (`from` keyword)

* Python allows **exception chaining**, where one exception is **raised from another**.
* Useful for debugging and understanding the **root cause of exceptions**.

```python
try:
    x = int("abc")
except ValueError as e:
    raise RuntimeError("Failed to convert input") from e
```

*Output:*

```
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ValueError: invalid literal for int() with base 10: 'abc'

The above exception was the direct cause of the following exception:

RuntimeError: Failed to convert input
```

---

### 24.9 Common Built-in Exceptions

| Exception Type      | Description                     |
| ------------------- | ------------------------------- |
| `ZeroDivisionError` | Division by zero                |
| `ValueError`        | Invalid value conversion        |
| `TypeError`         | Wrong type operation            |
| `IndexError`        | Invalid index                   |
| `KeyError`          | Invalid dictionary key          |
| `FileNotFoundError` | File not found                  |
| `ImportError`       | Module not found                |
| `AttributeError`    | Invalid object attribute access |

---

### 24.10 Best Practices

1. Catch **specific exceptions** instead of generic `except:`.
2. Use `finally` to **release resources**.
3. Avoid using exceptions for **normal flow control**.
4. Use `as` to **inspect the exception object**.
5. Use **error chaining** (`from`) to track the original cause of exceptions.
6. Always **debug logical errors** carefully; they don’t raise exceptions.

---

### 24.11 Full Example with `else` and `finally`

```python
try:
    num = int(input("Enter a number: "))
    result = 10 / num
except ValueError:
    print("Please enter a valid number")
except ZeroDivisionError:
    print("Cannot divide by zero")
else:
    print("Result is:", result)
finally:
    print("Execution completed")
```

*Sample Input:* `0`
*Output:*

```
Cannot divide by zero
Execution completed
```

*Sample Input:* `5`
*Output:*

```
Result is: 2.0
Execution completed
```

---

### 24.12 Summary

| Keyword/Concept | Purpose                                     |
| --------------- | ------------------------------------------- |
| `try`           | Wrap code that may throw exceptions         |
| `except`        | Handle exceptions                           |
| `else`          | Executes if no exception occurs             |
| `finally`       | Executes always, used for cleanup           |
| `raise`         | Manually raise an exception                 |
| `from`          | Chain exceptions to show the original cause |
| Syntax Error    | Code violates Python syntax                 |
| Logical Error   | Code runs but produces wrong output         |
| Runtime Error   | Error occurs during program execution       |

* Python errors can be **syntax, logical, or runtime**.
* `try-except` ensures **program continues execution** even in case of runtime errors.
* Error chaining helps in **debugging complex programs**.


---

## 25. File Handling in Python

File handling allows Python programs to **store data permanently** and **read/write external files**.
Files can be **text files** or **binary files**.

---

### 25.1 Introduction

* Files are used to **store data outside the program**.
* Python provides built-in functions to **open, read, write, and close files**.
* **Basic workflow:**

  1. Open a file
  2. Perform operations (read/write)
  3. Close the file

---

### 25.2 Opening a File

* **`open()`** is used to open a file.
* **Syntax:**

```python
file_object = open(filename, mode)
```

**Modes:**

| Mode   | Description                                      |
| ------ | ------------------------------------------------ |
| `'r'`  | Read (default). File must exist                  |
| `'w'`  | Write. Creates a new file or overwrites existing |
| `'a'`  | Append. Adds content to end of file              |
| `'x'`  | Create a new file. Fails if file exists          |
| `'rb'` | Read binary                                      |
| `'wb'` | Write binary                                     |
| `'ab'` | Append binary                                    |
| `'r+'` | Read and write                                   |
| `'w+'` | Write and read (overwrite)                       |
| `'a+'` | Append and read                                  |

**Example:**

```python
f = open("example.txt", "r")  # open in read mode
```

---

### 25.3 Closing a File

* **`close()`** releases the file resources.

```python
f = open("example.txt", "r")
f.close()
```

* **Best practice:** Use **context manager** to automatically close files.

---

### 25.4 Context Manager (`with` keyword)

* Automatically **opens and closes** files, even if errors occur.

```python
with open("example.txt", "r") as f:
    content = f.read()
    print(content)
# No need to call f.close()
```

---

### 25.5 Reading Files

* **`read()`** → Reads the whole file as a string
* **`read(size)`** → Reads `size` number of characters
* **`readline()`** → Reads one line at a time
* **`readlines()`** → Reads all lines as a list

**Example:**

```python
with open("example.txt", "r") as f:
    content = f.read()
    print(content)
```

**Example – Reading line by line:**

```python
with open("example.txt", "r") as f:
    for line in f:
        print(line.strip())
```

---

### 25.6 Writing Files

* **`write()`** → Writes a string to the file
* **`writelines()`** → Writes a list of strings

**Example – Writing a file:**

```python
with open("example.txt", "w") as f:
    f.write("Hello, World!\n")
    f.write("Welcome to Python file handling.\n")
```

**Example – Appending:**

```python
with open("example.txt", "a") as f:
    f.write("Appending new line.\n")
```

---

### 25.7 File Pointer & `seek()`

* The **file pointer** indicates the current position in the file.
* **`seek(offset, whence)`** moves the pointer.

  * `offset` → number of bytes to move
  * `whence` → reference position (0=start, 1=current, 2=end)

```python
with open("example.txt", "r") as f:
    print(f.read(5))  # reads first 5 chars
    f.seek(0)         # move pointer to beginning
    print(f.read(5))
```

---

### 25.8 Deleting a File

* Use **`os` module**:

```python
import os
os.remove("example.txt")  # Deletes file
```

---

### 25.9 Exception Handling in File Operations

* Use `try-except` to **handle file-related errors**.

```python
try:
    with open("missing.txt", "r") as f:
        content = f.read()
except FileNotFoundError:
    print("File not found!")
except IOError:
    print("Error reading/writing the file")
finally:
    print("File operation attempted")
```

---

### 25.10 Common File Handling Keywords

| Keyword      | Purpose                                     |
| ------------ | ------------------------------------------- |
| `open`       | Opens a file                                |
| `close`      | Closes a file                               |
| `with`       | Context manager for automatic file handling |
| `read`       | Reads content from file                     |
| `readline`   | Reads one line at a time                    |
| `readlines`  | Reads all lines into a list                 |
| `write`      | Writes string to file                       |
| `writelines` | Writes list of strings to file              |
| `seek`       | Moves file pointer                          |
| `os.remove`  | Deletes a file                              |

---

### 25.11 File Handling Examples

**Example 1 – Read & Print File**

```python
with open("data.txt", "r") as file:
    print(file.read())
```

**Example 2 – Write & Append**

```python
with open("data.txt", "w") as file:
    file.write("Line 1\n")
with open("data.txt", "a") as file:
    file.write("Line 2\n")
```

**Example 3 – Read lines as list**

```python
with open("data.txt", "r") as file:
    lines = file.readlines()
    for line in lines:
        print(line.strip())
```

**Example 4 – Handle missing file**

```python
try:
    with open("missing.txt", "r") as f:
        print(f.read())
except FileNotFoundError:
    print("File not found!")
```

---

### 25.12 Summary

| Concept                               | Purpose                                   |
| ------------------------------------- | ----------------------------------------- |
| `open(filename, mode)`                | Open a file                               |
| `close()`                             | Close the file                            |
| `with`                                | Automatically manage file opening/closing |
| File modes (`r`, `w`, `a`)            | Read, write, append                       |
| `read()`, `readline()`, `readlines()` | Read content                              |
| `write()`, `writelines()`             | Write content                             |
| `seek()`                              | Move file pointer                         |
| `os.remove()`                         | Delete file                               |
| Exception handling                    | Gracefully handle file errors             |

* File handling is essential for **data storage, reading logs, and working with external files**.
* Using **context managers and exception handling** ensures **safe and clean file operations**.


---

## 26. Python Classes, Objects, Variables, Methods, and Four Pillars of OOP

Python is an **object-oriented programming (OOP)** language.
**Classes** and **objects** are the core concepts of OOP in Python, and the **Four Pillars of OOP** ensure **code reusability, modularity, and maintainability**.

---

### 26.1 Introduction

* **Class:** Blueprint or template for creating objects.
* **Object:** Instance of a class.
* Classes help **organize code** and **model real-world entities**.

**Syntax of a class:**

```python
class ClassName:
    # class attributes
    # methods
```

**Creating an object:**

```python
object_name = ClassName()
```

---

### 26.2 Constructor (`__init__`)

* Special method automatically called when an **object is created**.
* Used to initialize **instance attributes**.

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

p1 = Person("Ram", 30)
print(p1.name, p1.age)
```

---

### 26.3 The `self` Keyword

* Refers to the **current instance** of the class.
* Used to **access instance attributes and methods**.

```python
class Person:
    def __init__(self, name):
        self.name = name
    
    def greet(self):  # instance method
        print(f"Hello, my name is {self.name}")

p1 = Person("Ram")
p1.greet()
```

---

### 26.4 Types of Variables in Classes

1. **Instance Variables** – Unique to each object
2. **Class (Static) Variables** – Shared by all objects
3. **Public Variables** – Accessible anywhere
4. **Private Variables** – Accessible only via getter/setter methods

```python
class Person:
    species = "Human"  # class variable
    
    def __init__(self, name, age):
        self.name = name  # instance/public variable
        self.__age = age  # private variable
    
    # Instance method
    def greet(self):
        print(f"Hello, my name is {self.name} and I am {self.__age}")
    
    # Getter for private variable
    def get_age(self):
        return self.__age
```

---

### 26.5 Methods in Classes

1. **Instance Methods** – Operate on instance variables
2. **Class Methods** – Operate on class variables (`@classmethod`)
3. **Static Methods** – Independent utility methods (`@staticmethod`)

```python
class Math:
    @staticmethod
    def add(a, b):
        return a + b

class Person:
    species = "Human"
    
    @classmethod
    def show_species(cls):
        print(f"Species: {cls.species}")
```

---

### 26.6 Inheritance

* Allows a **class to inherit attributes and methods from another class**.
* **Parent class (superclass)** → Base class
* **Child class (subclass)** → Derived class

```python
class Student(Person):
    def study(self):
        print(f"{self.name} is studying")
```

**Types of Inheritance:**

* Single Inheritance
* Multilevel Inheritance
* Multiple Inheritance

---

### 26.7 Encapsulation

* Hiding internal details using **private variables** (`__`).
* Access using **getter and setter methods**.

---

### 26.8 Polymorphism

* **Same method name, different behavior** via method overriding.
* Includes **operator overloading**.

```python
class Animal:
    def sound(self):
        print("Some sound")
        
class Dog(Animal):
    def sound(self):
        print("Bark")
```

---

### 27. Four Pillars of OOP

| Pillar            | Purpose                            | Example                                  |
| ----------------- | ---------------------------------- | ---------------------------------------- |
| **Encapsulation** | Hide internal data                 | Private variable + getter/setter         |
| **Abstraction**   | Hide implementation                | Abstract class & methods                 |
| **Inheritance**   | Reuse code                         | Child class inherits parent class        |
| **Polymorphism**  | Same interface, different behavior | Method overriding / operator overloading |

---

### 27.1 Abstraction

* **Definition:** Hiding complex implementation and exposing only necessary details.
* Achieved using **abstract classes** (`abc` module).

```python
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height

r = Rectangle(5, 10)
print(r.area())  # 50
```

---

### 26.9 Keywords Related to Classes

| Keyword / Concept | Purpose                                |
| ----------------- | -------------------------------------- |
| `class`           | Define a class                         |
| `__init__`        | Constructor for object initialization  |
| `self`            | Refers to current instance             |
| `@classmethod`    | Define class method                    |
| `@staticmethod`   | Define static method                   |
| `super()`         | Access parent class methods/attributes |
| `_variable`       | Protected attribute (convention)       |
| `__variable`      | Private attribute                      |
| `pass`            | Placeholder for empty class or method  |

---

### 26.10 Complete Example

```python
from abc import ABC, abstractmethod

class Person:
    species = "Human"  # class variable
    
    def __init__(self, name, age):
        self.name = name       # instance variable
        self.__age = age       # private variable
    
    def greet(self):           # instance method
        print(f"Hello, my name is {self.name} and I am {self.__age}")
    
    def get_age(self):         # getter for private variable
        return self.__age
    
    @classmethod
    def show_species(cls):     # class method
        print(f"Species: {cls.species}")
    
    @staticmethod
    def welcome():             # static method
        print("Welcome to Python classes")

class Student(Person):
    def study(self):
        print(f"{self.name} is studying")

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, w, h):
        self.w = w
        self.h = h
    def area(self):
        return self.w * self.h

# Usage
p1 = Person("Ram", 30)
p1.greet()
print("Age via getter:", p1.get_age())
Person.show_species()
Person.welcome()

s1 = Student("Hari", 25)
s1.greet()
s1.study()

r = Rectangle(5, 10)
print("Area:", r.area())
```

---

### 26.11 Summary

| Concept                                          | Description                               |
| ------------------------------------------------ | ----------------------------------------- |
| Class                                            | Blueprint for objects                     |
| Object                                           | Instance of a class                       |
| Constructor (`__init__`)                         | Initializes instance attributes           |
| `self`                                           | Refers to instance                        |
| Instance variable                                | Unique to each object                     |
| Class (static) variable                          | Shared by all objects                     |
| Public variable                                  | Accessible anywhere                       |
| Private variable                                 | Accessible only via getter/setter         |
| Instance method                                  | Operates on instance attributes           |
| Class methods                                    | Operates on class attributes              |
| Static methods                                   | Independent utility methods               |
| Inheritance                                      | Child class inherits parent class         |
| Encapsulation                                    | Hiding data using private attributes      |
| Abstraction                                      | Hiding implementation, exposing interface |
| Polymorphism                                     | Method overriding / operator overloading  |
| Keywords (`class`, `self`, `@classmethod`, etc.) | Define and manage classes                 |

---



![OOP](https://hackmd.io/_uploads/H1W-QuJFgx.png)



