# Python

## Python Notes

```python
name = "Akash Kumar"
print(len(name))  # Outputs the length of the string
print(name[1:5])  # Outputs the substring from index 1 to 4 (excluding index 5)
print(name[1:-1])  # Outputs the substring from index 1 to the second last character
```

## Indexing in Python:

* Index -1 refers to the last character in a string, while -5 refers to the character 'K' in 'Kumar'.
* Positive indexes start from 0, denoting the first character, 1 for the second, and so on.

## Slicing in Python:

* `[1:]`: Prints the entire string from index 1 to the end.
* `[:3]` or `[:]`: Prints the substring from index 0 to 2 (inclusive of indexes 0, 1, 2).

## Arithmetic Operations in Python:

* `/` performs division and returns a floating-point number.
* `//` performs floor division, returning only the integer part of the quotient.

## Augmented Assignment Operator in Python:

* The statement `x = x + 1` is equivalent to `x += 1`.

## Working with Numbers in Python:

* `round(2.9)` returns `3`.
* `abs(-2.9)` returns `2.9` in its positive form.

## Math Functions in Python:

* `math.ceil(1.2)` returns `2`.
* `math.ceil(2.4)` returns `3`.

## Array Creation:

```python
import array as ar
numbersArray = ar.array('i', [1, 2, 3, 4, 5, 6])  # Creating an array of integers
```

## Python Data Types and Ordering

### Built-in Functions:

* `int()`
* `str()`
* `float()`
* `bool()` - Some values are meant to be falsy like:
  * 0
  * None
  * ""

### Primitive Types in Python:

1. Strings
2. Numbers:
3. `int`, `float`, complex numbers
4. Boolean

### Lexicographical Order:

* `"bag" == "BAG"` is `False` because even though they appear the same, in programming, they are represented by different numbers, like `ord("b")`.

#### ASCII Representation:

* For uppercase letters:
  * From 65 to 90 (ASCII)
  * For lowercase letters: From 97 to 122
  * For numbers: From 48 to 57 (0 to 9)
* Ascii Uppercase Alphabet Print:

```python
import string
import time

for i in string.ascii_uppercase:
    print(f"{i} => {ord(i)}")
    time.sleep(0.5)
```

* Range of ASCII Uppercase Letters:

```python
for i in range(65, 91):
    print(chr(i))
    time.sleep(0.3)

```

### TERNARY OPERATOR

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

### Logical Operators in Python

In Python, logical operators exhibit short-circuit behavior. For `and` operators, if the first operand is `False`, the evaluation stops as the whole condition will be `False`. Similarly, for `or` operators, if the first operand is `True`, the evaluation stops as the whole condition will be `True`.

Example:

```python
income and score and student
income or score or student

if age >= 18 and age < 65:
    print("")
if 18 <= age < 65:
    print("")
```

### Range Function in Python

The `range()` function in Python can be used with different parameters. Both `range(3)` and `range(0, 3)` result in the same iteration: starting from 0 and iterating 3 times.

The `range()` function follows this structure:

* `range(start, stop, step)`

For example, `range(1, end+1)` starts from 1 (inclusive) and iterates until `end+1` (exclusive), effectively repeating `end` times.

Example usage:

```python
range(0, 21, 2)  # Generates a sequence of multiples of 2
```

### For-Else Loop in Python

In Python, the `for-else` loop is a construct used to execute a block of code in the scenario where the `for` loop completes its iterations without encountering a `break` statement.

#### Syntax:

```python
for item in iterable:
    if condition:
        # Perform operations
        break
else:
    # Execute this block if the loop completes without encountering a break
    # Operations to perform when the loop completes without a break
```

* If the for loop is exited via a break statement, the else block will not execute.
* However, if the loop completes all iterations without encountering a break, the else block will execute.

### Functions in Python

Functions in Python are defined using the `def` keyword followed by the function name and parameters. Arguments can then be passed to these functions when calling them.

#### Defining a Function

```python
def function_name(these_are_parameters):
    # Function body
    # Perform operations using parameters
    # Return a value using the return statement
```

#### Calling a Function

```python

function_name(this_is_arguments)  # Calling a function with arguments

```

* By default, arguments in Python functions are required unless default values are specified for them.

#### Return Statement

* The return statement in Python functions is used to return a value. If no value is specified, the function returns None. When a function returns a value, it evaluates as True, otherwise, it returns False.

```python
def greet(name):
    return f"Hello, {name}!"

msg = greet("Akash")
print(msg)  # Output: Hello, Akash!

```

* Remember that every function in Python returns a value, even if it's None. Functions can be used to perform operations and return specific results or manipulate data.

#### Keyword Arguments in Python:

* Keyword arguments allow us to pass arguments to a function using parameter names.

### For instance:

```python
print(increment(5, 10))
# This calls the 'increment' function with arguments '5' and '10'

# Alternatively, using keyword arguments:
print(increment(number=5, by=10))
# This achieves the same as the previous call but uses keyword arguments 'number' and 'by'
```

* Additionally, default values can be defined for function parameters, allowing us to omit certain arguments:

```python
def increment(number, by=1):
    return number + by

# Here, 'by' has a default value of 1
# If 'by' is not specified, it automatically takes the default value
print(increment(5))  # Output: 6 (5 + 1)
print(increment(5, 3))  # Output: 8 (5 + 3)

```

## Optional Arguments in Python Functions

When defining arguments in Python functions, it's essential to define optional arguments at the end. It's not possible to define a required argument after an optional one.

If we have a scenario where the parameters are not predefined, such as when multiplying multiple numbers, we can utilize the `*args` technique within the function.

For example:

```python
def multiply(*numbers):
    # This creates an iterable tuple with the numbers passed to the function
    pass  # Perform the necessary operations with the numbers
```

* The \*numbers parameter here collects all passed arguments into an iterable tuple within the function, allowing flexibility in handling various numbers.

## Double Asterisks (\*\*kwargs) in Python

The double asterisks `**kwargs` in Python save values in a dictionary. This technique, often called "keyword arguments," receives arguments in the form of key-value pairs. For instance:

```python
def sample_function(**kwargs):
    # This allows receiving arguments in a key-value format like id=1, name="Akash", age=22
    pass  # Perform operations with the received key-value pairs
```

* The \*\*kwargs parameter collects these keyword arguments into a dictionary within the function. Once collected, you can access and manipulate these values just like you would with a dictionary in Python.

## Scope of Variables in Functions

Variables defined within a function are considered local variables. They exist solely within the function's scope and are inaccessible outside it.

However, it's possible to declare the same parameters in different functions without any issues.

To access global variables within a function, you can use the `global` keyword followed by the variable name. For instance:

```python
message = "a"

def my_function():
    global message
    message = "b"  # Assigning a new value to the global variable 'message'
```

* The global keyword allows modification of a global variable within a function, but it's often considered a bad practice due to the potential for unexpected side effects and code complexity.

#### FizzBuzz Function

This function performs the FizzBuzz operation on a given input number and returns the result.

```python
def fizz_buzz(input):
    inputM = "FizzBUzz" if not input % 3 and not input % 5 else "Fizz" if not input % 3 else "Buzz" if not input % 5 else input

    return inputM

print(fizz_buzz(71))
```

#### Lists

* If we create two lists in a list, then it is called a two-dimensional list.
* The `list()` function needs an iterable object and then creates a list of that, like `list(range(20))`.
* When printing a list, we can use slicing, denoted by `::`. For example, `::2` means it will print the second number and skip the others.
* If the slicing value is `2`, the first index will be printed, and then it will skip the number of counts specified in the slicing value. For instance:
* 1 2 3 4 5 6 ::2
* 1 skip 3 skip 5 skip
* When using `3` as the slicing value, it will skip as follows:
* 1 skip skip 4 skip skip 7 skip skip 10
* Using `::1` prints the normal list, and `::-1` prints it in reverse.

## Unpacking Lists in Python

Unpacking lists in Python involves assigning values of a list to multiple variables simultaneously.

```python
listNumber = [1, 2, 3, 4, 5]
first = listNumber[0]
second = listNumber[1]
third = listNumber[2]
fourth = listNumber[3]
```

## Unpacking list values to multiple variables in one line

```python
first, second, third, fourth, fifth = listNumber

```

* Note: The number of variables on the left side should match the length of the list for successful unpacking.

## Unpacking Lists and Extracting Last Number

In Python, unpacking lists allows assigning list values to multiple variables simultaneously. The `*` operator can be used to capture remaining values.

```python
# Unpacking list values to multiple variables and capturing the remaining values
listNumber = [1, 2, 3, 4, 5]

# Unpacking with *other to capture remaining values
first, second, *other = listNumber
```

* This assigns the first and second values according to their indexes and assigns the remaining list elements to the other variable.

```python
# Extracting the last number from a list using unpacking
first, *other, second = listNumber

```

* The above code extracts the first and last values while capturing the intermediate values in the other variable.
* This feature is beneficial when handling lists with a large number of elements or when there's a need to extract specific elements efficiently.

## Iterating Lists with Index Using `enumerate`

When iterating over lists in Python, you can obtain both the index and the value of each element by using the `enumerate()` function.

```python
listNumber = ["a", "b", "c", "d", "e"]

# Using enumerate to access both index and item in the list
for index, letter in enumerate(listNumber):
    print(index, letter)
```

* The enumerate() function generates an index-value tuple for each element in the list during iteration. By unpacking this tuple directly in the loop, you can access both the index and the item of the list simultaneously.
* This method is convenient when you need to access both the index and the corresponding value while iterating through a list.

## List Methods in Python

Python offers several built-in methods to manipulate lists effectively.

#### Adding Items to Lists

* `list.append(item)`: Adds an item to the end of the list.
* `list.insert(index, item)`: Inserts an item at a specified index in the list.

#### Removing Items from Lists

* `list.pop()`: Removes the last item from the list.
* `list.pop(index)`: Removes the item at the specified index from the list.
* `list.remove(item)`: Removes the first occurrence of the specified item from the list.

**Deleting Items**

* `del list[index]`: Deletes the item at the specified index.
* `del list[start:end]`: Deletes items within a specified range.
* `list.clear()`: Removes all items from the list.

#### Retrieving Item Information

* `list.index(item)`: Finds the index of the specified item.
* `list.count(item)`: Counts occurrences of a specific item in the list.

#### Sorting Lists

* `list.sort()`: Sorts the list in ascending order.
* `list.sort(reverse=True)`: Sorts the list in descending order.
* `sorted(iterable)`: Returns a new sorted list without modifying the original list.

#### Example:

```python
lst = [1, 2, 3, 4, 5, 5, 5, 5, 3, 5, 5, 3]

# Count occurrence and print indices of an item
if lst.count(5):
    for index, item in enumerate(lst):
        if item == 5:
            print(f"5 is at index {index}")
```

## Sorting Lists with Key Parameter and Lambda Functions

When dealing with complex data structures like tuples or lists containing multiple items or strings, direct sorting might not work as intended. In Python, the `sort()` method provides a `key` parameter to facilitate sorting based on specific criteria.

#### Using a Function with `key` Parameter

```python
def read_index(items):
    return items[1]

item = [(1, 'Apple', 30), (3, 'Banana', 20), (2, 'Orange', 25)]

print("Normal List...")
for i in item:
    print(i)

item.sort(key=read_index)
print("Sorted List...")
for i in item:
    print(i)

print("Reverse Sorted")
item.sort(key=read_index, reverse=True)
for i in item:
    print(i)
```

#### Lambda Functions for Sorting

* Instead of defining a separate function, lambda functions (anonymous functions) can be used with the key parameter to achieve the same result more concisely.

```python
item = [(1, 'Apple', 30), (3, 'Banana', 20), (2, 'Orange', 25)]

print("Normal List...")
for i in item:
    print(i)

item.sort(key=lambda items: items[1])
print("Sorted List...")
for i in item:
    print(i)

print("Reverse Sorted")
item.sort(key=lambda items: items[1], reverse=True)
for i in item:
    print(i)

```

#### Use Cases of Lambda Functions

* Lambda functions are versatile and handy, especially when used in scenarios like filtering or sorting complex data:

```python
# Filtering even numbers using lambda
evenNumbers = [2, 5, 7, 10, 12, 15, 17, 20]
filtered = list(filter(lambda x: x % 2 == 0, evenNumbers))

# Sorting dictionaries based on a specific key using lambda and sorted
data = [{'name': 'Alice', 'age': 25}, {'name': 'Bob', 'age': 30}, {'name': 'Charlie', 'age': 20}]
sorted_data = sorted(data, key=lambda x: x['age'])

```

* Lambda functions offer a concise way to define simple functions inline, making them useful for filtering and sorting operations.

#### Map Function in Python

The `map` function in Python requires two parameters: the function and the iterable.

Here is an example using a lambda function with a list containing names and their respective ages:

```python
list1 = [
    ("Akash", 2),
    ("Kumar", 1),
    ("Nar", 8)
]

ages = list(map(lambda item: item[1], list1))
print(ages)
```

#### Filter Function in Python

The `filter()` function in Python takes two arguments: a function (which can be a lambda function or a defined function that returns a boolean value) and an iterable.

An example of using `filter()` with a lambda function to filter even numbers from a list:

```python
even_numbers = list(filter(lambda num: not num % 2, ll))
```

* This code snippet demonstrates how to filter even numbers from a list (ll) using the filter() function with a lambda function and convert the result to a list for further processing.

#### List Comprehension in Python

List comprehension is a concise and powerful way to create lists in Python.

Consider the following example:

```python
list1 = [
    ("Akash", 2),
    ("Kumar", 1),
    ("Nar", 8)
]
```

## Extracting names and ages from a list of tuples

```python
names = [i[0] for i in list1]
ages = [j[1] for j in list1]
print(names)
print(ages)
```

## Iterating through names and ages using a loop

```python
for i in range(3):
    print(f"Names => {names[i]} || Ages => {ages[i]}")
```

## Using list comprehension for filtering based on a condition

```python
agess = [item for item in list1 if not item[1] > 4]
```

## Additional list examples

```python
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c', 'd']
list3 = ['x', 'y', 'z']
list4 = list("AKash Kumar")
```

#### Function For Zip

```python
def functionForZip(*lists):
    zipped = []

    minimumLoopWeCanDo = min(len(line) for line in lists)

    for i in range(minimumLoopWeCanDo):
        tupleToBeAdded = tuple(tupleNo[i] for tupleNo in lists)
        # The tuple before the list comprehension function is important.
        # In every loop, it takes the i'th list and uses it as tupleNo, referencing that loop.
        # It then takes the i'th index from that list and runs as much as the quantity of the lists.
        # Before adding it to the zip, it completes the tuple because of the for loop.
        zipped.append(tupleToBeAdded)

    return zipped

result = functionForZip(list1, list2, list3, list4)
print(result)

```

#### Stacks in programming operate on the principle of Last-In, First-Out (LIFO).

* Items are added or pushed at the end of the stack, and the last added item is the first to be removed.
* Use 'append' to insert an item into the stack.
* Use 'pop' to remove the last item.
* 'not' with the list name acts as a boolean function for checking if the stack is empty.
* '-1' index refers to the last value in the stack.
* Conceptually similar to a mobile browsing session where the last visited pages are accessible first.

#### Similar to stacks, a queue follows the First-In, First-Out (FIFO) principle.

* 'deque' is an efficient double-ended queue for removing elements from the front and back.
* 'Queue' needs to be imported and operates on the first item added, which is the first to be retrieved.

```python
from queue import Queue

# Creating a Queue object
my_queue = Queue()

# Enqueuing elements into the queue
my_queue.put(10)
my_queue.put(20)
my_queue.put(30)

# Dequeuing elements from the queue (FIFO order)
print(my_queue.get())  # Output: 10
print(my_queue.get())  # Output: 20
print(my_queue.get())  # Output: 30

# Demonstrating a new Queue object
new_queue = Queue()

new_queue.put(10)
print(new_queue.get())  # Output: 10
new_queue.put(200)
new_queue.put(30)
print(new_queue.get())  # Output: 200

```

* This example illustrates how a queue behaves, enqueuing and dequeuing elements in a first-in, first-out order using Python's Queue module.

#### Tuples in Python

Tuples in Python can be defined using parentheses `()` or without them. For instance:

* `tupleNumbers = 1, 2, 4` is a tuple.
* If it's a single value tuple, ensure a comma is included at the end: `singleValueTuple = 5,`

#### Operations on Tuples

Operations on tuples include:

* Concatenation: `newTuple = tuple1 + tuple2`
* Multiplication: `multipliedTuple = tuple1 * 3`

#### Converting to Tuples

Converting other iterables to a tuple:

* Use `tuple()` function: `tuple_from_list = tuple([1, 2, 3])` or `tuple_from_string = tuple("hello")`

#### Iterating Over Strings to Create a Tuple

Strings can be directly iterated to obtain a tuple: `my_string = "hello"; tuple_from_string = tuple(my_string)`

#### Swapping Variables in Python

In Python, swapping the values of two variables is often done in a single line without using a temporary variable. For instance:

```python
x = 10
y = 11

# Traditional swapping method
temp = x
x = y
y = temp
```

### But in Python, it can be efficiently achieved with one line of code:

```python
x, y = y, x

```

* This concise method leverages tuple unpacking, where the right-hand side tuple values are assigned to the variables on the left-hand side. This works seamlessly for swapping two values without requiring a third variable.
* Similarly, you can assign multiple variables in a single line:

```python
a, b, c = 1, 2, 3  # Equivalent to (1, 2, 3)

```

#### Sets in Python

* Sets are excellent for removing duplicate elements in a list. Using a set, duplicates are eliminated.

```python
setNo = set([1, 1, 2, 4, 5])

```

* Methods like .add() and .remove() are used to manage sets, and they are represented with curly braces {}.

## Set Operations

Sets support various mathematical operations:

* **Union (`|`)**: Combines unique elements from both sets.
* **Intersection (`&`)**: Retrieves common elements present in both sets.
* **Difference (`-`)**: Retrieves elements in the first set but not in the second.
* **Symmetric Difference (`^`)**: Retrieves unique elements in both sets.

```python
first = set(range(1, 6))
second = {3, 5, 6, 6, 8, 9, 4, 4, 3}

print(first | second)  # Union
print(first & second)  # Intersection
print(first - second)  # Difference
print(first ^ second)  # Symmetric Difference
```

* Sets are unordered collections of unique items and do not allow duplicates. They do not support indexing.

## Dictionary Operations

Dictionaries can be declared with named keys and values:

```python
point = dict(x=1, y=2)
```

* The index in dictionaries is the key, not sequential like 0, 1, 2 in lists. If the key doesn't exist, an error occurs. To prevent errors, use the .get() method:

```python
print(point.get("a", 0))  # Outputs 0 if the key "a" doesn't exist

```

## To delete a key:

```python
del point["x"]

```

* This removes the key-value pair associated with the key "x".

## Dictionary Iteration

In a loop or iteration over a dictionary, it returns the keys of the dictionary.

We can iterate through a dictionary using:

```python
my_dict = {'a': 1, 'b': 2, 'c': 3}

# Iterating through keys
for key in my_dict:
    value = my_dict[key]
    print(f"Key: {key}, Value: {value}")

# Iterating through dictionary items (as tuples)
for key, value in my_dict.items():
    print(f"Key: {key}, Value: {value}")
```

* The dict.items() method provides an iterator that yields tuples of key-value pairs during iteration. Each iteration in the loop will have a tuple containing the key and its corresponding value.

## Comprehensions in Python

Similar to list comprehensions, dictionaries can also be created using comprehension expressions.

For example:

```python
# List Comprehension
values = [x * 2 for x in range(5)]  # Generates even numbers in a list

# Dictionary Comprehension
value_dict = {x: x * 2 for x in range(5)}  # Generates a dictionary with keys and their respective values

print(value_dict)

for key, value in value_dict.items():
    print(f"{key} => {value}")
```

* When using comprehension expressions with () brackets, it creates a generator object. Generators are beneficial when dealing with large datasets as they consume less memory. They generate new values each time they are iterated but do not store them. The getsizeof function from the sys module can be used to determine the memory size of an object.

#### Generators

```python
from sys import getsizeof

# Generator Expression
gen = (x * 2 for x in range(200))
print("Generator:", getsizeof(gen))

# List Comprehension
value = [x * 2 for x in range(200)]
print("List:", getsizeof(value))
```

* Generators are more memory-efficient compared to lists. Generators do not hold all the values in memory at once; they generate values dynamically, consuming less memory. Hence, they do not have a len() method as they are not storing the entire sequence in memory. On the other hand, lists hold all the generated values in memory, and therefore, they do have a len() method to determine the length of the sequence.

#### Unpacking Operator

When printing a list, it typically shows as a sequence enclosed in square brackets: `[item1, item2, item3]`. To print the individual items separately, use the unpacking operator `*`.

For example:

```python
numbers = [1, 2, 3, 4]
print(*numbers)  # Unpacks the list and prints individual items: 1 2 3 4
```

* The unpacking operator \* can be used with any iterable, including strings or numbers. It allows combining multiple iterables into a new one:

```python
value = [*range(5), *"HelloWorld"]  # Combining range and string iterables

```

* For dictionaries, the double asterisk \*\* can be used. When combining dictionaries with the same keys, the value from the second dictionary overrides the value from the first dictionary:

```python
first = {'a': 1, 'b': 2}
second = {'b': 3, 'c': 4}
combined = {**first, **second}  # Merging two dictionaries

```

#### Expections and Try

* Handling exceptions using try-except blocks:

```python
try:
    # Your code that may raise an exception
except ValueError as ex:
    print("Lovely message:", ex)  # Print a message if a ValueError occurs
    print(type(ex))  # Print the type of the exception

# Adding an `else` clause to execute code when no exception occurs
else:
    # Code to execute if no exception occurs in the `try` block

```

#### Format Specifiers in Python's f-strings

In Python's f-strings, various format specifiers can be used to format different types of values. Here are some common format specifiers and their uses:

**Integer Formatting:**

* `:d` - Formats an integer as a decimal number.
* `:b` - Formats an integer as a binary number.
* `:o` - Formats an integer as an octal number.
* `:x` or `:X` - Formats an integer as a hexadecimal number (lowercase or uppercase).

**Example:**

```python
number = 42
print(f"Decimal: {number:d}, Binary: {number:b}, Octal: {number:o}, Hex: {number:x}")
# Output: Decimal: 42, Binary: 101010, Octal: 52, Hex: 2a
```

### Floating-Point Formatting:

* `:f` - Formats a floating-point number as a decimal number.
* `:.nf` - Formats a floating-point number with 'n' decimal places.

**Example:**

```python
value = 3.14159
print(f"Float: {value:f}, Formatted Float: {value:.2f}")
# Output: Float: 3.141590, Formatted Float: 3.14
```

### String Formatting:

* `:s` - Formats a value as a string.

**Example:**

```python
name = "Alice"
age = 30
print(f"Name: {name:s}, Age: {age:d}")
# Output: Name: Alice, Age: 30
```

### Padding and Alignment:

* `:<` - Left-aligns the text.
* `:>` - Right-aligns the text.
* `:^` - Centers the text.
* `:<n`, `:>n`, `:^n` - Aligns to a specified width 'n'.

**Example:**

```python
text = "Hello"
print(f"Left-aligned: {text:<10}, Right-aligned: {text:>10}, Centered: {text:^10}")
# Output: Left-aligned: Hello     , Right-aligned:      Hello, Centered:   Hello
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.mrakashkumar.in/programming/python.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
