Python coding
53 subscribers
24 files
11 links
​A technical hub dedicated to Python programming, web development, and software engineering. Learn core syntax, build full-stack projects, and master backend development.
Download Telegram
Python coding
🐍 Python Tip #82: The bisect Module Ever wanted to keep a list sorted without manually searching for where a new value should go? Python's bisect module can help. It uses binary search to find the correct position in an already-sorted list. --- Importing…
🐍 Python Tip #83: heapq

Need to quickly find the smallest or largest items in a collection?

Python's heapq module gives you a heap-based priority queue.

It's especially useful when you repeatedly need the smallest or largest values without sorting the entire list every time.

---

Importing heapq

import heapq

---

Creating a Min-Heap

A normal list can be turned into a heap using heapify().

import heapq

numbers = [7, 2, 9, 1, 5]

heapq.heapify(numbers)

print(numbers)

The smallest value is always at index 0.

print(numbers[0])

Output:

1

---

Removing the Smallest Item

Use heappop():

import heapq

numbers = [7, 2, 9, 1, 5]

heapq.heapify(numbers)

smallest = heapq.heappop(numbers)

print(smallest)
print(numbers)

Output:

1
[2, 5, 9, 7]

The heap structure isn't necessarily completely sorted.

It only guarantees that the smallest item is at the front.

---

Adding an Item

Use heappush():

import heapq

numbers = [2, 5, 7]

heapq.heapify(numbers)

heapq.heappush(numbers, 1)

print(numbers)

Output:

[1, 2, 7, 5]

---

Getting the Smallest Items

You can use nsmallest() when you only need a few of the smallest values.

import heapq

numbers = [15, 3, 27, 8, 12, 1]

result = heapq.nsmallest(3, numbers)

print(result)

Output:

[1, 3, 8]

You can also use nlargest():

result = heapq.nlargest(3, numbers)

print(result)

Output:

[27, 15, 12]

---

Real Example: Priority Tasks

import heapq

tasks = []

heapq.heappush(tasks, (2, "Send email"))
heapq.heappush(tasks, (1, "Fix critical bug"))
heapq.heappush(tasks, (3, "Update documentation"))

while tasks:
priority, task = heapq.heappop(tasks)
print(priority, task)

Output:

1 Fix critical bug
2 Send email
3 Update documentation

The smaller priority number gets processed first.

---

Why use heapq?

βœ“ Useful for priority queues
βœ“ Efficiently handles the smallest items
βœ“ Can find the largest/smallest N values
βœ“ Useful in scheduling and algorithms
βœ“ Common in real-world data processing

---

Challenge:

Given:

numbers = [42, 7, 19, 3, 25, 11]

Use heapq to:

1. Turn the list into a heap.
2. Remove the smallest value.
3. Add 5.
4. Print the smallest 3 values.

Try solving it without using sorted().
Python coding
🐍 Python Tip #83: heapq Need to quickly find the smallest or largest items in a collection? Python's heapq module gives you a heap-based priority queue. It's especially useful when you repeatedly need the smallest or largest values without sorting the entire…
🐍 Python Tip #84: collections.defaultdict

Ever needed a dictionary where missing keys should automatically get a default value?

That's exactly what defaultdict is useful for.

It's part of Python's collections module and can make grouping and counting data much cleaner.

---

Importing defaultdict

from collections import defaultdict

---

Basic Example

from collections import defaultdict

scores = defaultdict(int)

scores["Ali"] += 10
scores["Sara"] += 20

print(scores)

Output:

defaultdict(<class 'int'>, {'Ali': 10, 'Sara': 20})

With a normal dictionary, trying to modify a key that doesn't exist would require creating it first.

defaultdict(int) automatically starts missing values at 0.

---

Counting Items

One useful application is counting occurrences:

from collections import defaultdict

fruits = ["apple", "banana", "apple", "orange", "banana", "apple"]

counts = defaultdict(int)

for fruit in fruits:
counts[fruit] += 1

print(dict(counts))

Output:

{'apple': 3, 'banana': 2, 'orange': 1}

---

Creating Lists Automatically

You can also use list as the default factory.

from collections import defaultdict

students = defaultdict(list)

students["Class A"].append("Ali")
students["Class A"].append("Sara")
students["Class B"].append("John")

print(dict(students))

Output:

{'Class A': ['Ali', 'Sara'], 'Class B': ['John']}

You don't need to manually create an empty list for every new class.

---

Another Example: Grouping Data

from collections import defaultdict

products = [
("Laptop", "Electronics"),
("Phone", "Electronics"),
("Desk", "Furniture"),
("Chair", "Furniture")
]

categories = defaultdict(list)

for product, category in products:
categories[category].append(product)

print(dict(categories))

Output:

{
'Electronics': ['Laptop', 'Phone'],
'Furniture': ['Desk', 'Chair']
}

---

Why use defaultdict?

βœ“ Automatically handles missing keys
βœ“ Great for counting
βœ“ Useful for grouping data
βœ“ Makes dictionary-based code cleaner
βœ“ Part of Python's standard library

---

Challenge:

Given:

words = ["python", "java", "python", "c", "java", "python"]

Use defaultdict(int) to count how many times each programming language appears.

Expected result:

{'python': 3, 'java': 2, 'c': 1}
Python coding
🐍 Python Tip #84: collections.defaultdict Ever needed a dictionary where missing keys should automatically get a default value? That's exactly what defaultdict is useful for. It's part of Python's collections module and can make grouping and counting data…
🐍 Python Tip #85: functools.reduce()

Sometimes you have a list of values and want to repeatedly combine them into one final result.

That's where reduce() comes in.

It applies a function to the first two items, then uses the result with the next item, and keeps going until only one final value remains.

---

Importing reduce

from functools import reduce

---

Basic Example

from functools import reduce

numbers = [1, 2, 3, 4, 5]

total = reduce(lambda a, b: a + b, numbers)

print(total)

Output:

15

What's happening?

1 + 2 β†’ 3
3 + 3 β†’ 6
6 + 4 β†’ 10
10 + 5 β†’ 15

---

Multiplying Values

from functools import reduce

numbers = [2, 3, 4]

result = reduce(lambda a, b: a * b, numbers)

print(result)

Output:

24

Because:

2 Γ— 3 Γ— 4 = 24

---

Using a Normal Function

You don't have to use a lambda.

from functools import reduce

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

numbers = [10, 20, 30, 40]

result = reduce(add, numbers)

print(result)

Output:

100

---

Using an Initial Value

You can provide a starting value as the third argument.

from functools import reduce

numbers = [1, 2, 3]

result = reduce(
lambda a, b: a + b,
numbers,
10
)

print(result)

Output:

16

The calculation starts with:

10 + 1 + 2 + 3 = 16

---

When Should You Use reduce()?

It's useful when a sequence needs to be repeatedly combined into one value.

Examples include:

βœ“ Calculating products
βœ“ Combining values
βœ“ Building custom aggregation logic
βœ“ Functional programming
βœ“ Processing a sequence into one final result

For simple sums, though, sum() is usually clearer.

---

Challenge:

Given:

numbers = [2, 4, 6, 8]

Use reduce() to multiply all the numbers together.

Expected output:

384

Can you do it without using math.prod()?
Python coding
🐍 Python Tip #85: functools.reduce() Sometimes you have a list of values and want to repeatedly combine them into one final result. That's where reduce() comes in. It applies a function to the first two items, then uses the result with the next item, and…
🐍 Python Tip #86: functools.wraps()

When you create decorators in Python, there's a small problem that can easily be overlooked: the decorator can replace the original function's name and documentation.

functools.wraps() fixes that by preserving important information about the original function.

---

Importing wraps

from functools import wraps

---

The Problem Without wraps()

def my_decorator(func):
def wrapper():
print("Before function")
func()
return wrapper

@my_decorator
def greet():
"""Say hello to the user."""
print("Hello!")

print(greet.name)
print(greet.doc)

Output:

wrapper
None

The original function's metadata has been replaced by the wrapper's metadata.

---

Using wraps()

from functools import wraps

def my_decorator(func):
@wraps(func)
def wrapper():
print("Before function")
func()
return wrapper

@my_decorator
def greet():
"""Say hello to the user."""
print("Hello!")

print(greet.name)
print(greet.doc)

Output:

greet
Say hello to the user.

Now Python knows that the wrapped function is still greet.

---

Why Is This Useful?

Decorators are common in real Python projects.

They're used for things like:

βœ“ Logging
βœ“ Authentication
βœ“ Timing functions
βœ“ Caching
βœ“ Validation
βœ“ Permission checks
βœ“ Framework features

Using @wraps(func) makes your decorators behave more like the original functions.

---

A More Practical Example

from functools import wraps
import time

def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()

result = func(*args, **kwargs)

end = time.perf_counter()

print(f"{func.name} took {end - start:.6f} seconds")

return result

return wrapper

@timer
def calculate():
return sum(range(100000))

print(calculate())

The decorator measures the function's execution time while @wraps() preserves the original function's metadata.

---

Quick Rule

When writing your own decorators, a good habit is:

@wraps(func)

Put it directly above the wrapper function.

---

Challenge:

Create a decorator called logger that:

1. Prints "Function started" before running a function.
2. Runs the original function.
3. Prints "Function finished" afterward.
4. Uses @wraps(func).

Then decorate a function called hello() and check its __name__.

It should still print:

hello
Python coding
🐍 Python Tip #86: functools.wraps() When you create decorators in Python, there's a small problem that can easily be overlooked: the decorator can replace the original function's name and documentation. functools.wraps() fixes that by preserving important…
🐍 Python Tip #87: collections.ChainMap

Ever needed to work with multiple dictionaries as if they were one?

ChainMap from the collections module lets you combine multiple dictionaries into a single view without actually merging or copying them.

---

Importing ChainMap

from collections import ChainMap

---

Basic Example

from collections import ChainMap

defaults = {
"theme": "light",
"language": "English"
}

user_settings = {
"theme": "dark"
}

settings = ChainMap(user_settings, defaults)

print(settings["theme"])
print(settings["language"])

Output:

dark
English

Python checks the dictionaries from left to right.

So if a key exists in user_settings, that value is used first.

---

Why Is This Useful?

Imagine an application has:

1. User settings
2. Project settings
3. Default settings

Instead of copying everything into one dictionary, you can use:

settings = ChainMap(
user_settings,
project_settings,
defaults
)

If the user has a custom value, it takes priority.

If not, Python checks the next dictionary.

---

Adding a New Dictionary

You can add another mapping with new_child():

from collections import ChainMap

defaults = {"color": "blue"}
user = {"name": "Ali"}

settings = ChainMap(user, defaults)

temporary = settings.new_child(
{"color": "red"}
)

print(temporary["color"])

Output:

red

The new dictionary gets the highest priority.

---

Updating Values

If you change a value:

settings["name"] = "Sara"

the change is made in the first dictionary in the chain.

ChainMap doesn't merge everything into one new dictionary.

It keeps references to the original mappings.

---

Real-World Example

from collections import ChainMap

system_config = {
"debug": False,
"language": "English"
}

app_config = {
"debug": True
}

config = ChainMap(app_config, system_config)

print(config["debug"])
print(config["language"])

Output:

True
English

This pattern can be useful for configuration systems where different levels of settings override each other.

---

Why use ChainMap?

βœ“ Combines multiple dictionaries without copying them
βœ“ Useful for configuration systems
βœ“ Supports priority-based lookups
βœ“ Keeps the original dictionaries separate
βœ“ Useful when working with layered settings

---

Challenge:

Create three dictionaries:

defaults = {"theme": "light", "font": "Arial"}
user = {"theme": "dark"}
temporary = {"font": "Courier"}

Use ChainMap so that:

1. temporary has the highest priority.
2. user comes next.
3. defaults is the fallback.

Then print the values of:

theme
font

Expected output:

dark
Courier
Python coding
🐍 Python Tip #87: collections.ChainMap Ever needed to work with multiple dictionaries as if they were one? ChainMap from the collections module lets you combine multiple dictionaries into a single view without actually merging or copying them. --- Importing…
🐍 Python Tip #88: contextvars

Ever needed a variable whose value is local to the current task or execution context?

Python's contextvars module is especially useful in asynchronous applications where multiple tasks can be running at the same time.

---

Importing contextvars

import contextvars

---

Creating a Context Variable

import contextvars

user = contextvars.ContextVar("user")

user.set("Yehya")

print(user.get())

Output:

Yehya

ContextVar stores a value that belongs to the current execution context.

---

Using a Default Value

import contextvars

language = contextvars.ContextVar(
"language",
default="English"
)

print(language.get())

Output:

English

If you haven't assigned a value yet, the default is returned.

---

Changing the Value

import contextvars

username = contextvars.ContextVar(
"username",
default="Guest"
)

print(username.get())

username.set("Yehya")

print(username.get())

Output:

Guest
Yehya

---

Using ContextVar in Functions

import contextvars

current_user = contextvars.ContextVar(
"current_user",
default="Guest"
)

def show_user():
print("Current user:", current_user.get())

current_user.set("Ali")

show_user()

current_user.set("Sara")

show_user()

Output:

Current user: Ali
Current user: Sara

---

Why Is This Useful?

ContextVar is commonly useful in:

βœ“ Async applications
βœ“ Web servers
βœ“ Request-specific data
βœ“ Logging context
βœ“ User/session information
βœ“ Code where multiple tasks need separate values

For example, a web application might store a request ID in a ContextVar so different parts of the application can access it without passing the ID through every function.

---

Important Difference

A normal global variable is shared:

current_user = "Ali"

A ContextVar is designed so different execution contexts can have different values.

This becomes especially useful with asyncio and other concurrent code.

---

Challenge:

Create a ContextVar called:

current_user

Give it a default value of:

"Guest"

Then:

1. Print the default value.
2. Change it to "Yehya".
3. Create a function that prints the current user.
4. Call the function.

Expected output:

Guest
Yehya
Python coding
🐍 Python Tip #88: contextvars Ever needed a variable whose value is local to the current task or execution context? Python's contextvars module is especially useful in asynchronous applications where multiple tasks can be running at the same time. --- …
🐍 Python Tip #89: types.SimpleNamespace

Sometimes you have a few related values and want to access them with dot notation instead of dictionary brackets.

SimpleNamespace from Python's types module gives you a simple object for storing attributes.

---

Importing SimpleNamespace

from types import SimpleNamespace

---

Basic Example

from types import SimpleNamespace

user = SimpleNamespace(
name="Yehya",
age=16,
language="Python"
)

print(user.name)
print(user.age)
print(user.language)

Output:

Yehya
16
Python

Instead of:

user["name"]

you can use:

user.name

---

Changing Values

The attributes can be changed normally:

user.age = 17

print(user.age)

Output:

17

You can also add new attributes:

user.country = "Ethiopia"

print(user.country)

Output:

Ethiopia

---

Creating One From a Dictionary

You can also convert dictionary data into a namespace:

from types import SimpleNamespace

data = {
"name": "Sara",
"score": 95
}

student = SimpleNamespace(**data)

print(student.name)
print(student.score)

Output:

Sara
95

---

Converting Back to a Dictionary

Use vars():

data = vars(student)

print(data)

Output:

{'name': 'Sara', 'score': 95}

---

When Is It Useful?

SimpleNamespace can be handy for:

βœ“ Small data containers
βœ“ Configuration objects
βœ“ Temporary structured data
βœ“ Making dictionary data easier to access
βœ“ Quick prototypes

It's not a replacement for dataclass when you need a more structured data model.

---

Real Example

from types import SimpleNamespace

server = SimpleNamespace(
host="localhost",
port=8000,
debug=True
)

print(f"Server: {server.host}:{server.port}")
print("Debug:", server.debug)

Output:

Server: localhost:8000
Debug: True

---

Challenge:

Create a SimpleNamespace called student with:

name = "Yehya"
grade = 11
subject = "Python"

Then:

1. Print the student's name.
2. Change the grade to 12.
3. Add a new attribute called score.
4. Print all the information.

Can you do it without using a dictionary?
Python coding
🐍 Python Tip #89: types.SimpleNamespace Sometimes you have a few related values and want to access them with dot notation instead of dictionary brackets. SimpleNamespace from Python's types module gives you a simple object for storing attributes. --- Importing…
🐍 Python Tip #90: functools.cache()

If a function keeps receiving the same arguments and doing the same expensive calculation again and again, caching can save a lot of work.

Python's functools.cache() automatically remembers previous results and reuses them when the same arguments appear again.

---

Importing cache

from functools import cache

---

Basic Example

from functools import cache

@cache
def square(number):
print("Calculating...")
return number ** 2

print(square(5))
print(square(5))
print(square(10))

Output:

Calculating...
25
25
Calculating...
100

Notice that "Calculating..." only appears once for 5.

The second square(5) gets the stored result instead of running the function again.

---

A More Useful Example

from functools import cache

@cache
def fibonacci(n):
if n < 2:
return n

return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))

Output:

55

Without caching, recursive Fibonacci calculations repeat many of the same calculations.

With @cache, previously calculated results are reused.

---

Checking the Cache

You can inspect the cache with:

fibonacci.cache_info()

For example, you might see:

CacheInfo(hits=8, misses=11, maxsize=None, currsize=11)

hits = results retrieved from the cache.

misses = calculations that had to actually run.

currsize = number of stored results.

---

Clearing the Cache

If you want to remove all stored results:

fibonacci.cache_clear()

After that, the function will calculate the results again when called.

---

Important Note

@cache is basically an unlimited cache.

That means stored results can continue growing as new arguments are used.

If you need a limited cache size, functools.lru_cache() is usually more appropriate.

---

Why use cache()?

βœ“ Avoids repeating expensive calculations
βœ“ Extremely useful with recursive functions
βœ“ Simple to add with @cache
βœ“ Can make repeated function calls much faster
βœ“ Part of Python's standard library

---

Challenge:

Create a cached function:

@cache
def factorial(n):

It should calculate:

5! = 120

Then call:

factorial(5)
factorial(5)
factorial(6)

Finally, use:

factorial.cache_info()

and check how many cache hits you get.