---
# System prepended metadata

title: Miscellaneous

---

<div>
    <p>
        For more details, please visit <strong><a href="https://edube.org/learn/pe-2/python-essentials-2-module-4-1">this link</a></strong> to access documents and practice.
    </p>  
</div>

# Miscellaneous
### Generators
*  Generators is a piece of specialized code able to produce a series of values, and to control the iteration process
```python
for i in range(5):
    print(i)
# The range function is a generator
```
* Function returns 1 | Generator returns a series of values

**The iterator protocol is a way in which an object should behave to conform to the rules imposed by the context of the for and in statements.** An object conforming to the iterator protocol is called an **iterator**.
![](https://hackmd.io/_uploads/r1sbntTAn.png)
```python
class MyIterable:
    def __init__(self, start, end):
        self.current = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.end:
            raise StopIteration
        else:
            self.current += 1
            return self.current - 1

# Using the custom iterable
my_iterable = MyIterable(1, 5)

for i in my_iterable:
    print(i)

```
* Explain: In this example, MyIterable is a class that implements the iterator protocol. It has ```__iter__``` which returns self (since the object itself is the iterator), and ```__next__``` which returns the current value and increments it until it reaches the end value. When you use a for loop to iterate over my_iterable, it will call iter(my_iterable) to get an iterator and then repeatedly call next(iterator) to get the values until a StopIteration is raised.

#### yield
```python
def fun(n):
    for i in range(n):
        return i
# the function above is not a generator
def fun(n):
    for i in range(n):
        yield i
# the function above is a generator
```
* yield is a keyword in Python that is used in the body of a function like a return statement, but instead of terminating the function, it temporarily suspends the function's execution and sends a value back to the caller. This allows the function to be resumed from where it left off the next time it's called.

#### list comprehensions
* list() function
```python
def powers_of_2(n):
    power = 1
    for i in range(n):
        yield power
        power *= 2


t = list(powers_of_2(3))
print(t)
# the list() function is used to convert the output of the generator function powers_of_2(n) into a list.
```
* in operator
```python
def powers_of_2(n):
    power = 1
    for i in range(n):
        yield power
        power *= 2


for i in range(20):
    if i in powers_of_2(4):
        print(i)
# the in operator allows you to use a generator
```

#### example of list comprehension
```python
list_1 = []

for ex in range(6):
    list_1.append(10 ** ex)

list_2 = [10 ** ex for ex in range(6)]

print(list_1)
print(list_2)
#[1, 10, 100, 1000, 10000, 100000]
#[1, 10, 100, 1000, 10000, 100000]
```
* Use if statement in list comprehension
>expression_one if condition else expression_two
```python
# Example 1: Filtering even numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

even_numbers = [x for x in numbers if x % 2 == 0]
print(even_numbers)
```

#### List comprehensions vs. generators
* Just one change can turn any list comprehension into a generator
```python
the_list = [1 if x % 2 == 0 else 0 for x in range(10)]
the_generator = (1 if x % 2 == 0 else 0 for x in range(10))

for v in the_list:
    print(v, end=" ")
print()

for v in the_generator:
    print(v, end=" ")
print()
# output: 1 0 1 0 1 0 1 0 1 0 
# this difference is the parentheses. The brackets make a comprehension, the parentheses make a generator.
```

#### The lambda function
>lambda arguments: expression
```python
add = lambda x, y: x + y
add(1,2)
#output = 3
```
 **Using map with a lambda function**
```python
numbers = [1, 2, 3, 4, 5]
squared_numbers = map(lambda x: x**2, numbers)
print(list(squared_numbers))  # Output: [1, 4, 9, 16, 25]
# The map() function applies the lambda function to each element in the numbers list, returning an iterator of the results.
```
**Using filter with a lambda function**
```python
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers))  # Output: [2, 4, 6, 8, 10]
# The filter() function applies the lambda function to each element in the numbers list and returns an iterator containing only the elements for which the lambda function returns True.
```
**Sorting a list of strings by their length**
```python
words = ["apple", "banana", "cherry", "date", "fig"]
sorted_words_by_length = sorted(words, key=lambda x: len(x))
print(sorted_words_by_length)  # Output: ['fig', 'date', 'apple', 'cherry', 'banana']
# The sorted() function sorts the words list based on the length of each word. The key parameter specifies a function that calculates a "sort key" for each element. Here, it uses the lambda function to get the length of each word as the sort key.
```
### Closures
* **closure** is a technique which allows the storing of values in spite of the fact that the context in which they have been created does not exist anymore


# Understand basic Input/Output terminology
## • I/O modes


