Skip to content

Commit cadb6ff

Browse files
authored
Merge branch 'main' into patch-2
2 parents 2647dbc + d2d6db0 commit cadb6ff

32 files changed

+2496
-1
lines changed

contrib/advanced-python/closures.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Closures
2+
In order to have complete understanding of this topic in python, one needs to be crystal clear with the concept of functions and the different types of them which are namely First Class Functions and Nested Functions.
3+
4+
### First Class Functions
5+
These are the normal functions used by the programmer in routine as they can be assigned to variables, passed as arguments and returned from other functions.
6+
### Nested Functions
7+
These are the functions defined within other functions and involve thorough usage of **Closures**. It is also referred as **Inner Functions** by some books. There are times when it is required to prevent a function or the data it has access to from being accessed from other parts of the code, and this is where Nested Functions come into play. Basically, its usage allows the encapsulation of that particular data/function within another function. This enables it to be virtually hidden from the global scope.
8+
9+
## Defining Closures
10+
In nested functions, if the outer function basically ends up returning the inner function, in this case the concept of closures comes into play.
11+
12+
A closure is a function object that remembers values in enclosing scopes even if they are not present in memory. There are certain neccesary condtions required to create a closure in python :
13+
1. The inner function must be defined inside the outer function.
14+
2. The inner function must refer to a value defined in the outer function.
15+
3. The inner function must return a value.
16+
17+
## Advantages of Closures
18+
* Closures make it possible to pass data to inner functions without first passing them to outer functions
19+
* Closures can be used to create private variables and functions
20+
* They also make it possible to invoke the inner function from outside of the encapsulating outer function.
21+
* It improves code readability and maintainability
22+
23+
## Examples implementing Closures
24+
### Example 1 : Basic Implementation
25+
```python
26+
def make_multiplier_of(n):
27+
def multiplier(x):
28+
return x * n
29+
return multiplier
30+
31+
times3 = make_multiplier_of(3)
32+
times5 = make_multiplier_of(5)
33+
34+
print(times3(9))
35+
print(times5(3))
36+
```
37+
#### Output:
38+
```
39+
27
40+
15
41+
```
42+
The **multiplier function** is defined inside the **make_multiplier_of function**. It has access to the n variable from the outer scope, even after the make_multiplier_of function has returned. This is an example of a closure.
43+
44+
### Example 2 : Implementation with Decorators
45+
```python
46+
def decorator_function(original_function):
47+
def wrapper_function(*args, **kwargs):
48+
print(f"Wrapper executed before {original_function.__name__}")
49+
return original_function(*args, **kwargs)
50+
return wrapper_function
51+
52+
@decorator_function
53+
def display():
54+
print("Display function executed")
55+
56+
display()
57+
```
58+
#### Output:
59+
```
60+
Wrapper executed before display
61+
Display function executed
62+
```
63+
The code in the example defines a decorator function: ***decorator_function*** that takes a function as an argument and returns a new function **wrapper_function**. The **wrapper_function** function prints a message to the console before calling the original function which appends the name of the called function as specified in the code.
64+
65+
The **@decorator_function** syntax is used to apply the decorator_function decorator to the display function. This means that the display function is replaced with the result of calling **decorator_function(display)**.
66+
67+
When the **display()** function is called, the wrapper_function function is executed instead. The wrapper_function function prints a message to the console and then calls the original display function.
68+
### Example 3 : Implementation with for loop
69+
```python
70+
def create_closures():
71+
closures = []
72+
for i in range(5):
73+
def closure(i=i): # Capture current value of i by default argument
74+
return i
75+
closures.append(closure)
76+
return closures
77+
78+
my_closures = create_closures()
79+
for closure in my_closures:
80+
print(closure())
81+
82+
```
83+
#### Output:
84+
```
85+
0
86+
1
87+
2
88+
3
89+
4
90+
```
91+
The code in the example defines a function **create_closures** that creates a list of closure functions. Each closure function returns the current value of the loop variable i.
92+
93+
The closure function is defined inside the **create_closures function**. It has access to the i variable from the **outer scope**, even after the create_closures function has returned. This is an example of a closure.
94+
95+
The **i**=*i* argument in the closure function is used to capture the current value of *i* by default argument. This is necessary because the ****i** variable in the outer scope is a loop variable, and its value changes in each iteration of the loop. By capturing the current value of *i* in the default argument, we ensure that each closure function returns the correct value of **i**. This is responsible for the generation of output 0,1,2,3,4.
96+
97+
98+
For more examples related to closures, [click here](https://dev.to/bshadmehr/understanding-closures-in-python-a-comprehensive-tutorial-11ld).
99+
100+
## Summary
101+
Closures in Python provide a powerful mechanism for encapsulating state and behavior, enabling more flexible and modular code. Understanding and effectively using closures enables the creation of function factories, allows functions to have state, and facilitates functional programming techniques
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Understanding the `eval` Function in Python
2+
## Introduction
3+
4+
The `eval` function in Python allows you to execute a string-based Python expression dynamically. This can be useful in various scenarios where you need to evaluate expressions that are not known until runtime.
5+
6+
## Syntax
7+
```python
8+
eval(expression, globals=None, locals=None)
9+
```
10+
11+
### Parameters:
12+
13+
* expression: String is parsed and evaluated as a Python expression
14+
* globals [optional]: Dictionary to specify the available global methods and variables.
15+
* locals [optional]: Another dictionary to specify the available local methods and variables.
16+
17+
## Examples
18+
Example 1:
19+
```python
20+
result = eval('2 + 3 * 4')
21+
print(result) # Output: 14
22+
```
23+
Example 2:
24+
25+
```python
26+
x = 10
27+
expression = 'x * 2'
28+
result = eval(expression, {'x': x})
29+
print(result) # Output: 20
30+
```
31+
Example 3:
32+
```python
33+
x = 10
34+
def multiply(a, b):
35+
return a * b
36+
expression = 'multiply(x, 5) + 2'
37+
result = eval(expression)
38+
print("Result:",result) # Output: Result:52
39+
```
40+
Example 4:
41+
```python
42+
expression = input("Enter a Python expression: ")
43+
result = eval(expression)
44+
print("Result:", result)
45+
#input= "3+2"
46+
#Output: Result:5
47+
```
48+
49+
Example 5:
50+
```python
51+
import numpy as np
52+
a=np.random.randint(1,9)
53+
b=np.random.randint(1,9)
54+
operations=["*","-","+"]
55+
op=np.random.choice(operations)
56+
57+
expression=str(a)+op+str(b)
58+
correct_answer=eval(expression)
59+
given_answer=int(input(str(a)+" "+op+" "+str(b)+" = "))
60+
61+
if given_answer==correct_answer:
62+
print("Correct")
63+
else:
64+
print("Incorrect")
65+
print("correct answer is :" ,correct_answer)
66+
67+
#2 * 1 = 8
68+
#Incorrect
69+
#correct answer is : 2
70+
#or
71+
#3 * 2 = 6
72+
#Correct
73+
```
74+
## Conclusion
75+
The eval function is a powerful tool in Python that allows for dynamic evaluation of expressions.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Filter Function
2+
3+
## Definition
4+
The filter function is a built-in Python function used for constructing an iterator from elements of an iterable for which a function returns true.
5+
6+
**Syntax**:
7+
```python
8+
filter(function, iterable)
9+
```
10+
**Parameters**:<br>
11+
*function*: A function that tests if each element of an iterable returns True or False.<br>
12+
*iterable*: An iterable like sets, lists, tuples, etc., whose elements are to be filtered.<br>
13+
*Returns* : An iterator that is already filtered.
14+
15+
## Basic Usage
16+
**Example 1: Filtering a List of Numbers**:
17+
```python
18+
# Define a function that returns True for even numbers
19+
def is_even(n):
20+
return n % 2 == 0
21+
22+
numbers = [1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
23+
even_numbers = filter(is_even, numbers)
24+
25+
# Convert the filter object to a list
26+
print(list(even_numbers)) # Output: [2, 4, 6, 8, 10]
27+
```
28+
29+
**Example 2: Filtering with a Lambda Function**:
30+
```python
31+
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
32+
odd_numbers = filter(lambda x: x % 2 != 0, numbers)
33+
34+
print(list(odd_numbers)) # Output: [1, 3, 5, 7, 9]
35+
```
36+
37+
**Example 3: Filtering Strings**:
38+
```python
39+
words = ["apple", "banana", "cherry", "date", "elderberry", "fig", "grape" , "python"]
40+
long_words = filter(lambda word: len(word) > 5, words)
41+
42+
print(list(long_words)) # Output: ['banana', 'cherry', 'elderberry', 'python']
43+
```
44+
45+
## Advanced Usage
46+
**Example 4: Filtering Objects with Attributes**:
47+
```python
48+
class Person:
49+
def __init__(self, name, age):
50+
self.name = name
51+
self.age = age
52+
53+
people = [
54+
Person("Alice", 30),
55+
Person("Bob", 15),
56+
Person("Charlie", 25),
57+
Person("David", 35)
58+
]
59+
60+
adults = filter(lambda person: person.age >= 18, people)
61+
adult_names = map(lambda person: person.name, adults)
62+
63+
print(list(adult_names)) # Output: ['Alice', 'Charlie', 'David']
64+
```
65+
66+
**Example 5: Using None as the Function**:
67+
```python
68+
numbers = [0, 1, 2, 3, 0, 4, 0, 5]
69+
non_zero_numbers = filter(None, numbers)
70+
71+
print(list(non_zero_numbers)) # Output: [1, 2, 3, 4, 5]
72+
```
73+
**NOTE**: When None is passed as the function, filter removes all items that are false.
74+
75+
## Time Complexity:
76+
- The time complexity of filter() depends on two factors:
77+
1. The time complexity of the filtering function (the one you provide as an argument).
78+
2. The size of the iterable being filtered.
79+
- If the filtering function has a constant time complexity (e.g., O(1)), the overall time complexity of filter() is linear (O(n)), where ‘n’ is the number of elements in the iterable.
80+
81+
## Space Complexity:
82+
- The space complexity of filter() is also influenced by the filtering function and the size of the iterable.
83+
- Since filter() returns an iterator, it doesn’t create a new list in memory. Instead, it generates filtered elements on-the-fly as you iterate over it. Therefore, the space complexity is O(1).
84+
85+
## Conclusion:
86+
Python’s filter() allows you to perform filtering operations on iterables. This kind of operation consists of applying a Boolean function to the items in an iterable and keeping only those values for which the function returns a true result. In general, you can use filter() to process existing iterables and produce new iterables containing the values that you currently need.Both versions of Python support filter(), but Python 3’s approach is more memory-efficient due to the use of iterators.

contrib/advanced-python/generators.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Generators
2+
3+
## Introduction
4+
5+
Generators in Python are a sophisticated feature that enables the creation of iterators without the need to construct a full list in memory. They allow you to generate values on-the-fly, which is particularly beneficial for working with large datasets or infinite sequences. We will explore generators in depth, covering their types, mathematical formulation, advantages, disadvantages, and implementation examples.
6+
7+
## Function Generators
8+
9+
Function generators are created using the `yield` keyword within a function. When invoked, a function generator returns a generator iterator, allowing you to iterate over the values generated by the function.
10+
11+
### Mathematical Formulation
12+
13+
Function generators can be represented mathematically using set-builder notation. The general form is:
14+
15+
```
16+
{expression | variable in iterable, condition}
17+
```
18+
19+
Where:
20+
- `expression` is the expression to generate values.
21+
- `variable` is the variable used in the expression.
22+
- `iterable` is the sequence of values to iterate over.
23+
- `condition` is an optional condition that filters the values.
24+
25+
### Advantages of Function Generators
26+
27+
1. **Memory Efficiency**: Function generators produce values lazily, meaning they generate values only when needed, saving memory compared to constructing an entire sequence upfront.
28+
29+
2. **Lazy Evaluation**: Values are generated on-the-fly as they are consumed, leading to improved performance and reduced overhead, especially when dealing with large datasets.
30+
31+
3. **Infinite Sequences**: Function generators can represent infinite sequences, such as the Fibonacci sequence, allowing you to work with data streams of arbitrary length without consuming excessive memory.
32+
33+
### Disadvantages of Function Generators
34+
35+
1. **Single Iteration**: Once a function generator is exhausted, it cannot be reused. If you need to iterate over the sequence again, you'll have to create a new generator.
36+
37+
2. **Limited Random Access**: Function generators do not support random access like lists. They only allow sequential access, which might be a limitation depending on the use case.
38+
39+
### Implementation Example
40+
41+
```python
42+
def fibonacci():
43+
a, b = 0, 1
44+
while True:
45+
yield a
46+
a, b = b, a + b
47+
48+
# Usage
49+
fib_gen = fibonacci()
50+
for _ in range(10):
51+
print(next(fib_gen))
52+
```
53+
54+
## Generator Expressions
55+
56+
Generator expressions are similar to list comprehensions but return a generator object instead of a list. They offer a concise way to create generators without the need for a separate function.
57+
58+
### Mathematical Formulation
59+
60+
Generator expressions can also be represented mathematically using set-builder notation. The general form is the same as for function generators.
61+
62+
### Advantages of Generator Expressions
63+
64+
1. **Memory Efficiency**: Generator expressions produce values lazily, similar to function generators, resulting in memory savings.
65+
66+
2. **Lazy Evaluation**: Values are generated on-the-fly as they are consumed, providing improved performance and reduced overhead.
67+
68+
### Disadvantages of Generator Expressions
69+
70+
1. **Single Iteration**: Like function generators, once a generator expression is exhausted, it cannot be reused.
71+
72+
2. **Limited Random Access**: Generator expressions, similar to function generators, do not support random access.
73+
74+
### Implementation Example
75+
76+
```python
77+
# Generate squares of numbers from 0 to 9
78+
square_gen = (x**2 for x in range(10))
79+
80+
# Usage
81+
for num in square_gen:
82+
print(num)
83+
```
84+
85+
## Conclusion
86+
87+
Generators offer a powerful mechanism for creating iterators efficiently in Python. By understanding the differences between function generators and generator expressions, along with their mathematical formulation, advantages, and disadvantages, you can leverage them effectively in various scenarios. Whether you're dealing with large datasets or need to work with infinite sequences, generators provide a memory-efficient solution with lazy evaluation capabilities, contributing to more elegant and scalable code.

contrib/advanced-python/index.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
# List of sections
22

3-
- [OOPs](OOPs.md)
3+
- [OOPs](oops.md)
44
- [Decorators/\*args/**kwargs](decorator-kwargs-args.md)
5+
- ['itertools' module](itertools.md)
6+
- [Type Hinting](type-hinting.md)
57
- [Lambda Function](lambda-function.md)
68
- [Working with Dates & Times in Python](dates_and_times.md)
79
- [Regular Expressions in Python](regular_expressions.md)
810
- [JSON module](json-module.md)
911
- [Map Function](map-function.md)
1012
- [Protocols](protocols.md)
1113
- [Exception Handling in Python](exception-handling.md)
14+
- [Generators](generators.md)
15+
- [Match Case Statement](match-case.md)
16+
- [Closures](closures.md)
17+
- [Filter](filter-function.md)
18+
- [Reduce](reduce-function.md)
19+
- [List Comprehension](list-comprehension.md)
20+
- [Eval Function](eval_function.md)

0 commit comments

Comments
 (0)