# Python code review checklist

> Python code review checklist of the mistakes Revix checks for in pull requests, from swallowed exceptions to log messages formatted too early.

Canonical URL: https://www.revix.ai/checklists/python

Python code review often misses code that hides failures. This Python PR checklist covers the mistakes Revix checks for in pull requests. Look for a broad except that swallows errors, a log message formatted before it is needed, and Any used where a precise type fits. Each rule shows the code to flag, the fix and its source.

Author: Oscar Estudillo · Updated: 2026-10-30

## 11 rules to check in every Python pull request

### Avoid bare except or broad except Exception that swallows errors

Broad handlers mask programming errors such as NameError or TypeError and make failures surface far from their cause.

**Avoid**

```python
try:
    value = int(raw)
except:
    value = 0
```

**Prefer**

```python
try:
    value = int(raw)
except ValueError:
    value = 0
```

Flagged by Revix in real reviews · Anti-pattern · All current versions · Source: [8.5. Exception Chaining](https://docs.python.org/3/tutorial/errors.html)

### Pre-formatting log messages defeats deferred formatting

Eager formatting wastes CPU in hot paths where debug logging is disabled in production.

**Avoid**

```python
logger.debug(f'user {user!r} loaded')
```

**Prefer**

```python
logger.debug('user %r loaded', user)
```

Flagged by Revix in real reviews · Performance · All current versions · Source: [Logging HOWTO](https://docs.python.org/3/howto/logging.html)

### Avoid Any where a precise or object type fits

Annotating with Any silently removes the protection the type checker provides at the call sites that receive the value.

**Avoid**

```python
def load(x: Any) -> str: return x
```

**Prefer**

```python
def load(x: object) -> str: return str(x)
```

Flagged by Revix in real reviews · Anti-pattern · All current versions · Source: [typing — Support for type hints](https://docs.python.org/3/library/typing.html)

### Return Self instead of the base class name

Using the class name loses the subclass type for chained calls and alternative constructors.

**Avoid**

```python
def set_name(self, n: str) -> 'Foo': self.n = n; return self
```

**Prefer**

```python
def set_name(self, n: str) -> Self: self.n = n; return self
```

Flagged by Revix in real reviews · Best practice · All current versions · Source: [typing — Support for type hints](https://docs.python.org/3/library/typing.html)

### Avoid tempfile.mktemp (race-prone)

The gap between naming and creating a file allows a symlink or pre-creation attack on the path.

**Avoid**

```python
import tempfile
path = tempfile.mktemp()
open(path, 'w').write(data)
```

**Prefer**

```python
import tempfile
f = tempfile.NamedTemporaryFile(delete=False)
f.write(data)
f.close()
```

Security · All current versions · Source: [tempfile — SpooledTemporaryFile](https://docs.python.org/3/library/tempfile.html)

### Do not pass untrusted input to getoutput or getstatusoutput

These helpers give up the argument-list safety of subprocess and are unsafe with dynamic input.

**Avoid**

```python
subprocess.getoutput('ls ' + path)
```

**Prefer**

```python
subprocess.check_output(['ls', path], text=True)
```

Security · All current versions · Source: [subprocess - Timeout Behavior](https://docs.python.org/3/library/subprocess.html)

### Do not store passwords in recoverable form

Recoverable password storage turns any database leak into an immediate account compromise.

**Avoid**

```python
user.password = request.form['password']
user.save()
```

**Prefer**

```python
import hashlib, os
salt = os.urandom(16)
user.password_hash = hashlib.pbkdf2_hmac('sha256', pw.encode(), salt, 600000)
```

Security · All current versions · Source: [secrets — Generate secure random numbers for managing secrets](https://docs.python.org/3/library/secrets.html)

### Passing non-literal strings to LiteralString parameters

Sensitive APIs typed with LiteralString rely on that guarantee to prevent injection.

**Avoid**

```python
run_query(f'SELECT * FROM t WHERE n = {name}')
```

**Prefer**

```python
cursor.execute('SELECT * FROM t WHERE n = ?', (name,))
```

Security · All current versions · Source: [typing — Support for type hints](https://docs.python.org/3/library/typing.html)

### Serializing NaN or Infinity produces invalid JSON

The default silently produces output that is not valid JSON, so the error appears far from the source, often in a downstream consumer.

**Avoid**

```python
payload = json.dumps({'avg': float('nan')})
```

**Prefer**

```python
payload = json.dumps({'avg': avg}, allow_nan=False)
```

Security · All current versions · Source: [json — JSON encoder and decoder](https://docs.python.org/3/library/json.html)

### strict=False admits control characters

Relaxed parsing of untrusted input weakens the validity guarantee the decoder otherwise gives.

**Avoid**

```python
json.JSONDecoder(strict=False).decode(body)
```

**Prefer**

```python
json.JSONDecoder().decode(body)
```

Security · All current versions · Source: [json — JSON encoder and decoder](https://docs.python.org/3/library/json.html)

### Unpickling untrusted data

Unpickling attacker-controlled bytes gives the attacker code execution in your process.

**Avoid**

```python
obj = pickle.loads(request.body)
```

**Prefer**

```python
obj = json.loads(request.body)  # or verify an hmac signature before pickle.loads
```

Security · All current versions · Source: [pickle - Python object serialization](https://docs.python.org/3/library/pickle.html)
