RuleScan: pass
Python
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.
Add a trust badge to your README
[](https://waxmark.app/items/python-2/scan)
---
description: 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.
globs: **/*.py
---
# 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:
```python
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:
```python
def my_function():
x = 1
y = 2
# Separate logical steps
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**:
1. Standard library imports.
2. Third-party library imports.
3. Local application/project-specific imports.
* **Alphabetical Order**: Sort imports alphabetically within each group.
* **Absolute Imports**: Prefer absolute imports over relative imports.
❌ BAD:
```python
import os, sys
from my_package.sub_module import some_function
import requests
from .another_module import another_function
```
✅ GOOD:
```python
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:
```python
class myClass: # Class name not CamelCase
def Get_Data(self): # Method name not lowercase_with_underscores
MY_VAR = 10 # Variable name not lowercase_with_underscores
return MY_VAR
```
✅ GOOD:
```python
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:
```python
def calculate_sum(a, b): # This function adds two numbers
return a + b
```
✅ GOOD:
```python
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:
```python
def process_data(data):
# ...
return len(data)
def get_user(user_id):
# ...
return {"id": user_id, "name": "Test"}
```
✅ GOOD:
```python
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:
```bash
# Installing directly into global Python environment
pip install requests black
```
✅ GOOD:
```bash
# Using Poetry
poetry new my_project
cd my_project
poetry add requests black --group dev
poetry run python my_script.py
# Using Pipenv
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:
```python
# No tests, or using unittest.TestCase directly without pytest
def add(a, b):
return a + b
# Manual testing
print(add(1, 2))
```
✅ GOOD:
```python
# src/my_package/math.py
def add(a: int, b: int) -> int:
return a + b
# tests/test_math.py
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) # type: ignore
```
## 9. Common Patterns & Anti-patterns
* **Context Managers**: Use `with` statements for resource management.
❌ BAD:
```python
f = open("file.txt", "r")
data = f.read()
f.close() # Easy to forget or miss on error
```
✅ GOOD:
```python
with open("file.txt", "r") as f:
data = f.read()
# File is automatically closed
```
* **List Comprehensions/Generator Expressions**: For concise data transformations.
❌ BAD:
```python
squares = []
for i in range(10):
squares.append(i * i)
```
✅ GOOD:
```python
squares = [i * i for i in range(10)]
```
* **F-strings**: Prefer `f-strings` for string formatting.
❌ BAD:
```python
name = "Alice"
age = 30
print("Hello, %s. You are %d years old." % (name, age))
print("Hello, {}. You are {} years old.".format(name, age))
```
✅ GOOD:
```python
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 `enum.Enum` for symbolic constants.
❌ BAD:
```python
STATUS_PENDING = "pending"
STATUS_COMPLETED = "completed"
```
✅ GOOD:
```python
from enum import Enum
class Status(Enum):
PENDING = "pending"
COMPLETED = "completed"
current_status = Status.PENDING
```
* **Avoid Mutable Default Arguments**: This is a common pitfall.
❌ BAD:
```python
def add_item(item, item_list=[]): # item_list is created once
item_list.append(item)
return item_list
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2] - unexpected!
```
✅ GOOD:
```python
from typing import List, Optional
def add_item(item: Any, item_list: Optional[List[Any]] = None) -> List[Any]:
if item_list is None:
item_list = []
item_list.append(item)
return item_list
print(add_item(1)) # [1]
print(add_item(2)) # [2]
```
* **Catch Specific Exceptions**: Never use bare `except:`.
❌ BAD:
```python
try:
# risky operation
except: # Catches ALL exceptions, including SystemExit, KeyboardInterrupt
print("An error occurred.")
```
✅ GOOD:
```python
try:
result = 1 / 0
except ZeroDivisionError:
print("Cannot divide by zero.")
except Exception as e: # Catching a broader base for unexpected errors
print(f"An unexpected error occurred: {e}")
```