Python Programming
40 subscribers
5 photos
2 links
🐍 Python Programming | Code. Learn. Grow.

Welcome to Python Programming — your go-to Telegram community for mastering Python, from basics to advanced!
Download Telegram
#!/usr/bin/env python3
import argparse, json, sys
from collections import defaultdict
from datetime import date, datetime, timedelta
from pathlib import Path

# ── Optional fancy output ─────────────────────────────────────────────────────
try:
from rich.table import Table
from rich.console import Console
console = Console()
def fancy_report(stats):
tbl = Table(title="📊 Habit Summary")
tbl.add_column("Habit")
tbl.add_column("Streak", justify="right")
tbl.add_column("Done / Total", justify="right")
for h, (streak, done, total) in stats.items():
tbl.add_row(h, str(streak), f"{done} / {total}")
console.print(tbl)
except Exception: # rich not installed – fall back to plain prints
console = None
def fancy_report(stats):
for h, (streak, done, total) in stats.items():
print(f"{h:20} Streak: {streak:2} Done/Total: {done}/{total}")

# ── Helper functions ────────────────────────────────────────────────────────
DATA_FILE = Path(__file__).with_name("habits.json")

def load_data() -> defaultdict:
if DATA_FILE.exists():
with DATA_FILE.open() as f:
raw = json.load(f)
# Convert nested dicts back to defaultdicts for auto‑creation
return defaultdict(lambda: defaultdict(bool), {
habit: defaultdict(bool, entries) for habit, entries in raw.items()
})
return defaultdict(lambda: defaultdict(bool))

def save_data(data: defaultdict):
# Turn defaultdicts into normal dicts for JSON serialisation
serialisable = {h: dict(days) for h, days in data.items()}
DATA_FILE.write_text(json.dumps(serialisable, indent=2, sort_keys=True))

def mark_done(data, habit, when):
iso = when.isoformat()
data[habit][iso] = True
print(f"✅ Marked '{habit}' as done on {iso}")

def streak_for(habit_days):
"""Longest consecutive streak ending today."""
today = date.today()
streak = 0
while habit_days.get(today.isoformat()):
streak += 1
today -= timedelta(days=1)
return streak

def generate_report(data):
stats = {}
for habit, days in data.items():
total = len(days)
done = sum(1 for v in days.values() if v)
streak = streak_for(days)
stats[habit] = (streak, done, total)
fancy_report(stats)

# ── CLI handling ───────────────────────────────────────────────────────────
parser = argparse.ArgumentParser(
description="🗓️ Simple command‑line habit tracker"
)
sub = parser.add_subparsers(dest="cmd", required=True)

add_p = sub.add_parser("add", help="Register a new habit")
add_p.add_argument("habit", help="Name of the habit")

done_p = sub.add_parser("done", help="Mark habit as completed today")
done_p.add_argument("habit", help="Habit to mark")
done_p.add_argument(
"-d", "--date",
type=lambda s: datetime.strptime(s, "%Y-%m-%d").date(),
default=date.today(),
help="Date in YYYY‑MM‑DD (default: today)"
)

sub.add_parser("report", help="Show a summary table")

args = parser.parse_args()
db = load_data()

if args.cmd == "add":
if args.habit in db:
sys.exit(f"⚠️ Habit '{args.habit}' already exists.")
db[args.habit] # default dict creates empty entry
print(f"🆕 Added habit: {args.habit}")

elif args.cmd == "done":
if args.habit not in db:
sys.exit(f"❓ Unknown habit '{args.habit}'. Add it first.")
mark_done(db, args.habit, args.date)

elif args.cmd == "report":
generate_report(db)

save_data(db)


What’s happening under the hood?

* defaultdict removes the need for “if habit not in data: data[habit] = {}” – the container auto‑creates an empty dict the moment you access a new key.
*
Hey Python newbies! 🌱
Ever felt like you’re “walking blind” through a list and wish you could see where you are at each step? Let’s fix that with a tiny, super‑useful tool called enumerate.

🎯 What problem does it solve?

You have a list and you want two things at the same time:

1. The item itself (e.g., a word, a number, a name).
2. The position of that item (its index – the “0, 1, 2…” number).

The classic way is to use a counter variable and manually increase it:

i = 0
for fruit in fruits:
print(i, fruit)
i += 1


That works, but it’s a bit clunky and easy to forget the i += 1 line.

🛠️ Enter enumerate

enumerate is a built‑in function that automatically pairs each item with its index. It returns an iterator of tuples (index, item).

Why it works:
Under the hood, enumerate creates a tiny object that keeps an internal counter. Each time the loop asks for the next element, it returns the current counter value and the next item from the original iterable, then bumps the counter by one. No extra variables needed!

📚 How to use it

fruits = ["apple", "banana", "cherry", "date"]

for idx, fruit in enumerate(fruits):
print(f"Item {idx}: {fruit}")


Output

Item 0: apple
Item 1: banana
Item 2: cherry
Item 3: date


Key points

- idx (or any name you like) receives the index.
- fruit receives the actual list element.
- The index starts at 0 by default – that’s Python’s way of counting from zero.

If you’d rather start counting from 1 (maybe for user‑facing messages), just give enumerate a second argument:

for idx, fruit in enumerate(fruits, start=1):
print(f"{idx}. {fruit}")


Output:

1. apple
2. banana
3. cherry
4. date


🚀 When is enumerate a game‑changer?

- Finding the position of a specific value – you can break out of the loop as soon as you spot what you need.
- Creating numbered menus – perfect for command‑line interfaces.
- Debugging – quickly see “which element broke my code” without extra prints.

📌 Quick cheat‑sheet

- Syntax: enumerate(iterable, start=0)
- Returns: an iterator of (index, item) tuples
- Common use: for i, x in enumerate(some_list): …

Give it a try!
Take any list you already have (numbers, strings, even a list of dictionaries) and rewrite the loop with enumerate. You’ll see how clean and readable your code becomes.

Happy coding, and remember: small helpers like enumerate make big differences. 🚀✨
🔍 Dive Deep: Context Variables — the secret sauce for clean async state management

When you start mixing asyncio tasks, you quickly notice a recurring pain point: how do you keep per‑task state without leaking it into other coroutines?
The usual suspects – globals, thread‑locals, or even passing arguments around – either break the async model or make the code noisy.

Enter contextvars (Python 3.7+). It gives you task‑local storage that propagates automatically across await points, while staying isolated between concurrent tasks. Let’s unpack why it works, and how to wield it like a pro.

📚 The why – what makes contextvars tick?

1. Isolation at the coroutine level
A Context is a snapshot of all active ContextVar values. Each Task holds its own Context, created from the parent’s snapshot at the moment the task is scheduled. This means any change a task makes to a variable stays local to that task.

2. Transparent propagation through await
When you await a coroutine, the interpreter automatically switches to the awaiting task’s Context. No manual plumbing required – the same variable you read/write before the await will resolve to the same value after it.

3. Explicit reset & token semantics
Setting a ContextVar returns a token that lets you roll back the change (var.reset(token)). This is crucial for temporary overrides (think request‑scoped data in a web server) without polluting the surrounding context.

4. Compatibility with asyncio primitives
TaskGroup, gather, create_task, and even run_in_executor respect the current Context, so your state travels safely across thread pools as well.

⚙️ The how – practical patterns

1️⃣ Basic usage – a request‑id logger

import asyncio
import contextvars
import random
import time

# A ContextVar that will hold a unique request identifier
request_id = contextvars.ContextVar("request_id", default="unknown")

def log(msg: str):
"""Simple logger that prefixes the current request_id."""
rid = request_id.get()
print(f"[{time.strftime('%X')}] [{rid}] {msg}")

async def handler(name: str):
# Assign a fresh id for this handler – isolated per task
token = request_id.set(f"{name}-{random.randint(1000, 9999)}")
log("started")
await asyncio.sleep(random.random() * 0.3)
log("still running")
# Clean up – optional but good practice if you reuse the same ContextVar elsewhere
request_id.reset(token)
log("finished")

async def main():
await asyncio.gather(handler("alpha"), handler("beta"), handler("gamma"))

asyncio.run(main())


Why it works:
- Each handler gets its own snapshot of request_id.
- The set call only affects the current task’s Context; other tasks keep their own values.
- When we reset with the token, we restore the previous value (or the default), preventing accidental bleed‑through if the same coroutine is reused later.

2️⃣ Propagating context into a thread pool

import concurrent.futures
import contextvars

user = contextvars.ContextVar("user", default="anonymous")

def blocking_io():
# Even though we’re on a thread, the current Context is copied automatically
return f"processing for {user.get()}"

async def main():
user.set("alice")
loop = asyncio.get_running_loop()
with concurrent.futures.ThreadPoolExecutor() as pool:
result = await loop.run_in_executor(pool, blocking_io)
print(result) # → processing for alice


Why it works:
run_in_executor captures the current Context and injects it into the worker thread. The thread sees the same user value, but other threads get their own copies, preserving isolation.

3️⃣ Temporary overrides with ContextVar.run

(continued below 👇)
Sometimes you need a one‑off context change without messing with tokens:

def compute():
return f"value is {request_id.get()}"

# Run compute() with a temporary request_id
print(request_id.run(lambda: "temp-42", compute))
# → value is temp-42


ContextVar.run creates a new Context where the variable is set to the supplied value, executes the callable, then discards that Context. It’s a clean, functional‑style alternative to manual token handling.

🛡️ Common pitfalls & how to avoid them

- Never store mutable objects directly in a ContextVar unless you intend to share them.
The Context only copies the reference; mutating the object will be seen by every task that holds the same reference. Use immutable types (str, int, tuple) or deep‑copy if needed.

- Don’t forget the default!
If you access a ContextVar before any set, you’ll get a LookupError. Supplying a default= value sidesteps the exception and makes the variable safely readable everywhere.

- Be wary of contextvars.copy_context() in libraries.
Some third‑party tools (e.g., tracing libraries) clone the Context manually. If you rely on side‑effects inside those clones, you might get surprising divergence. Stick to the high‑level API (set, reset, run) unless you truly need a snapshot.

🎯 TL;DR – when to reach for contextvars

- Per‑request or per‑task metadata (request IDs, user sessions, locale) in async servers.
- Thread‑pool boundaries where you still need logical request scoping.
- Temporary overrides for testing or sandboxed execution.

Bottom line: contextvars gives you structured, leak‑free state that travels with your async flow, without the boilerplate of passing arguments everywhere. Master it, and your concurrent code will stay tidy, debuggable, and future‑proof.

Happy coding! 🚀
🚀 One‑Sitting Python Project: “Markdown Link Linter”

Ever opened a markdown file and wondered whether the links you’ve pasted are still alive? A tiny CLI that walks through a folder, pulls out every http(s)://… reference, pings each URL, and spits out a clean report can save you hours of broken‑link hunting. It fits comfortably in a single sitting, yet touches several core Python ideas.

Why it’s useful
- Keeps documentation trustworthy → no 404 surprises for readers.
- Works on any repo – just point it at the root folder.
- Gives you a quick “what’s broken” checklist you can commit back to the repo.

Core concepts you’ll practice

- Pathlib for OS‑independent file discovery (Path.rglob('*.md')).
- Regular expressions to extract URLs from raw text (re.findall).
- Requests (or the lighter http.client) to perform a HEAD request – we only need the status line, not the whole page, which makes the check fast and polite.
- Set to deduplicate URLs before hitting the network.
- ThreadPoolExecutor (from concurrent.futures) to parallelise the I/O‑bound checks without diving into async‑await.

The “why” behind the choices

- Pathlib abstracts away Windows vs. POSIX path quirks, letting you write for md in Path('.').rglob('*.md'): and be confident it works everywhere.
- A HEAD request returns just the headers (including the status code). If a server refuses HEAD, we gracefully fall back to a lightweight GET with stream=True and immediately close the connection – this avoids downloading megabytes of data.
- Using a set removes duplicate links early, which cuts network traffic dramatically, especially in large docs that reuse the same reference.
- ThreadPoolExecutor gives you simple concurrency: each thread waits on network I/O, so a pool of 10‑20 threads can finish a hundred checks in a blink, without the boilerplate of asyncio and aiohttp.

A minimal skeleton to spark you

import re
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests

# 1️⃣ Gather markdown files
md_files = list(Path('.').rglob('*.md'))

# 2️⃣ Pull out URLs (simple but effective)
url_pat = re.compile(r'https?://[^\s)>\]\'"]+')
all_urls = {match for f in md_files for match in url_pat.findall(f.read_text())}

def check_url(url: str) -> tuple[str, int]:
"""Return (url, status_code). Try HEAD first, fall back to GET."""
try:
r = requests.head(url, allow_redirects=True, timeout=5)
if r.status_code >= 400: # some sites dislike HEAD
r = requests.get(url, stream=True, timeout=5)
return url, r.status_code
except requests.RequestException:
return url, 0 # 0 signals a network error

# 3️⃣ Parallel check
broken = []
with ThreadPoolExecutor(max_workers=15) as pool:
future_to_url = {pool.submit(check_url, u): u for u in all_urls}
for fut in as_completed(future_to_url):
url, code = fut.result()
if code >= 400 or code == 0:
broken.append((url, code))

# 4️⃣ Report
print("\n🔎 Link check complete!\n")
if broken:
print("❌ Broken links found:")
for u, c in broken:
status = "no response" if c == 0 else f"HTTP {c}"
print(f" • {u} → {status}")
else:
print("✅ All links are reachable.")


How to expand it (optional, not required for the one‑sitting version)

- Output a Markdown table or JSON file so CI can fail on broken links.
- Add a --exclude flag to ignore certain domains (e.g., localhost).
- Integrate with GitHub Actions to run automatically on PRs.

(continued below 👇)
Give it a spin: copy the script into link_linter.py, pip install requests, then run python link_linter.py from the root of any docs repo. In under five minutes you’ll have a living audit of every hyperlink. Happy hacking! 🌟
👋 Hey Python newcomers!
Ever wanted to walk through a list and know where you are in that list at the same time?
Most beginners start with something like:

for i in range(len(my_list)):
item = my_list[i]
# do something with i and item


It works, but it’s a bit clunky and can lead to hidden bugs. Let’s unpack why enumerate is the friend you’ve been missing, and how it makes your code cleaner, safer, and easier to read.

📚 What’s really happening?

- range(len(my_list)) creates a new sequence of numbers from 0 up to len(my_list)-1.
- Every iteration the interpreter looks up my_list[i] by index.
- If you ever change the list length inside the loop (e.g., my_list.append(...)), the range you built doesn’t know about that change – it will still run the original number of times, which can cause IndexError or silently skip items.

enumerate solves this by turning the list itself into an iterator that yields pairs: (index, value). The iterator walks the list exactly as it exists at that moment, respecting any modifications (though you usually shouldn’t modify while iterating).

✅ The tidy way with enumerate

fruits = ["apple", "banana", "cherry", "date"]

for idx, fruit in enumerate(fruits):
print(f"#{idx + 1}: {fruit}")


What you see:

- idx automatically starts at 0 (you can change the start with a second argument).
- fruit is the actual element at that position.
- No manual indexing, no extra len() call, no risk of mismatched lengths.

Why it works:
enumerate internally keeps a counter and, for each element it pulls from the original iterable, it returns a tuple (counter, element). The for loop then unpacks that tuple into idx and fruit.

🎯 When to use it

- Printing a numbered list (like the example above).
- Processing items while remembering their position – e.g., marking the first occurrence of a duplicate.
- When you need both index and value for calculations, without extra look‑ups.

⚠️ Common pitfalls (and how enumerate avoids them)

Mistake you might make — What goes wrong — How enumerate helps

Using range(len(seq)) and then changing seq inside the loop — Loop runs the old length → IndexError or skipped items — enumerate walks the current state of the iterable
Forgetting to add +1 for human‑readable numbering — Output starts at 0, which looks odd to users — You can pass a start value: enumerate(seq, start=1)
Manually managing two counters (e.g., i = 0; for x in seq: i += 1) — Easy to forget to increment, leads to off‑by‑one bugs — enumerate does the counting for you, no extra state

🛠️ Quick tip: start counting at 1

If you want the first item to be numbered 1 instead of 0, just give enumerate a start value:

for position, fruit in enumerate(fruits, start=1):
print(f"{position}. {fruit}")


Output:

1. apple
2. banana
3. cherry
4. date


📌 TL;DR (Take‑away)

- enumerate = clean, safe, and readable way to get both index and value while looping.
- It avoids manual index maths, reduces error‑prone code, and works with any iterable (lists, strings, tuples, even generators).
- Remember the optional start argument for human‑friendly numbering.

Give it a spin in your next script, and you’ll wonder how you ever lived without it! 🚀

Happy coding! 🎉
⚡️ Unlock High‑Performance Memoization for Any Argument Type

If you’ve been using functools.lru_cache you already know how sweet caching can be for pure functions.
But there’s a hidden snag: lru_cache only works with hash‑able arguments. As soon as you pass a list, dict, or a custom mutable object you’ll hit a TypeError.

Below is a drop‑in replacement that lets you cache any arguments – even unhashable ones – while still giving you the same LRU semantics, thread‑safety, and cache‑statistics you love.

🔍 How it works under the hood

1. Normalization – Every argument is transformed into an immutable, hash‑able representation.
* For built‑ins we recursively convert list → tuple, dict → frozenset of key/value pairs, set → frozenset.
* For custom objects we fall back to their id (the memory address) – safe because the object’s identity never changes during a single call.

2. Key construction – The normalized positional arguments and a sorted tuple of normalized keyword arguments are concatenated into a single tuple key.
This guarantees that (a=1, b=2) and (b=2, a=1) map to the same cache entry.

3. LRU mechanics – We reuse functools._lru_cache_wrapper logic via functools.lru_cache on an inner helper that receives the normalized key.
The wrapper handles the doubly‑linked list, hit/miss counters, and thread lock – we don’t have to reinvent that part.

4. Cache inspection – The wrapper still exposes .cache_info() and .cache_clear(), so you can monitor usage exactly like the std‑lib version.

💡 When to reach for this

* Functions that accept large data structures (e.g., pandas DataFrames, nested dicts) but are pure (no side‑effects).
* Recursive algorithms where the state is a mutable container – think DP on grids stored as lists of lists.
* APIs that receive JSON‑like payloads and would benefit from memoizing expensive validation or transformation steps.

🛠️ Full implementation

(continued below 👇)
import functools
import threading
from collections.abc import Mapping, Sequence, Set

def _make_hashable(obj):
"""Recursively turn mutable containers into immutable, hash‑able equivalents."""
if isinstance(obj, (str, bytes, int, float, bool, type(None))):
return obj # already hashable
if isinstance(obj, Mapping):
# Sort items to make order irrelevant
return frozenset((k, _make_hashable(v)) for k, v in sorted(obj.items()))
if isinstance(obj, (Set, frozenset)):
return frozenset(_make_hashable(v) for v in sorted(obj))
if isinstance(obj, Sequence):
# Treat bytes as already handled; for lists/tuples, recurse
return tuple(_make_hashable(v) for v in obj)
# Fallback: use object identity – safe for the duration of a single call
return id(obj)

def universal_lru_cache(maxsize=128, typed=False):
"""
LRU cache decorator that works with any arguments, mutable or not.
Mirrors functools.lru_cache signature.
"""
def decorator(user_func):
lock = threading.RLock()

@functools.wraps(user_func)
def wrapper(*args, **kwargs):
# Normalise args to a hashable key
key = (
tuple(_make_hashable(arg) for arg in args),
tuple(sorted((k, _make_hashable(v)) for k, v in kwargs.items()))
)
# Use the built‑in lru_cache on a thin inner function that receives the normalized key
@functools.lru_cache(maxsize=maxsize, typed=typed)
def _inner(normalised_key):
# Unpack the original args back – we don't need them here because
# the user function will be called with the *original* args/kwargs
return user_func(*args, **kwargs)

# The lock guards the first‑time creation of the inner function per wrapper
with lock:
return _inner(key)

# expose the original lru_cache helpers for introspection
wrapper.cache_info = lambda: wrapper.__wrapped__.cache_info()
wrapper.cache_clear = lambda: wrapper.__wrapped__.cache_clear()
return wrapper
return decorator


⚙️ Example usage

@universal_lru_cache(maxsize=4)
def expensive_sum(matrix):
"""Pretend this is a heavy computation on a mutable list‑of‑lists."""
total = 0
for row in matrix:
for val in row:
total += val
return total

# First call – cache miss
print(expensive_sum([[1, 2], [3, 4]])) # → 10

# Second call with an *identical* mutable structure – cache hit!
print(expensive_sum([[1, 2], [3, 4]])) # → 10 (instant)

# Different shape – cache miss
print(expensive_sum([[5, 5, 5], [5, 5, 5]])) # → 30

print(expensive_sum.cache_info())
# CacheInfo(hits=1, misses=2, maxsize=4, currsize=2)


🔎 Why this works

* The normalization step guarantees that two semantically equal containers produce the exact same hash.
By turning a list into a tuple (and recursively doing the same for nested structures), we preserve the element order while gaining immutability.

* Sorting mappings removes the nondeterminism of dictionary insertion order, so {a:1, b:2} and {b:2, a:1} collide on purpose – they represent the same logical input.

* Using id as a fallback ensures that objects without a natural immutable representation (e.g., file handles, custom classes without __hash__) still get a stable key for the duration of that call. It’s safe because the cache key lives only as long as the function call; the object’s identity won’t change.

(continued below 👇)
Wrapping an inner function with functools.lru_cache lets us reuse the battle‑tested LRU implementation (including thread‑locking, eviction policy, and statistics) without re‑implementing those low‑level details. The outer wrapper simply translates between real arguments and the canonical* hashable key.

🚀 Takeaway checklist

* ✅ You can now memoize functions that accept any mutable data structure.
* ✅ Cache hits are deterministic – identical logical inputs, regardless of mutability, map to the same entry.
* ✅ No performance penalty beyond the one‑time normalization (which is cheap for most data shapes).
* ✅ Still get cache_info() and cache_clear() for introspection and control.

Give it a spin in your next data‑heavy project and watch those redundant calculations vanish! 🌟
🚀 One‑Sitting Python Project: “Silent Clipboard Historian”

Ever copy‑and‑paste a piece of code, a link, or a snippet and later wish you could scroll back to the exact thing you copied ? A clipboard logger gives you a personal “undo” for the clipboard, works offline, and fits in a single script you can run whenever you need it.

Why it works

1. pyperclip talks to the OS clipboard in a cross‑platform way (Windows, macOS, Linux).
2. The script polls the clipboard every few hundred milliseconds – fast enough for human speed but light on CPU.
3. By keeping the last seen value in memory we can detect changes and avoid logging the same text over and over.
4. Each new entry is stamped with datetime.utcnow() and appended to a JSON file. JSON is human‑readable, easy to load later, and works without a DB server.
5. A tiny CLI helper (--show N) lets you peek at the last N entries without opening the file manually.

All of this fits into ≈ 60 lines and needs only two third‑party packages.

Libraries / concepts you’ll touch

- pyperclip – clipboard access.
- json – serialising a list of dictionaries.
- datetime – UTC timestamps, ISO‑8601 format.
- argparse – a friendly command‑line interface.
- Polling loop – while True: … time.sleep(0.5) – a simple event‑driven pattern without extra dependencies.

Quick sketch of the core loop

import json, time, argparse, sys
from datetime import datetime
import pyperclip

LOGFILE = "clipboard_history.json"

def load_log():
try:
with open(LOGFILE, "r", encoding="utf-8") as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return []

def save_log(entry, history):
history.append(entry)
with open(LOGFILE, "w", encoding="utf-8") as f:
json.dump(history, f, ensure_ascii=False, indent=2)

def main(show_last: int = None):
if show_last:
# ----- quick read‑only mode -----
history = load_log()
for rec in history[-show_last:]:
print(f"[{rec['ts']}] {rec['text']!r}")
return

# ----- live‑logging mode -----
history = load_log()
last_clip = pyperclip.paste() # initialise with current content
print("📝 Clipboard historian started – press Ctrl‑C to stop.")
try:
while True:
cur = pyperclip.paste()
if cur != last_clip:
entry = {"ts": datetime.utcnow().isoformat() + "Z", "text": cur}
save_log(entry, history)
print(f"🔖 Captured at {entry['ts']}")
last_clip = cur
time.sleep(0.5) # half‑second pause → low CPU usage
except KeyboardInterrupt:
print("\n🛑 Stopped. History saved to", LOGFILE)

if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Silent Clipboard Historian – log everything you copy."
)
parser.add_argument(
"-n", "--show", type=int, metavar="N",
help="Print the last N clipboard entries and exit."
)
args = parser.parse_args()
main(show_last=args.show)


What the snippet does

- load_log() safely opens the JSON file; if it doesn’t exist we start with an empty list.
- save_log() appends a new dict ({"ts": "...", "text": "…"}) and writes the whole list back – JSON’s indent=2 keeps the file readable.
- In live‑logging mode we keep last_clip in a local variable; every loop we compare the freshly‑pasted text. If it changed, we create a timestamp with datetime.utcnow().isoformat() + "Z" (the Z marks UTC) and dump it.
- The --show N flag bypasses the loop, loads the file, and prints the most recent N lines – perfect for quick look‑ups.

How to run it

(continued below 👇)
pip install pyperclip          # one‑off dependency
python clipboard_hist.py # start logging
python clipboard_hist.py -n 5 # see the last 5 copies


Tips for extending (optional, after you’ve built it)

- Deduplication: store a hash of the text to avoid logging identical snippets that appear later.
- Desktop notifications: use plyer or win10toast to pop a tiny “saved” banner each time you copy.
- Search: add a --find <keyword> flag that filters entries containing the word.

Give it a try, tweak the polling interval if you feel adventurous, and enjoy having a personal “clipboard time‑machine” at your fingertips. Happy coding! 🎉
❤1
👋 Hey Python beginners!
Ever written a loop like this and wondered “How do I keep track of the position of each item?”

fruits = ["apple", "banana", "cherry"]
i = 0
for fruit in fruits:
print(i, fruit) # ❓ What’s i?
i += 1


You’ve just invented a counter yourself – that works, but Python already gives us a cleaner, safer tool: enumerate.

What enumerate does, in plain words
- enumerate takes any iterable (like a list, tuple, or string).
- It adds a running number (starting at 0 by default) to each element and hands you a pair (index, value).
- Think of it as a “smart counter” that never forgets to increase, even if you break out of the loop early.

Why it’s better than a manual counter
1. Less code, less chance of mistakes – you don’t need to create, update, and remember a separate variable.
2. Works automatically with break or continue. If you skip an iteration, the counter stays correct.
3. Readability: anyone reading your code immediately sees you’re dealing with both an item and its position.

Using enumerate – the basics

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

for index, fruit in enumerate(fruits):
print(f"Item {index}: {fruit}")


What happens?

1. enumerate(fruits) creates an iterator that yields (0, "apple"), then (1, "banana"), and so on.
2. The for loop unpacks each tuple into index and fruit.
3. Inside the body you can use both values freely.

Output

Item 0: apple
Item 1: banana
Item 2: cherry


Custom start number

Sometimes you want counting to start at 1 (human‑friendly) instead of 0. Just pass a second argument:

for pos, fruit in enumerate(fruits, start=1):
print(f"{pos}. {fruit}")


Output

1. apple
2. banana
3. cherry


Why start=1 works: enumerate adds the offset you give it to the internal 0‑based counter before returning the pair.

Real‑world mini‑example: finding the first vowel in a word

word = "python"

for i, ch in enumerate(word):
if ch.lower() in "aeiou":
print(f"First vowel '{ch}' at position {i}")
break
else:
print("No vowel found")


- The loop checks each character.
- As soon as a vowel appears, we break out, and enumerate already knows the correct index (i).
- The else block runs only if the loop finishes without break, a handy pattern for “search‑and‑report” tasks.

TL;DR (the quick cheat‑sheet)

- enumerate(iterable, start=0) → gives you (index, value) pairs.
- Use it instead of manual counters for cleaner, safer loops.
- Adjust start when you need human‑readable numbering.

Give it a try in your next script – you’ll see how much smoother looping feels! 🚀

Happy coding! 🎉
🔧 Ever wanted to import Python code that lives somewhere other than a .py file on disk?

Maybe the source is stored in a database, fetched over the network, or generated on‑the‑fly.
The good news is that Python’s import machinery is entirely pluggable – you can teach the interpreter how to locate and load a module wherever you like.

Below is a self‑contained, intermediate‑to‑advanced example that shows you how to:

1. Create a custom finder that tells Python “I know how to locate this module”.
2. Build a loader that compiles the source code and injects the resulting module object.
3. Hook the finder into sys.meta_path so the interpreter automatically uses it.

> Why this works:
> The import system follows the PEP 302 protocol. When import foo runs, Python asks each entry in sys.meta_path (a list of finder objects) to produce a module spec (importlib.machinery.ModuleSpec). If a finder returns a spec, its associated loader is called to execute the module’s code. By providing our own finder/loader pair we can intercept the process at the exact point where the standard file‑system loader would normally kick in.

(continued below 👇)
# in_memory_importer.py
import sys
import types
import importlib.abc
import importlib.util
from hashlib import sha256

# ----------------------------------------------------------------------
# 1️⃣ A simple “virtual file system”: module name → source string
# ----------------------------------------------------------------------
VIRTUAL_MODULES = {
"demo_greeting": """
def hello(name: str) -> str:
return f"👋 Hello, {name}!"
""",
"demo_math": """
# A tiny math helper – deliberately kept in a string
def add(a, b):
return a + b

def mul(a, b):
return a * b
""",
}

# ----------------------------------------------------------------------
# 2️⃣ Finder – decides whether it can handle a given fully‑qualified name
# ----------------------------------------------------------------------
class InMemoryFinder(importlib.abc.MetaPathFinder):
def find_spec(self, fullname, path, target=None):
# `fullname` is the absolute module name (e.g. "demo_greeting")
if fullname in VIRTUAL_MODULES:
# We create a spec that points to our custom loader
return importlib.util.spec_from_loader(fullname, InMemoryLoader())
# Returning None tells Python to try the next finder in the chain
return None

# ----------------------------------------------------------------------
# 3️⃣ Loader – turns the source string into a real module object
# ----------------------------------------------------------------------
class InMemoryLoader(importlib.abc.Loader):
def create_module(self, spec):
"""
Optional hook. Returning None means “use the default module creation
semantics” (i.e. a plain `types.ModuleType`). Keeping it simple.
"""
return None

def exec_module(self, module):
"""
This is where the actual code execution happens.
`module.__spec__` already contains the name we need.
"""
source = VIRTUAL_MODULES[module.__spec__.name]
# Compile the source to a code object – this gives us a proper
# filename for tracebacks and enables `inspect.getsource`.
code = compile(source, f"<in‑memory:{module.__spec__.name}>", "exec")
# Execute the code in the module’s namespace.
exec(code, module.__dict__)

# Optional: give the module a stable __file__ attribute.
# Some libraries (e.g. pathlib.Path(__file__).parent) rely on it.
module.__file__ = f"<in‑memory:{module.__spec__.name}>"

# Optional: compute a deterministic hash for caching tools.
module.__hash__ = sha256(source.encode()).hexdigest()

# ----------------------------------------------------------------------
# 4️⃣ Register the finder – do this *once* at program start‑up
# ----------------------------------------------------------------------
sys.meta_path.insert(0, InMemoryFinder())

# ----------------------------------------------------------------------
# 5️⃣ Demo usage (you can delete this block in production)
# ----------------------------------------------------------------------
if __name__ == "__main__":
import demo_greeting
import demo_math

print(demo_greeting.hello("Ada"))
print("2 + 3 =", demo_math.add(2, 3))
print("4 * 5 =", demo_math.mul(4, 5))


How the pieces interact

(continued below 👇)
- sys.meta_path is a list of finder objects consulted in order. By inserting our InMemoryFinder at position 0 we give it priority over the built‑in file‑system finder.
- find_spec receives the full module name. If the name exists in our VIRTUAL_MODULES dict we hand back a ModuleSpec that points to InMemoryLoader. Returning None is crucial; it tells the interpreter “I don’t know this one, try the next finder”.
- exec_module receives the module object that Python already created (or that create_module returned). We fetch the source, compile it (so the code object carries a sensible filename and line numbers), and exec it in the module’s dictionary. After exec finishes the module’s globals contain the functions, classes, etc., exactly as if the code had been loaded from a .py file.
- Why we compile: compiling gives us a code object that the interpreter can cache, introspect, and display proper tracebacks. It also lets tools like inspect.getsource work because we set module.__file__ to a recognizable pseudo‑filename.
- Thread safety: the import system already holds an internal lock (importlib._bootstrap._lock) while loading a module, so our loader does not need extra synchronization – the same module will never be executed twice concurrently.

When to reach for this pattern

- Configuration‑driven plugins stored in a database or remote service.
- On‑the‑fly generated code (e.g., JIT‑like scenarios where you synthesize Python source at runtime).
- Testing: you can inject mock modules without touching the filesystem, keeping test suites fast and isolated.
- Packaging tricks: bundle tiny helper modules inside a single binary (e.g., when using PyInstaller) and load them from a compressed archive.

💡 Quick sanity check: After running the script, try import demo_greeting; print(demo_greeting.__file__). You’ll see the pseudo‑filename <in‑memory:demo_greeting>, proving that the module thinks it lives somewhere virtual – yet it behaves exactly like any regular module.

Feel free to adapt the VIRTUAL_MODULES dict into a database query, an HTTP call, or any other data source. The import system is flexible enough that once you master the finder/loader contract, you can make Python import from anywhere. 🚀
🚀 One‑Sitting Python Project: “Smart Downloads Organizer”

Ever stare at a chaotic Downloads folder and wish the files would just file themselves? Build a tiny daemon that watches the folder, inspects each new item, and instantly ships it to a sensible sub‑directory (e.g., Images, Docs, Archives). All in under an hour, no GUI required.

Why it works

1. File‑system events are push‑based – the watchdog library creates an Observer that runs in a background thread and calls you only when something actually changes. No need for a busy‑wait loop that burns CPU.
2. MIME types give intent – Python’s built‑in mimetypes module maps a filename (or its contents via magic if you want extra accuracy) to a high‑level type like image/jpeg or application/pdf. Those top‑level strings (image, video, text, application) become the basis for your target folders.
3. Atomic moves keep things safe – shutil.move works across filesystems and automatically falls back to a copy‑+‑delete when a simple rename isn’t possible, so the script stays robust even if the user redirects downloads to a network drive.
4. Configurable rules = extensibility – By storing a tiny JSON/YAML mapping (.pdf → Documents, *.zip → Archives) you let the user tweak the behavior without touching code. The core loop stays unchanged.

What you’ll need

* watchdog – watches directories for create/modify/delete events.
* mimetypes (standard library) – guesses the file’s type from its name.
* shutil & pathlib – safe, cross‑platform path handling and moving.
* (Optional) python-magic – for content‑based MIME detection if you want extra accuracy.

High‑level flow

1. Load / create a mapping (type → folder).
2. Start a watchdog.Observer on the target directory.
3. For each FileCreatedEvent:
a. Wait a tiny moment (time.sleep(0.5)) to let the download finish.
b. Ask mimetypes.guess_type for the MIME string.
c. Derive the top‑level category (image, text, application, …).
d. Resolve the destination folder (fallback to Others).
e. shutil.move the file there, creating the folder if needed.
4. Keep the observer alive with a clean try/except KeyboardInterrupt block.

Code snapshot

(continued below 👇)
import json
import time
import shutil
from pathlib import Path
import mimetypes

from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

# ---- Config ---------------------------------------------------------
# You can edit this file to change where each type lands.
CONFIG_PATH = Path.home() / ".downloads_organizer.json"
DEFAULT_MAP = {
"image": "Images",
"video": "Videos",
"text": "Documents",
"application": "Archives", # e.g. zip, pdf, exe
"audio": "Audio"
}
if CONFIG_PATH.exists():
TYPE_MAP = json.loads(CONFIG_PATH.read_text())
else:
TYPE_MAP = DEFAULT_MAP
CONFIG_PATH.write_text(json.dumps(DEFAULT_MAP, indent=2))

# ---- Event handler --------------------------------------------------
class DownloadHandler(FileSystemEventHandler):
def __init__(self, watch_path: Path):
self.watch_path = watch_path

def on_created(self, event):
# We only care about files, not sub‑folders.
if event.is_directory:
return

src = Path(event.src_path)

# Give the OS a moment to finish writing the file.
time.sleep(0.5)

# Guess MIME type → "image/jpeg" → "image"
mime, _ = mimetypes.guess_type(src.name)
category = mime.split("/")[0] if mime else "unknown"

# Resolve destination folder (fallback to "Others")
folder_name = TYPE_MAP.get(category, "Others")
dest_dir = self.watch_path / folder_name
dest_dir.mkdir(exist_ok=True)

dest = dest_dir / src.name
try:
shutil.move(str(src), str(dest))
print(f"🗂️ Moved {src.name} → {dest_dir.name}/")
except Exception as exc:
print(f"⚠️ Failed to move {src.name}: {exc}")

# ---- Main loop ------------------------------------------------------
def main():
downloads = Path.home() / "Downloads"
handler = DownloadHandler(downloads)
observer = Observer()
observer.schedule(handler, str(downloads), recursive=False)
observer.start()
print(f"👀 Watching {downloads} … Press Ctrl+C to stop.")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()

if __name__ == "__main__":
main()


What’s happening under the hood?

Observer.schedule registers handler for the Downloads* directory.
* When the OS creates a new file, on_created fires once – no polling.
* mimetypes.guess_type simply looks at the file extension; it’s fast and requires no extra binaries.
* shutil.move abstracts away os.rename vs. copy‑+‑delete, so you don’t need to think about cross‑disk moves.

⚡️ Take it further (optional challenges)

* Replace mimetypes with magic.from_file(..., mime=True) for content‑based detection.
Add a dry‑run* flag that only prints actions.
* Hook into the system notification center (plyer or notify2) to pop a toast each time a file lands.
Persist a log* (sqlite or plain CSV) of moved files for later analytics.

Give it a spin, watch your Downloads tidy itself, and feel the instant gratification of a script that actually does something useful every time you grab a file. Happy hacking! 🎉
👋 Hey Python newbies!
Ever needed to go through a list and also know which position you’re at?
You might have tried something like this:

for i in range(len(names)):
print(i, names[i])


It works, but there’s a cleaner, safer way that Python gives you for free – enumerate(). Let’s unpack what it does and why it’s often the better choice.

🔎 What is enumerate()?
enumerate() takes any iterable (a list, tuple, string, etc.) and turns it into a sequence of pairs:

first element → the index (starting at 0 by default)
second element → the actual item from your original iterable

So instead of juggling two separate things (range and indexing), you get both in one tidy package.

🛠️ How to use it

names = ["Alice", "Bob", "Charlie"]

for idx, name in enumerate(names):
print(f"#{idx + 1}: {name}")


What happens step‑by‑step?

1. enumerate(names) creates an iterator that yields (0, "Alice"), then (1, "Bob"), then (2, "Charlie").
2. The for loop unpacks each pair into idx and name.
3. Inside the block you can use both variables directly.

💡 Why is enumerate() nicer than range(len(...))?

✅ enumerate() — ❌ range(len(...))

Readability – “for each index and item” is obvious. — You have to remember to index the list manually.
Safety – Won’t raise an error if the list changes size during the loop. — If you accidentally change the list length, range may become out‑of‑sync.
Flexibility – You can start counting from any number (e.g., enumerate(seq, start=1)). — You’d need extra arithmetic (i+1).
Works with any iterable – even generators that don’t support len(). — Requires something that has a length (len()).

🔧 Quick variations

Start counting from 1 (often nicer for human‑readable output):

for pos, fruit in enumerate(["apple", "banana", "cherry"], start=1):
print(f"{pos}. {fruit}")


Enumerate a string (each character gets an index):

for i, ch in enumerate("hello"):
print(i, ch)


🚀 Mini challenge

Try printing the items of the list tasks = ["code", "debug", "test"] in reverse order while still showing the original index (0‑based). Hint: combine enumerate() with reversed().

tasks = ["code", "debug", "test"]

for idx, task in enumerate(reversed(tasks)):
# your code here
pass


Solution:

for idx, task in enumerate(reversed(tasks)):
print(f"Original index {len(tasks)-1-idx}: {task}")


👍 Takeaway
Whenever you need both the position and the value while looping, reach for enumerate(). It makes your code clearer, less error‑prone, and works with any kind of iterable – a tiny tool that pays big dividends as you write more Python. Happy coding! 🚀
🛡️ Power‑up your network calls with a universal retry‑with‑backoff decorator

When you call external APIs, databases, or even local services, transient failures are the norm—not the exception. A bare‑bones try/except quickly becomes noisy and hard to maintain. Below is a single, reusable decorator that:

* Works for both sync and async functions (no duplicate code).
* Implements exponential back‑off with optional jitter (randomness) to avoid thundering‑herd effects.
* Lets you fine‑tune max attempts, base delay, and which exception types trigger a retry.
* Preserves the original function’s signature and metadata so tools like IDEs, help(), and type‑checkers stay happy.

👉 Why the pieces matter

1. functools.wraps – Copies __name__, __doc__, and the signature from the wrapped function to the wrapper. Without it, introspection (e.g. inspect.signature) would see the generic wrapper signature, breaking static analysis and docs.

2. Closure over is_async – We detect iscoroutinefunction once when the decorator is applied, not on every call. This avoids the overhead of re‑checking and lets us choose the correct await/sleep path.

3. Exponential back‑off formula


delay = base * (2 ** attempt)



Each retry doubles the wait time, giving the failing service time to recover.

4. Jitter (random.uniform(0, jitter)) – Adding a small random component prevents many clients from retrying exactly at the same moment, which could otherwise cause another spike of failures.

5. Separate sleep helpers – time.sleep blocks the thread; asyncio.sleep yields control back to the event loop. By abstracting them, the same retry logic works for both sync and async call‑sites.

6. Configurable exceptions – By default we retry on any Exception, but you can restrict it to, say, requests.exceptions.RequestException or a custom TransientError.

📦 The code

(continued below 👇)
❤1