From af7524f37bffcfdc1ed6f41b16c15928b139ceb7 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Fri, 12 Dec 2025 13:08:32 +0100 Subject: [PATCH 1/4] Sample code for the article on best practices --- python-best-practices/README.md | 3 + python-best-practices/classes.py | 59 ++++++++++++++++++++ python-best-practices/code_style.py | 7 +++ python-best-practices/comments.py | 46 +++++++++++++++ python-best-practices/comprehensions.py | 8 +++ python-best-practices/concurrency.py | 39 +++++++++++++ python-best-practices/conditionals.py | 41 ++++++++++++++ python-best-practices/docstrings.py | 18 ++++++ python-best-practices/exceptions.py | 52 +++++++++++++++++ python-best-practices/functions.py | 41 ++++++++++++++ python-best-practices/imports.py | 28 ++++++++++ python-best-practices/loggers.py | 27 +++++++++ python-best-practices/loops.py | 14 +++++ python-best-practices/mutability.py | 20 +++++++ python-best-practices/public_names.py | 24 ++++++++ python-best-practices/pythonic.py | 12 ++++ python-best-practices/rafactoring.py | 39 +++++++++++++ python-best-practices/resources.py | 10 ++++ python-best-practices/stdlib.py | 16 ++++++ python-best-practices/testing.py | 31 ++++++++++ python-best-practices/type_hint.py | 33 +++++++++++ python-best-practices/variables_constants.py | 17 ++++++ 22 files changed, 585 insertions(+) create mode 100644 python-best-practices/README.md create mode 100644 python-best-practices/classes.py create mode 100644 python-best-practices/code_style.py create mode 100644 python-best-practices/comments.py create mode 100644 python-best-practices/comprehensions.py create mode 100644 python-best-practices/concurrency.py create mode 100644 python-best-practices/conditionals.py create mode 100644 python-best-practices/docstrings.py create mode 100644 python-best-practices/exceptions.py create mode 100644 python-best-practices/functions.py create mode 100644 python-best-practices/imports.py create mode 100644 python-best-practices/loggers.py create mode 100644 python-best-practices/loops.py create mode 100644 python-best-practices/mutability.py create mode 100644 python-best-practices/public_names.py create mode 100644 python-best-practices/pythonic.py create mode 100644 python-best-practices/rafactoring.py create mode 100644 python-best-practices/resources.py create mode 100644 python-best-practices/stdlib.py create mode 100644 python-best-practices/testing.py create mode 100644 python-best-practices/type_hint.py create mode 100644 python-best-practices/variables_constants.py diff --git a/python-best-practices/README.md b/python-best-practices/README.md new file mode 100644 index 0000000000..fbe6915990 --- /dev/null +++ b/python-best-practices/README.md @@ -0,0 +1,3 @@ +# Python Best Practices: From Messy to Pythonic Code + +This folder provides the code examples for the Real Python tutorial [Python Best Practices: From Messy to Pythonic Code](https://realpython.com/python-best-practice/). diff --git a/python-best-practices/classes.py b/python-best-practices/classes.py new file mode 100644 index 0000000000..6429d8236d --- /dev/null +++ b/python-best-practices/classes.py @@ -0,0 +1,59 @@ +# Avoid this: +# class Article: +# def __init__(self, title, body, tags, db): +# self.title = title +# self.body = body +# self.tags = tags or [] +# self.db = db +# self.slug = None +# self.published = False + +# def publish(self): +# if self.slug is None: +# self.slug = "-".join(self.title.lower().split()) + +# self.db.save_article( +# title=self.title, +# body=self.body, +# tags=self.tags, +# slug=self.slug, +# ) + +# self.published = True + + +# Favor this: +from dataclasses import dataclass, field +from datetime import datetime + + +@dataclass +class Article: + title: str + body: str + tags: list[str] = field(default_factory=list) + created_at: datetime = field(default_factory=datetime.utcnow) + published_at: datetime | None = None + + @property + def is_published(self) -> bool: + return self.published_at is not None + + @property + def slug(self) -> str: + return "-".join(self.title.lower().split()) + + def __str__(self) -> str: + status = "published" if self.is_published else "draft" + return f"{self.title} [{status}]" + + +class Publisher: + def __init__(self, db): + self._db = db + + def publish(self, article: Article) -> None: + if article.is_published: + return + article.published_at = datetime.utcnow() + self._db.save(article) diff --git a/python-best-practices/code_style.py b/python-best-practices/code_style.py new file mode 100644 index 0000000000..c471125caa --- /dev/null +++ b/python-best-practices/code_style.py @@ -0,0 +1,7 @@ +# Avoid this: +# def addNum(a,b):return a+ b + + +# Favor this: +def add_numbers(a, b): + return a + b diff --git a/python-best-practices/comments.py b/python-best-practices/comments.py new file mode 100644 index 0000000000..293d258293 --- /dev/null +++ b/python-best-practices/comments.py @@ -0,0 +1,46 @@ +# Avoid this: +# def find_index(sorted_items, target): +# # Set left index +# left = 0 +# # Set right index +# right = len(sorted_items) - 1 +# # Loop while left is less than right +# while left <= right: +# # Compute middle +# mid = (left + right) // 2 +# # Check if equal +# if sorted_items[mid] == target: +# # Return mid +# return mid +# # Check if less than target +# elif sorted_items[mid] < target: +# # Move left up +# left = mid + 1 +# else: +# # Move right down +# right = mid - 1 +# # Return -1 if not found +# return -1 + + +# Favor this: +def find_index(sorted_items, target): + """Return the index of target in sorted_items, or -1 if not found.""" + left = 0 + right = len(sorted_items) - 1 + + while left <= right: + mid = (left + right) // 2 + value = sorted_items[mid] + + if value == target: + return mid + + # If target is larger, you can safely ignore the left half + if value < target: + left = mid + 1 + # Otherwise, target must be in the left half (if present) + else: + right = mid - 1 + + return -1 diff --git a/python-best-practices/comprehensions.py b/python-best-practices/comprehensions.py new file mode 100644 index 0000000000..fc47dfbe64 --- /dev/null +++ b/python-best-practices/comprehensions.py @@ -0,0 +1,8 @@ +# Avoid this: +# cubes = [] +# for number in range(10): +# cubes.append(number**3) + +# Favor this: +cubes = [number**3 for number in range(10)] +cubes diff --git a/python-best-practices/concurrency.py b/python-best-practices/concurrency.py new file mode 100644 index 0000000000..80388d12cb --- /dev/null +++ b/python-best-practices/concurrency.py @@ -0,0 +1,39 @@ +# Avoid this: +# import asyncio + +# import requests + +# async def main(): +# await asyncio.gather( +# fetch_status("https://example.com"), +# fetch_status("https://python.org"), +# ) + +# async def fetch_status(url): +# response = requests.get(url) # Blocking I/O task +# return response.status_code + +# asyncio.run(main()) + + +# Favor this: +import asyncio + +import aiohttp + + +async def main(): + async with aiohttp.ClientSession() as session: + statuses = await asyncio.gather( + fetch_status(session, "https://example.com"), + fetch_status(session, "https://realpython.com"), + ) + print(statuses) + + +async def fetch_status(session, url): + async with session.get(url) as response: # Non-blocking I/O task + return response.status + + +asyncio.run(main()) diff --git a/python-best-practices/conditionals.py b/python-best-practices/conditionals.py new file mode 100644 index 0000000000..ff0ab4afb6 --- /dev/null +++ b/python-best-practices/conditionals.py @@ -0,0 +1,41 @@ +# Avoid this: +# def shipping_cost(country, items): +# if country is not None: +# if country == "US": +# if len(items) > 0: # Non-empty cart? +# if len(items) > 10: # Free shipping? +# return 0 +# else: +# return 5 +# else: +# return 0 +# elif country == "CA": +# if len(items) > 0: # Non-empty cart? +# return 10 +# else: +# return 0 +# else: +# # Other countries +# if len(items) > 0: # Non-empty cart? +# return 20 +# else: +# return 0 +# else: +# raise ValueError("invalid country") + + +# Favor this: +def shipping_cost(country, items): + if country is None: + raise ValueError("invalid country") + + if not items: # Empty cart? + return 0 + + if country == "US": + return 0 if len(items) > 10 else 5 + + if country == "CA": + return 10 + + return 20 # Other countries diff --git a/python-best-practices/docstrings.py b/python-best-practices/docstrings.py new file mode 100644 index 0000000000..c52663334a --- /dev/null +++ b/python-best-practices/docstrings.py @@ -0,0 +1,18 @@ +# Avoid this: +# def add(a, b): +# """Return the sum of a and b.""" +# return a + b + + +# Favor this: +def add(a, b): + """Sum two numbers. + + Args: + a (int or float): The first number. + b (int or float): The second number. + + Returns: + int or float: The sum of the two numbers. + """ + return a + b diff --git a/python-best-practices/exceptions.py b/python-best-practices/exceptions.py new file mode 100644 index 0000000000..7d0106328f --- /dev/null +++ b/python-best-practices/exceptions.py @@ -0,0 +1,52 @@ +# Avoid this: +# import json + + +# def load_config(path): +# try: +# with open(path, encoding="utf-8") as config: +# data = json.load(config) +# except Exception: +# # If something went wrong, return an empty config +# return {} +# return data + + +# def main(): +# try: +# config = load_config("settings.json") +# except Exception: +# print("Sorry, something went wrong.") +# # Do something with config... + + +# Favor this: +import json +import logging + +log = logging.getLogger(__name__) + + +class ConfigError(Exception): + """Raised when issues occur with config file.""" + + +def load_config(path): + try: + with open(path, encoding="utf-8") as config: + data = json.load(config) + except FileNotFoundError as error: + raise ConfigError(f"Config file not found: {path}") from error + except json.JSONDecodeError as error: + raise ConfigError(f"Invalid JSON: {path}") from error + return data + + +def main(): + try: + config = load_config("settings.json") + print("Config:", config) + except ConfigError as error: + log.error("Error loading the config: %s", error) + print("Sorry, something went wrong while loading the settings.") + # Do something with config... diff --git a/python-best-practices/functions.py b/python-best-practices/functions.py new file mode 100644 index 0000000000..365ac12daa --- /dev/null +++ b/python-best-practices/functions.py @@ -0,0 +1,41 @@ +# Avoid this: +# import csv + +# def process_users(users, min_age, filename, send_email): +# adults = [] +# for user in users: +# if user["age"] >= min_age: +# adults.append(user) + +# with open(filename, mode="w", newline="", encoding="utf-8") as csv_file: +# writer = csv.writer(csv_file) +# writer.writerow(["name", "age"]) +# for user in adults: +# writer.writerow([user["name"], user["age"]]) + +# if send_email: +# # Emailing logic here... + +# return adults, filename + +# Favor this: +import csv + + +def filter_adult_users(users, *, min_age=18): + """Return users whose age is at least min_age.""" + return [user for user in users if user["age"] >= min_age] + + +def save_users_csv(users, filename): + """Save users to a CSV file.""" + with open(filename, mode="w", newline="", encoding="utf-8") as csv_file: + writer = csv.writer(csv_file) + writer.writerow(["name", "age"]) + for user in users: + writer.writerow([user["name"], user["age"]]) + + +def send_users_report(filename): + """Send the report.""" + # Emailing logic here... diff --git a/python-best-practices/imports.py b/python-best-practices/imports.py new file mode 100644 index 0000000000..89941272dc --- /dev/null +++ b/python-best-practices/imports.py @@ -0,0 +1,28 @@ +# Avoid this: +# from app.utils.helpers import format_date, slugify +# import requests, os, sys +# from .models import * +# import json + +# def get_data(): +# # Call the API here... + +# def build_report(data): +# # Generate report here... + + +# Favor this: +# import json +# import os +# import sys + +# import requests + +# from app.utils import helpers +# from . import models + +# def get_data(): +# # Call the API here... + +# def build_report(data): +# # Generate report here... diff --git a/python-best-practices/loggers.py b/python-best-practices/loggers.py new file mode 100644 index 0000000000..d66135d0de --- /dev/null +++ b/python-best-practices/loggers.py @@ -0,0 +1,27 @@ +# Avoid this: +# import logging + +# logging.basicConfig(level=logging.INFO) + +# def authenticate_user(username: str, password: str) -> bool: +# if username != "admin" or password != "secret": +# logging.error("Authentication failed for user %s", username) +# return False + +# logging.info("User %s authenticated successfully", username) +# return True + + +# Favor this: +import logging + +log = logging.getLogger(__name__) + + +def authenticate_user(username: str, password: str) -> bool: + if username != "admin" or password != "secret": + log.error("Authentication failed for user %s", username) + return False + + log.info("User %s authenticated successfully", username) + return True diff --git a/python-best-practices/loops.py b/python-best-practices/loops.py new file mode 100644 index 0000000000..cc656aad92 --- /dev/null +++ b/python-best-practices/loops.py @@ -0,0 +1,14 @@ +# Avoid this: +# items = ["apple", "banana", "cherry"] +# labeled = [] +# i = 0 +# while i < len(items): +# labeled.append(f"{i}: {items[i].upper()}") +# i += 1 + + +# Favor this: +items = ["apple", "banana", "cherry"] +labeled = [] +for index, item in enumerate(items): + labeled.append(f"{index}: {item.upper()}") diff --git a/python-best-practices/mutability.py b/python-best-practices/mutability.py new file mode 100644 index 0000000000..5c05670f76 --- /dev/null +++ b/python-best-practices/mutability.py @@ -0,0 +1,20 @@ +# Avoid this: +# def add_tag(tag, tags=[]): +# tags.append(tag) +# return tags + + +# add_tag("python") +# add_tag("best-practices") + + +# Favor this: +def add_tag(tag, tags=None): + if tags is None: + tags = [] + tags.append(tag) + return tags + + +add_tag("python") +add_tag("best-practices") diff --git a/python-best-practices/public_names.py b/python-best-practices/public_names.py new file mode 100644 index 0000000000..c03056f08d --- /dev/null +++ b/python-best-practices/public_names.py @@ -0,0 +1,24 @@ +# Avoid this: +# TAX_RATE = 0.20 + +# def calculate_tax(amount): +# """Return the tax for the given amount.""" +# return round_amount(amount * TAX_RATE) + +# def round_amount(amount, decimals=2): +# return round(amount, decimals) + + +# Favor this: +__all__ = ["TAX_RATE", "calculate_tax"] # Optional + +TAX_RATE = 0.20 + + +def calculate_tax(amount): + """Return the tax for the given amount.""" + return _round_amount(amount * TAX_RATE) + + +def _round_amount(amount: float, decimals=2): + return round(amount, decimals) diff --git a/python-best-practices/pythonic.py b/python-best-practices/pythonic.py new file mode 100644 index 0000000000..2479c4d961 --- /dev/null +++ b/python-best-practices/pythonic.py @@ -0,0 +1,12 @@ +# Avoid this: +# def total_length(items): +# result = 0 +# if isinstance(items, list): +# for i in range(len(items)): +# result += len(items[i]) +# return result + + +# Favor this: +def total_length(items): + return sum(len(item) for item in items) diff --git a/python-best-practices/rafactoring.py b/python-best-practices/rafactoring.py new file mode 100644 index 0000000000..87d1051bb4 --- /dev/null +++ b/python-best-practices/rafactoring.py @@ -0,0 +1,39 @@ +# Avoid this: +# def find_duplicate_emails(users): +# duplicates = [] +# seen = [] +# for user in users: +# email = user.get("email") + +# if email is None: +# print("Missing email for", user.get("id")) +# continue + +# # Check if we've seen this email before +# already_seen = False +# for index in range(len(seen)): +# if seen[index] == email: +# already_seen = True +# break + +# if already_seen: +# duplicates.append(email) +# else: +# seen.append(email) + +# print("Found", len(duplicates), "duplicate emails") +# return duplicates + + +# Favor this: +from collections import Counter + + +def _extract_emails(users): + return [user["email"] for user in users if "email" in user] + + +def find_duplicate_emails(users): + emails = _extract_emails(users) + counts = Counter(emails) + return [email for email, count in counts.items() if count > 1] diff --git a/python-best-practices/resources.py b/python-best-practices/resources.py new file mode 100644 index 0000000000..c07164f908 --- /dev/null +++ b/python-best-practices/resources.py @@ -0,0 +1,10 @@ +# Avoid this: +# def read_first_line(file_path): +# file = open(file_path, encoding="utf-8") +# return file.readline().rstrip("\n") + + +# Favor this: +def read_first_line(path): + with open(path, encoding="utf-8") as file: + return file.readline().rstrip("\n") diff --git a/python-best-practices/stdlib.py b/python-best-practices/stdlib.py new file mode 100644 index 0000000000..0418585635 --- /dev/null +++ b/python-best-practices/stdlib.py @@ -0,0 +1,16 @@ +# Avoid this: +# words = ["python", "pep8", "python", "testing"] +# counts = {} +# for word in words: +# if word in counts: +# counts[word] += 1 +# else: +# counts[word] = 1 + + +# Favor this: +from collections import Counter + +words = ["python", "pep8", "python", "testing"] +counts = Counter(words) +print(counts) diff --git a/python-best-practices/testing.py b/python-best-practices/testing.py new file mode 100644 index 0000000000..6555b3c304 --- /dev/null +++ b/python-best-practices/testing.py @@ -0,0 +1,31 @@ +# Avoid this: +# def add(a, b): +# return a + b + + +# import random + + +# def test_add(): +# a = random.randint(50, 150) +# b = random.randint(50, 150) +# result = add(a, b) +# assert 50 <= result <= 300 +# print("OK") + + +# Favor this: +# import pytest +# from calculations import add + + +# @pytest.mark.parametrize( +# "a, b, expected", +# [ +# (100, 10, 110), +# (100, 35, 135), +# (200, -50, 150), +# ], +# ) +# def test_add(a, b, expected): +# assert add(a, b) == expected diff --git a/python-best-practices/type_hint.py b/python-best-practices/type_hint.py new file mode 100644 index 0000000000..e8811b04a1 --- /dev/null +++ b/python-best-practices/type_hint.py @@ -0,0 +1,33 @@ +# Avoid this: +# import json +# from typing import Any + + +# def load_user(raw: str) -> dict[str, Any]: +# return json.loads(raw) + + +# def format_user(user: dict[str, Any]) -> str: +# return f"{user['name']} <{user['email']}>" + + +# Favor this: +import json +from typing import TypedDict + + +class UserPayload(TypedDict): + name: str + email: str + + +def load_user(raw: str) -> UserPayload: + data = json.loads(raw) + return { + "name": data["name"], + "email": data["email"], + } + + +def format_user(user: UserPayload) -> str: + return f"{user['name']} <{user['email']}>" diff --git a/python-best-practices/variables_constants.py b/python-best-practices/variables_constants.py new file mode 100644 index 0000000000..c49fefc6b4 --- /dev/null +++ b/python-best-practices/variables_constants.py @@ -0,0 +1,17 @@ +# Avoid this: +# hours = 35 + + +# def compute_net_salary(hours): +# return hours * 35 * (1 - (0.04 + 0.1)) + + +# Favor this: +hours = 35 +HOURLY_SALARY = 35 +SOCIAL_SECURITY_TAX_RATE = 0.04 +FEDERAL_TAX_RATE = 0.10 + + +def compute_net_salary(hours): + return hours * HOURLY_SALARY * (1 - (SOCIAL_SECURITY_TAX_RATE + FEDERAL_TAX_RATE)) From fc8e1327185f54f0f8552bf17ad90d59c6ec176c Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Fri, 12 Dec 2025 13:12:16 +0100 Subject: [PATCH 2/4] Fix linter issues --- python-best-practices/variables_constants.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python-best-practices/variables_constants.py b/python-best-practices/variables_constants.py index c49fefc6b4..30bb7f10ac 100644 --- a/python-best-practices/variables_constants.py +++ b/python-best-practices/variables_constants.py @@ -7,11 +7,13 @@ # Favor this: -hours = 35 HOURLY_SALARY = 35 SOCIAL_SECURITY_TAX_RATE = 0.04 FEDERAL_TAX_RATE = 0.10 +hours = 35 + def compute_net_salary(hours): - return hours * HOURLY_SALARY * (1 - (SOCIAL_SECURITY_TAX_RATE + FEDERAL_TAX_RATE)) + return hours * HOURLY_SALARY * \ + (1 - (SOCIAL_SECURITY_TAX_RATE + FEDERAL_TAX_RATE)) From 156f47d6098f5d8c8bc2b46509299f52c7b46106 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Fri, 12 Dec 2025 13:14:55 +0100 Subject: [PATCH 3/4] More fixes --- python-best-practices/variables_constants.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/python-best-practices/variables_constants.py b/python-best-practices/variables_constants.py index 30bb7f10ac..c514f3cfd9 100644 --- a/python-best-practices/variables_constants.py +++ b/python-best-practices/variables_constants.py @@ -7,13 +7,12 @@ # Favor this: -HOURLY_SALARY = 35 -SOCIAL_SECURITY_TAX_RATE = 0.04 -FEDERAL_TAX_RATE = 0.10 +# HOURLY_SALARY = 35 +# SOCIAL_SECURITY_TAX_RATE = 0.04 +# FEDERAL_TAX_RATE = 0.10 -hours = 35 +# hours = 35 -def compute_net_salary(hours): - return hours * HOURLY_SALARY * \ - (1 - (SOCIAL_SECURITY_TAX_RATE + FEDERAL_TAX_RATE)) +# def compute_net_salary(hours): + # return hours * HOURLY_SALARY * (1 - (SOCIAL_SECURITY_TAX_RATE + FEDERAL_TAX_RATE)) From 2f436c25d49992087b904dde4aaf701a23afde37 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Fri, 12 Dec 2025 13:17:43 +0100 Subject: [PATCH 4/4] Remove file --- python-best-practices/variables_constants.py | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 python-best-practices/variables_constants.py diff --git a/python-best-practices/variables_constants.py b/python-best-practices/variables_constants.py deleted file mode 100644 index c514f3cfd9..0000000000 --- a/python-best-practices/variables_constants.py +++ /dev/null @@ -1,18 +0,0 @@ -# Avoid this: -# hours = 35 - - -# def compute_net_salary(hours): -# return hours * 35 * (1 - (0.04 + 0.1)) - - -# Favor this: -# HOURLY_SALARY = 35 -# SOCIAL_SECURITY_TAX_RATE = 0.04 -# FEDERAL_TAX_RATE = 0.10 - -# hours = 35 - - -# def compute_net_salary(hours): - # return hours * HOURLY_SALARY * (1 - (SOCIAL_SECURITY_TAX_RATE + FEDERAL_TAX_RATE))