ruff PLW1507
labelled unknown
Analysis Nemotron 3 Super
Flags shallow copying of os.environ using copy.copy() due to mutation risks.
Fix: Replaces 'copy.copy(os.environ)' with 'os.environ.copy()' while preserving imports and surrounding context.
12 hazards
- Rewriting inside a function call argument like f(copy.copy(os.environ)) changes evaluation timing if os.environ.copy() has side effects (though it doesn't, the form differs).
- If 'copy' is locally rebound (e.g., 'copy = lambda x: x'), the fix incorrectly removes the call to the local 'copy'.
- In a comprehension like [copy.copy(os.environ) for _ in range(1)], the fix alters iteration behavior if os.environ.copy() is expensive or has side effects (it doesn't, but structurally differs).
- When the copy occurs in a lambda default argument like lambda x=copy.copy(os.environ): x, the fix changes when the copy is evaluated (at definition vs call time).
- If the code relies on the type of the copy being exactly copy._wrap type (not dict), switching to dict breaks isinstance checks against copy.copy's result type.
- In a try/except block where copy.copy might raise TypeError (if os.environ were not copyable, though it is), the fix removes the exception handling path.
- If os.environ is shadowed by a local variable before the copy, the fix still uses the global os.environ, breaking intended behavior.
- The fix may disrupt formatting-dependent tooling (e.g., linters that rely on line length) by replacing a longer expression with a shorter one.
- If the original code had a comment between 'copy.' and 'copy(os.environ)', the fix removes it during AST rewrite.
- In an augmented assignment like x += copy.copy(os.environ), the fix changes the right-hand side evaluation, potentially altering side effects if os.environ.copy() had them (it doesn't, but the form is different).
- When used in a context manager like with lock: copy.copy(os.environ), the fix does not affect the lock but changes the expression value, possibly breaking downstream logic expecting a copy proxy.
- If the module defines a class named 'environ' in os, the fix incorrectly attributes the copy to that instead of the real os.environ (though unlikely, depends on imports).
Programs
| id | result | title | hazard |
|---|---|---|---|
| r2-p005 | silent prediction off | Evaluation Order and Side Effects in Merged Expressions | Evaluation order and side effects when expressions are merged, duplicated or deleted |
Sources
r2-p005 · Evaluation Order and Side Effects in Merged Expressions
1import copy
2import os
3state = []
4class Track:
5 def __init__(self, name):
6 self.name = name
7 def __copy__(self):
8 state.append(f'copied {self.name}')
9 return self
10 def copy(self):
11 state.append(f'.copy {self.name}')
12 return self
13a = Track('a')
14b = Track('b')
15result = copy.copy(a) if state.append('eval') or True else b
16print('state:', state)
17print('result name:', result.name)
state: ['eval', 'copied a'] result name: a