| name | python |
| description | [Applies to: **/*.py] This guide defines the definitive Python best practices for our team, focusing on readability, maintainability, and modern development standards. Adhere to these rules for consistent, high-quality Python code. |
| source | cursor_mdc |
python Best Practices
This document outlines the definitive Python best practices for our team. Adherence ensures consistent, readable, and maintainable code across all projects. We prioritize PEP 8 as the foundation, augmented with modern tooling and patterns.
1. Code Layout & Formatting
Always adhere to PEP 8. Use an auto-formatter like Black or Ruff to enforce consistency.
- Indentation: Use 4 spaces. Never tabs.
- Line Length: Limit lines to 88 characters. Docstrings and comments should ideally wrap at 72 characters.
- Blank Lines:
- Two blank lines between top-level functions and classes.
- One blank line between methods within a class.
- One blank line to separate logical sections within functions/methods.
❌ BAD:
def my_func():
x = 1
y = 2
return x + y
class MyClass:
def __init__(self, name):
self.name = name
def greet(self):
print(f"Hello, {self.name}!")
✅ GOOD:
def my_function():
x = 1
y = 2
result = x + y
return result
class MyClass:
def __init__(self, name):
self.name = name
def greet(self):
print(f"Hello, {self.name}!")
2. Imports
Organize imports for clarity and to prevent circular dependencies. Use isort to automate this.
- Grouping:
- Standard library imports.
- Third-party library imports.
- Local application/project-specific imports.
- Alphabetical Order: Sort imports alphabetically within each group.
- Absolute Imports: Prefer absolute imports over relative imports.
❌ BAD:
import os, sys
from my_package.sub_module import some_function
import requests
from .another_module import another_function
✅ GOOD:
import os
import sys
import requests
from my_package.sub_module import some_function
from my_package.another_module import another_function
3. Naming Conventions
Follow PEP 8 naming conventions strictly.
- Modules:
lowercase_with_underscores
- Packages:
lowercase_with_underscores
- Classes:
CamelCase
- Functions/Methods:
lowercase_with_underscores
- Variables:
lowercase_with_underscores
- Constants:
UPPERCASE_WITH_UNDERSCORES
- Protected Members:
_single_leading_underscore (internal use)
- Private Members:
__double_leading_underscore (name mangling, avoid unless necessary for mixins)
❌ BAD:
class myClass:
def Get_Data(self):
MY_VAR = 10
return MY_VAR
✅ GOOD:
class MyClass:
def get_data(self):
my_var = 10
return my_var
GLOBAL_CONSTANT = 100
4. Docstrings & Comments
Document all public modules, classes, and functions using PEP 257 docstring conventions. Use reStructuredText format for Sphinx compatibility.
- Module Docstrings: Top of the file, after
__future__ imports.
- Class Docstrings: First line after the class definition.
- Function/Method Docstrings: First line after the
def statement.
- Comments: Use sparingly for why code exists, not what it does.
❌ BAD:
def calculate_sum(a, b):
return a + b
✅ GOOD:
def calculate_sum(a: int, b: int) -> int:
"""Calculate the sum of two integers.
:param a: The first integer.
:param b: The second integer.
:return: The sum of a and b.
:raises TypeError: If a or b are not integers.
"""
if not isinstance(a, int) or not isinstance(b, int):
raise TypeError("Inputs must be integers.")
return a + b
5. Type Hints
Always use type hints. They improve readability, enable static analysis with mypy, and catch errors early.
- All Function Signatures: Annotate parameters and return types.
- Variables: Annotate complex or ambiguous variable types.
typing module: Use List, Dict, Optional, Union, Callable, Any, etc.
TypeAlias: For complex type signatures.
❌ BAD:
def process_data(data):
return len(data)
def get_user(user_id):
return {"id": user_id, "name": "Test"}
✅ GOOD:
from typing import Dict, Any, List, Optional, Union, TypeAlias
UserId: TypeAlias = Union[int, str]
def process_data(data: List[str]) -> int:
"""Processes a list of strings and returns its length."""
return len(data)
def get_user(user_id: UserId) -> Optional[Dict[str, Any]]:
"""Retrieves user data by ID."""
if user_id == 1:
return {"id": 1, "name": "Alice"}
return None
6. Virtual Environments
Mandatory for all projects. Use Poetry or Pipenv for dependency management and environment isolation.
- Poetry: Recommended for new projects due to superior dependency resolution and packaging features.
- Pipenv: Acceptable for existing projects already using it.
- Never commit
venv/ directories.
❌ BAD:
pip install requests black
✅ GOOD:
poetry new my_project
cd my_project
poetry add requests black --group dev
poetry run python my_script.py
mkdir my_project && cd my_project
pipenv install requests
pipenv install black --dev
pipenv run python my_script.py
7. Packaging
Structure projects for easy distribution and installation.
src/ Layout: Place all package code inside a src/ directory.
pyproject.toml: Use this for project metadata and build configuration (PEP 621).
README.md: Comprehensive project description.
LICENSE: Clearly state the project's license.
❌ BAD:
my_project/
├── my_module.py
├── setup.py # Old style
└── requirements.txt
✅ GOOD:
my_project/
├── src/
│ └── my_package/
│ ├── __init__.py
│ └── main.py
├── pyproject.toml
├── README.md
├── LICENSE
└── tests/
└── test_main.py
8. Testing Approaches
Automated testing is non-negotiable. Use pytest for all tests.
pytest: The standard test runner.
- Coverage: Integrate
pytest-cov to ensure adequate test coverage. Aim for >90%.
- Fixtures: Use
pytest fixtures for setup and teardown.
- Parametrization: Use
pytest.mark.parametrize for testing multiple inputs.
- Mocks: Use
unittest.mock (or pytest-mock) for isolating units under test.
- Test-Driven Development (TDD): Strongly encouraged. Write tests before code.
❌ BAD:
def add(a, b):
return a + b
print(add(1, 2))
✅ GOOD:
def add(a: int, b: int) -> int:
return a + b
import pytest
from src.my_package.math import add
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
])
def test_add(a: int, b: int, expected: int):
assert add(a, b) == expected
def test_add_raises_type_error():
with pytest.raises(TypeError):
add("1", 2)
9. Common Patterns & Anti-patterns
-
Context Managers: Use with statements for resource management.
❌ BAD:
f = open("file.txt", "r")
data = f.read()
f.close()
✅ GOOD:
with open("file.txt", "r") as f:
data = f.read()
-
List Comprehensions/Generator Expressions: For concise data transformations.
❌ BAD:
squares = []
for i in range(10):
squares.append(i * i)
✅ GOOD:
squares = [i * i for i in range(10)]
-
F-strings: Prefer f-strings for string formatting.
❌ BAD:
name = "Alice"
age = 30
print("Hello, %s. You are %d years old." % (name, age))
print("Hello, {}. You are {} years old.".format(name, age))
✅ GOOD:
name = "Alice"
age = 30
print(f"Hello, {name}. You are {age} years old.")
- Caveat: Avoid complex expressions or function calls inside f-strings. Assign to a variable first.
-
Enums: Use for symbolic constants.