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 #58: The tempfile Module The tempfile module allows you to create temporary files and directories that are automatically cleaned up when no longer needed. It is useful for testing, data processing, and handling temporary data. --- Importing…
💡 Python Tip #59: The shutil Module

The shutil module provides high-level file and directory operations.

It makes it easy to copy, move, rename, and delete files and folders.

---

Importing shutil

import shutil

---

Copying a File

import shutil

shutil.copy("source.txt", "backup.txt")

print("File copied successfully.")

This creates a copy of source.txt named backup.txt.

---

Moving or Renaming a File

import shutil

shutil.move("old_file.txt", "new_file.txt")

print("File moved or renamed.")

If the destination is in another folder, the file is moved. If it's in the same folder with a different name, it is renamed.

---

Copying an Entire Directory

import shutil

shutil.copytree("Project", "Project_Backup")

print("Directory copied.")

This copies the entire Project folder, including all its files and subfolders.

---

Deleting a Directory

import shutil

shutil.rmtree("Old_Project")

print("Directory deleted.")

Warning: rmtree() permanently deletes the directory and everything inside it.

---

Getting Disk Usage

import shutil

usage = shutil.disk_usage(".")

print(f"Total: {usage.total} bytes")
print(f"Used: {usage.used} bytes")
print(f"Free: {usage.free} bytes")

Example Output:

Total: 512110190592 bytes
Used: 301245964288 bytes
Free: 210864226304 bytes

---

Why use shutil?

✓ Copy, move, and rename files easily
✓ Copy or remove entire directories
✓ Check disk usage information
✓ Commonly used in backup tools, automation scripts, and file management applications

---

Challenge:

Create a program that:

1. Creates a copy of a file named:

notes.txt

2. Rename the copied file to:

notes_backup.txt

3. Print a success message after each operation.
Python coding
💡 Python Tip #59: The shutil Module The shutil module provides high-level file and directory operations. It makes it easy to copy, move, rename, and delete files and folders. --- Importing shutil import shutil --- Copying a File import shutil shut…
🚀 Python Tip #60: The contextlib Module

The contextlib module provides utilities for working with context managers.

It helps you manage resources such as files, database connections, and locks safely and cleanly.

---

Importing contextmanager

from contextlib import contextmanager

---

Creating a Custom Context Manager

from contextlib import contextmanager

@contextmanager
def my_context():
print("Entering context...")
yield
print("Exiting context...")

with my_context():
print("Inside the block")

Output:

Entering context...
Inside the block
Exiting context...

The code before yield runs when entering the block, and the code after yield runs when exiting it.

---

Managing Resources

from contextlib import contextmanager

@contextmanager
def open_file(filename):
file = open(filename, "w")
try:
yield file
finally:
file.close()

with open_file("example.txt") as f:
f.write("Hello, Python!")

The file is automatically closed after leaving the with block.

---

Ignoring Exceptions

Use suppress() to ignore specific exceptions.

from contextlib import suppress

with suppress(FileNotFoundError):
open("missing_file.txt")

If the file doesn't exist, the program continues without raising an error.

---

Redirecting Output

You can temporarily redirect printed output.

from contextlib import redirect_stdout

with open("output.txt", "w") as file:
with redirect_stdout(file):
print("Hello, Python!")

The text is written to output.txt instead of appearing in the terminal.

---

Why use contextlib?

✓ Create custom context managers easily
✓ Automatically manage resources
✓ Simplify exception handling
✓ Commonly used in file handling, testing, logging, and resource management

---

Challenge:

Create a custom context manager that:

1. Prints:

Starting task...

before entering the with block.

2. Prints:

Task completed.

after leaving the with block.

3. Inside the with block, print:

Learning Python Context Managers!
Python coding pinned «Introduction to Programming with Python --- What is Python? Python is a popular programming language created by Guido van Rossum in 1991. It is known for: - Simplicity - Readability - Flexibility - Powerful libraries Python is beginner-friendly while…»
Python coding
🚀 Python Tip #60: The contextlib Module The contextlib module provides utilities for working with context managers. It helps you manage resources such as files, database connections, and locks safely and cleanly. --- Importing contextmanager from contextlib…
🐍 Python Tip #61: The functools.singledispatch Decorator

The singledispatch decorator from Python's functools module allows a function to behave differently depending on the type of its first argument.

This is known as single dispatch or function overloading by type.

---

Importing singledispatch

from functools import singledispatch

---

Basic Example

from functools import singledispatch

@singledispatch
def show(value):
print("Unsupported type")

show(10)

Output:

Unsupported type

---

Registering Functions for Different Types

from functools import singledispatch

@singledispatch
def show(value):
print("Unsupported type")

@show.register
def _(value: int):
print(f"Integer: {value}")

@show.register
def _(value: str):
print(f"String: {value}")

show(100)
show("Python")

Output:

Integer: 100
String: Python

The correct function is selected automatically based on the argument type.

---

Supporting Multiple Types

from functools import singledispatch

@singledispatch
def describe(value):
print("Unknown type")

@describe.register
def _(value: list):
print(f"List with {len(value)} items")

@describe.register
def _(value: dict):
print(f"Dictionary with {len(value)} keys")

describe([1, 2, 3])
describe({"a": 1, "b": 2})

Output:

List with 3 items
Dictionary with 2 keys

---

Fallback Behavior

If no registered type matches, the original function is used.

describe(3.14)

Output:

Unknown type

---

Why use singledispatch?

✓ Organizes type-specific logic cleanly
✓ Avoids long if isinstance() chains
✓ Easy to extend by registering new types
✓ Useful in APIs, data processing, and reusable libraries

---

Challenge:

Create a function named:

display()

using `@singledispatch.

Register implementations for:

-
int
-
str
-
tuple`

Each implementation should print a different message describing the received value.
Python coding
🐍 Python Tip #61: The functools.singledispatch Decorator The singledispatch decorator from Python's functools module allows a function to behave differently depending on the type of its first argument. This is known as single dispatch or function overloading…
🚀 Python Tip #62: The functools.lru_cache Decorator

The lru_cache decorator from Python's functools module stores the results of function calls.

If the function is called again with the same arguments, the cached result is returned instantly instead of recalculating it.

This technique is called memoization.

---

Importing lru_cache

from functools import lru_cache

---

Basic Example

from functools import lru_cache

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

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

Output:

Calculating...
25
25

Notice that "Calculating..." is printed only once because the second call uses the cached result.

---

Speeding Up Recursive Functions

Without caching:

def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)

With caching:

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(40))

The cached version is dramatically faster because previously computed values are reused.

---

Limiting Cache Size

from functools import lru_cache

@lru_cache(maxsize=5)
def cube(number):
print("Computing...")
return number ** 3

cube(2)
cube(3)
cube(2)

Only the most recently used results are kept in the cache.

---

Clearing the Cache

from functools import lru_cache

@lru_cache
def multiply(a, b):
return a * b

multiply(2, 3)

multiply.cache_clear()

This removes all cached results.

---

Why use lru_cache?

✓ Improves performance by avoiding repeated calculations
✓ Perfect for recursive algorithms like Fibonacci
✓ Easy to use with a single decorator
✓ Commonly used in dynamic programming, APIs, and computationally expensive functions

---

Challenge:

Create a recursive function that calculates the factorial of a number.

Then:

1. Decorate it with:

@lru_cache(maxsize=None)

2. Calculate:

factorial(10)

3. Call it a second time and observe that the cached result is returned instantly.
Python coding
🚀 Python Tip #62: The functools.lru_cache Decorator The lru_cache decorator from Python's functools module stores the results of function calls. If the function is called again with the same arguments, the cached result is returned instantly instead of recalculating…
💡 Python Tip #63: The functools.cached_property Decorator

The cached_property decorator from Python's functools module computes a property only once.

After the first access, the computed value is stored (cached), so future accesses return the cached result instead of recalculating it.

---

Importing cached_property

from functools import cached_property

---

Basic Example

from functools import cached_property

class Circle:
def __init__(self, radius):
self.radius = radius

@cached_property
def area(self):
print("Calculating area...")
return 3.14159 * self.radius ** 2

circle = Circle(5)

print(circle.area)
print(circle.area)

Output:

Calculating area...
78.53975
78.53975

Notice that "Calculating area..." is printed only once.

---

Without cached_property

class Circle:
def __init__(self, radius):
self.radius = radius

@property
def area(self):
print("Calculating area...")
return 3.14159 * self.radius ** 2

circle = Circle(5)

print(circle.area)
print(circle.area)

Output:

Calculating area...
78.53975
Calculating area...
78.53975

The calculation is performed every time the property is accessed.

---

Clearing the Cached Value

You can remove the cached value using del.

from functools import cached_property

class Square:
def __init__(self, side):
self.side = side

@cached_property
def area(self):
print("Calculating...")
return self.side ** 2

square = Square(4)

print(square.area)

del square.area

print(square.area)

Output:

Calculating...
16
Calculating...
16

After deleting the cached value, it is calculated again the next time it is accessed.

---

Why use cached_property?

✓ Improves performance for expensive calculations
✓ Computes a value only once per object
✓ Keeps code clean and readable
✓ Commonly used in data science, web frameworks, and applications with computationally expensive properties

---

Challenge:

Create a class named:

Rectangle

with the attributes:

width
height

Then:

1. Create a cached_property named:

area

2. Print the area twice.
3. Verify that the calculation occurs only once.
Python coding
💡 Python Tip #63: The functools.cached_property Decorator The cached_property decorator from Python's functools module computes a property only once. After the first access, the computed value is stored (cached), so future accesses return the cached result…
🚀 Python Tip #64: The typing.TypedDict Class

The TypedDict class from Python's typing module lets you define dictionaries with specific keys and value types.

It improves code readability and helps static type checkers detect errors.

---

Importing TypedDict

from typing import TypedDict

---

Creating a TypedDict

from typing import TypedDict

class Student(TypedDict):
name: str
age: int
grade: str

student: Student = {
"name": "Ali",
"age": 18,
"grade": "A"
}

print(student)

Output:

{
'name': 'Ali',
'age': 18,
'grade': 'A'
}

---

Accessing Values

from typing import TypedDict

class Employee(TypedDict):
name: str
salary: float

employee: Employee = {
"name": "John",
"salary": 4500.0
}

print(employee["name"])
print(employee["salary"])

Output:

John
4500.0

---

Optional Keys

You can make keys optional using total=False.

from typing import TypedDict

class User(TypedDict, total=False):
name: str
age: int
city: str

user: User = {
"name": "Yehya"
}

print(user)

Output:

{
'name': 'Yehya'
}

The `age and city keys are optional.

---

Why use
TypedDict?

Without
TypedDict:

``
python
student = {
"name": "Ali",
"age": 18
}

There's no indication of the expected structure.

With `TypedDict`, the expected keys and value types are clearly defined, making your code easier to understand and maintain.

---

Why use `TypedDict`?

✓ Defines structured dictionaries with type hints
✓ Improves code readability
✓ Helps static type checkers catch mistakes
✓ Commonly used in APIs, configuration files, and JSON-like data structures

---

Challenge:

Create a `TypedDict` named:

python
Book

with the following fields:

python
title: str
author: str
pages: int
price: float

Then:

1. Create a `Book` dictionary.
2. Print each value.
3. Add an optional field named:

python
publisher
`

using `total=False`.
Python coding
🚀 Python Tip #64: The typing.TypedDict Class The TypedDict class from Python's typing module lets you define dictionaries with specific keys and value types. It improves code readability and helps static type checkers detect errors. --- Importing TypedDict…
🐍 Python Tip #65: The typing.Protocol Class

The Protocol class from Python's typing module allows you to define a set of required methods and attributes without requiring inheritance.

This is known as structural typing or duck typing.

---

Importing Protocol

from typing import Protocol

---

Creating a Protocol

from typing import Protocol

class Speaker(Protocol):
def speak(self) -> str:
...

The `Speaker protocol requires a speak() method that returns a string.

---

Implementing the Protocol

``
python
from typing import Protocol

class Speaker(Protocol):
def speak(self) -> str:
...

class Person:
def speak(self) -> str:
return "Hello!"

person = Person()

print(person.speak())

Output:

python
Hello!

Notice that `Person` does **not** inherit from `Speaker`, but it still satisfies the protocol because it implements the required method.

---

Using Protocol in Functions

python
from typing import Protocol

class Animal(Protocol):
def sound(self) -> str:
...

class Dog:
def sound(self):
return "Woof!"

class Cat:
def sound(self):
return "Meow!"

def make_sound(animal: Animal):
print(animal.sound())

make_sound(Dog())
make_sound(Cat())

Output:

python
Woof!
Meow!

Both classes work because they provide the required `sound()` method.

---

Why use `Protocol`?

✓ Enables structural typing (duck typing)
✓ No need for explicit inheritance
✓ Makes code more flexible and reusable
✓ Commonly used in libraries, frameworks, APIs, and large Python projects

---

Challenge:

Create a protocol named:

python
Drawable

with a method:

python
draw()

Then:

1. Create two classes:

python
Circle
Rectangle
`

2. Implement the `draw()` method in both classes.
3. Write a function that accepts any `Drawable` object and calls its `draw()` method.
Python coding
🐍 Python Tip #65: The typing.Protocol Class The Protocol class from Python's typing module allows you to define a set of required methods and attributes without requiring inheritance. This is known as structural typing or duck typing. --- Importing Protocol…
🚀 Python Tip #66: The typing.Literal Type

The Literal type from Python's typing module allows you to restrict a variable or function parameter to specific, predefined values.

This improves type safety and makes your code easier to understand.

---

Importing Literal

from typing import Literal

---

Basic Example

from typing import Literal

Status = Literal["pending", "running", "completed"]

status: Status = "running"

print(status)

Output:

running

The variable can only have one of the specified values.

---

Using Literal in Functions

from typing import Literal

def set_theme(
theme: Literal["light", "dark"]
):
print(f"Theme: {theme}")

set_theme("dark")

Output:

Theme: dark

Passing any other value would be considered a type error by static type checkers.

---

Restricting Numeric Values

from typing import Literal

def roll_dice(
sides: Literal[4, 6, 8, 10, 12, 20]
):
print(f"Rolling a {sides}-sided dice.")

roll_dice(20)

Output:

Rolling a 20-sided dice.

---

Using Multiple Literal Values

from typing import Literal

Direction = Literal[
"north",
"south",
"east",
"west"
]

def move(direction: Direction):
print(f"Moving {direction}")

move("north")

Output:

Moving north

---

Why use Literal?

✓ Restricts values to predefined choices
✓ Improves code readability and maintainability
✓ Helps static type checkers detect invalid values
✓ Commonly used in APIs, configuration options, and application settings

---

Challenge:

Create a function named:

set_language()

The function should accept only these values:

"English"
"French"
"Spanish"

using `Literal.

Then call the function with:

``
python
"English"
`

and print the selected language.
Python coding
🚀 Python Tip #66: The typing.Literal Type The Literal type from Python's typing module allows you to restrict a variable or function parameter to specific, predefined values. This improves type safety and makes your code easier to understand. --- Importing…
💡 Python Tip #67: The typing.Final Type

The Final type from Python's typing module indicates that a variable, constant, or method should not be reassigned or overridden.

While Python does not enforce this at runtime, static type checkers can detect violations.

---

Importing Final

from typing import Final

---

Declaring a Constant

from typing import Final

PI: Final = 3.14159

print(PI)

Output:

3.14159

The value of PI is intended to remain unchanged.

---

Using Final with Variables

from typing import Final

MAX_USERS: Final = 100

print(MAX_USERS)

Output:

100

Attempting to reassign it:

MAX_USERS = 200

A static type checker will report this as an error.

---

Using Final in Classes

from typing import Final

class Configuration:
VERSION: Final = "1.0.0"

print(Configuration.VERSION)

Output:

1.0.0

The `VERSION constant is intended to remain fixed.

---

Using
Final for Methods

``
python
from typing import Final

class Animal:
@Final
def speak(self):
print("Animal sound")

A subclass should not override the `speak()` method.

---

Why use `Final`?

✓ Clearly marks constants and fixed values
✓ Helps prevent accidental reassignment
✓ Improves code readability and maintainability
✓ Commonly used for configuration values, constants, and methods that should not be overridden

---

Challenge:

Create a program that:

1. Declares a constant named:

python
APP_NAME

using `Final`.

2. Assign it the value:

python
"Python Learning Hub"
`

3. Print the constant.

4. Try to assign a new value to it and observe the warning from your static type checker.
Python coding
💡 Python Tip #67: The typing.Final Type The Final type from Python's typing module indicates that a variable, constant, or method should not be reassigned or overridden. While Python does not enforce this at runtime, static type checkers can detect violations.…
🚀 Python Tip #68: The typing.Annotated Type

The Annotated type from Python's typing module allows you to attach additional metadata to type hints.

This metadata can be used by frameworks, libraries, documentation tools, or static analysis tools without changing the underlying type.

---

Importing Annotated

from typing import Annotated

---

Basic Example

from typing import Annotated

age: Annotated[int, "Age must be between 0 and 120"] = 16

print(age)

Output:

16

The variable is still an int, but it now includes extra descriptive metadata.

---

Using Annotated in Functions

from typing import Annotated

def greet(
name: Annotated[str, "User's full name"]
):
print(f"Hello, {name}!")

greet("Yehya")

Output:

Hello, Yehya!

---

Using Multiple Metadata Values

You can attach more than one piece of metadata.

from typing import Annotated

temperature: Annotated[
float,
"Measured in Celsius",
"Range: -50 to 100"
] = 26.5

print(temperature)

Output:

26.5

---

Combining with Other Type Hints

from typing import Annotated

scores: list[
Annotated[int, "Exam score"]
] = [85, 92, 78]

print(scores)

Output:

[85, 92, 78]

The list contains integers, while the metadata describes what those integers represent.

---

Why use Annotated?

✓ Adds metadata without changing the original type
✓ Improves documentation and code readability
✓ Useful for validation frameworks and APIs
✓ Commonly used in FastAPI, Pydantic, and modern Python applications

---

Challenge:

Create a function named:

register_user()

The function should accept:

username
age

Use `Annotated to add the following descriptions:

- Username of the new user
- User's age in years

Then call the function with:

``
python
username = "Yehya"
age = 16
`
Python coding
🚀 Python Tip #68: The typing.Annotated Type The Annotated type from Python's typing module allows you to attach additional metadata to type hints. This metadata can be used by frameworks, libraries, documentation tools, or static analysis tools without changing…
🐍 Python Tip #69: The typing.NewType Function

The NewType function from Python's typing module creates a distinct type based on an existing type.

It helps prevent mixing different values that share the same underlying type, such as int or str.

---

Importing NewType

from typing import NewType

---

Creating a New Type

from typing import NewType

UserId = NewType("UserId", int)

user_id = UserId(101)

print(user_id)

Output:

101

Although UserId behaves like an int at runtime, static type checkers treat it as a separate type.

---

Using NewType in Functions

from typing import NewType

UserId = NewType("UserId", int)

def get_user(user_id: UserId):
print(f"User ID: {user_id}")

get_user(UserId(15))

Output:

User ID: 15

This makes the function's purpose clearer and helps prevent passing the wrong type.

---

Creating Multiple New Types

from typing import NewType

UserId = NewType("UserId", int)
ProductId = NewType("ProductId", int)

user = UserId(1)
product = ProductId(1)

print(user)
print(product)

Output:

1
1

Even though both are based on int, type checkers recognize them as different types.

---

Why use NewType?

✓ Creates meaningful custom types
✓ Prevents mixing logically different values
✓ Improves code readability and type safety
✓ Commonly used in APIs, databases, and large Python applications

---

Challenge:

Create two new types:

StudentId
TeacherId

Both should be based on int.

Then:

1. Create one value for each type.
2. Print both values.
3. Write a function that accepts only a StudentId and prints it.
`
Python coding
🐍 Python Tip #69: The typing.NewType Function The NewType function from Python's typing module creates a distinct type based on an existing type. It helps prevent mixing different values that share the same underlying type, such as int or str. --- Importing…
🚀 Python Tip #70: The operator Module

The operator module provides functions that correspond to Python's built-in operators.

It is especially useful when working with functions such as map(), filter(), and sorted().

---

Importing operator

import operator

---

Basic Example

import operator

print(operator.add(10, 5))
print(operator.sub(10, 5))
print(operator.mul(10, 5))
print(operator.truediv(10, 5))

Output:

15
5
50
2.0

These functions are equivalent to:

10 + 5
10 - 5
10 * 5
10 / 5

---

Using operator with map()

import operator

numbers = [1, 2, 3, 4]

result = list(map(operator.mul, numbers, [2, 2, 2, 2]))

print(result)

Output:

[2, 4, 6, 8]

---

Using operator.itemgetter()

itemgetter() is useful for sorting lists of dictionaries or tuples.

from operator import itemgetter

students = [
{"name": "Ali", "score": 85},
{"name": "Sara", "score": 95},
{"name": "John", "score": 78}
]

students.sort(key=itemgetter("score"))

print(students)

Output:

[
{'name': 'John', 'score': 78},
{'name': 'Ali', 'score': 85},
{'name': 'Sara', 'score': 95}
]

---

Sorting in Reverse

from operator import itemgetter

students.sort(
key=itemgetter("score"),
reverse=True
)

print(students)

Output:

[
{'name': 'Sara', 'score': 95},
{'name': 'Ali', 'score': 85},
{'name': 'John', 'score': 78}
]

---

Why use operator?

✓ Provides functions for common operators
✓ Works perfectly with functional programming tools
✓ Makes sorting complex data easier
✓ Commonly used with map(), sorted(), filter(), and data processing

---

Challenge:

Given:

students = [
{"name": "Ali", "score": 82},
{"name": "Sara", "score": 96},
{"name": "John", "score": 74},
{"name": "Mariam", "score": 89}
]

Use:

```python
operator.itemgetter()

to sort the students by their score from highest to lowest.

Expected order:

text
Sara
Mariam
Ali
John
`