ruff FURB167
labelled unknown
Analysis Nemotron 3 Super
Flags usage of short-form regex flag constants like re.I in favor of their long-form equivalents like re.IGNORECASE.
Fix: Replaces each short-form regex flag (e.g., re.I, re.L, etc.) with its corresponding descriptive long-form constant (e.g., re.IGNORECASE, re.LOCALE, etc.) while preserving all other code structure and expressions.
12 hazards
- Rewriting re.I inside a string literal like 're.I' would incorrectly modify code that is not executable, altering intended behavior.
- If re is locally aliased (e.g., import re as regex), the fix may fail to match or incorrectly rewrite references, breaking flag resolution.
- When the flag is used in a bitwise operation like re.I | re.L, rewriting only part of the expression could produce invalid syntax or incorrect flag combination.
- If re.I is accessed via getattr(re, 'I'), the fix does not apply and may leave dynamic attribute access unchanged, missing the intent.
- In a context where re is reassigned to a non-module object (e.g., re = 5), rewriting re.I would cause an AttributeError at runtime.
- The fix may insert re.IGNORECASE in a position where a comment trails the short form (e.g., re.I # inline), potentially misplacing or deleting the comment due to whitespace handling.
- If the short flag appears in a docstring or comment (e.g., "Use re.I here"), the fix could erroneously alter documentation, changing meaning without affecting execution.
- When the flag is used as a string key in a dictionary (e.g., {'re.I': value}), rewriting it changes the key, breaking lookup logic that depends on the literal string.
- In a lambda or comprehension where re is rebound locally (e.g., [re.I for re in range(5)]), the fix may misinterpret the loop variable as the module, leading to incorrect rewrites or errors.
- If re.I is imported via from re import I, the fix does not trigger on the bare I and may miss the alias, leaving inconsistent usage.
- The rewrite assumes re refers to the standard library module; if shadowed by a custom module with different attributes, the long form may not exist, causing AttributeError.
- In Python optimizations or restricted execution modes where re.IGNORECASE is not defined (though unlikely), the fix could introduce a NameError where none existed before.
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Local alias mismatch | If re is locally aliased (e.g., import re as regex), the fix may fail to match or incorrectly rewrite references, breaking flag resolution. |
| r1-p002 | silent | getattr dynamic access | If re.I is accessed via getattr(re, 'I'), the fix does not apply and may leave dynamic attribute access unchanged, missing the intent. |
| r1-p003 | silent | Dictionary string key | When the flag is used as a string key in a dictionary (e.g., {'re.I': value}), rewriting it changes the key, breaking lookup logic that depends on the literal string. |
| r1-p004 | fix ok | Dict ordering and duplicates | dict ordering, duplicate keys, __hash__/__eq__ overrides, sets |
| r1-p005 | fix ok | Truthiness and identity | truthiness of custom __bool__/__len__, None vs falsy, 'is' vs '==' |
| r1-p006 | fix ok | Bitwise operation partial rewrite | When the flag is used in a bitwise operation like re.I | re.L, rewriting only part of the expression could produce invalid syntax or incorrect flag combination. |
| r1-p007 | fix ok | Mutable default argument | mutable default arguments and aliasing after a rewrite |
| r1-p008 | fix ok | Bytes vs str encoding | bytes vs str, encoding and newline handling, text vs binary modes |
Sources
r1-p001 · Local alias mismatch
1import re as regex
2pattern = r'hello'
3string = 'HELLO world'
4if regex.search(pattern, string, regex.I):
5 print('matched')
6else:
7 print('no match')
--- orig.py 2026-10-17 17:35:20.977019168 -0400 +++ fixed.py 2026-10-17 17:35:21.166017049 -0400 @@ -1,7 +1,7 @@ import re as regex pattern = r'hello' string = 'HELLO world' -if regex.search(pattern, string, regex.I): +if regex.search(pattern, string, regex.IGNORECASE): print('matched') else: print('no match')
matched
r1-p002 · getattr dynamic access
1import re
2flag = getattr(re, 'I')
3pattern = r'hello'
4string = 'HELLO'
5print(bool(re.search(pattern, string, flag)))
True
r1-p003 · Dictionary string key
1import re
2d = {'re.I': 42}
3print(d.get('re.I', 0))
42
r1-p004 · Dict ordering and duplicates
1import re
2flags_dict = {re.I: 'ignore', re.L: 'locale', re.I: 'dup'}
3print(list(flags_dict.keys()))
4print(flags_dict[re.I])
--- orig.py 2026-10-17 17:35:21.562012611 -0400 +++ fixed.py 2026-10-17 17:35:21.743010582 -0400 @@ -1,4 +1,4 @@ import re -flags_dict = {re.I: 'ignore', re.L: 'locale', re.I: 'dup'} +flags_dict = {re.IGNORECASE: 'ignore', re.LOCALE: 'locale', re.IGNORECASE: 'dup'} print(list(flags_dict.keys())) -print(flags_dict[re.I]) +print(flags_dict[re.IGNORECASE])
[re.IGNORECASE, re.LOCALE] dup
r1-p005 · Truthiness and identity
1import re
2class FlagWrapper:
3 def __init__(self, flag):
4 self.flag = flag
5 def __bool__(self):
6 return self.flag != 0
7fw = FlagWrapper(re.I)
8print('Truthy:', bool(fw))
9print('Is zero:', fw.flag == 0)
10print('Equals I:', fw.flag == re.I)
--- orig.py 2026-10-17 17:35:21.849009394 -0400 +++ fixed.py 2026-10-17 17:35:22.029007377 -0400 @@ -4,7 +4,7 @@ self.flag = flag def __bool__(self): return self.flag != 0 -fw = FlagWrapper(re.I) +fw = FlagWrapper(re.IGNORECASE) print('Truthy:', bool(fw)) print('Is zero:', fw.flag == 0) -print('Equals I:', fw.flag == re.I) +print('Equals I:', fw.flag == re.IGNORECASE)
Truthy: True Is zero: False Equals I: True
r1-p006 · Bitwise operation partial rewrite
1import re
2flags = re.I | re.L
3pattern = r'\n'
4string = 'a\nb'
5print(re.findall(pattern, string, flags))
--- orig.py 2026-10-17 17:35:22.141006123 -0400 +++ fixed.py 2026-10-17 17:35:22.565001376 -0400 @@ -1,5 +1,5 @@ import re -flags = re.I | re.L +flags = re.IGNORECASE | re.LOCALE pattern = r'\n' string = 'a\nb' print(re.findall(pattern, string, flags))
r1-p007 · Mutable default argument
1import re
2def process(p, s, f=re.I):
3 return re.search(p, s, f)
4print(bool(process(r'h', 'H')))
--- orig.py 2026-10-17 17:35:22.795998790 -0400 +++ fixed.py 2026-10-17 17:35:22.972996809 -0400 @@ -1,4 +1,4 @@ import re -def process(p, s, f=re.I): +def process(p, s, f=re.IGNORECASE): return re.search(p, s, f) print(bool(process(r'h', 'H')))
True
r1-p008 · Bytes vs str encoding
1import re
2pattern = b'HELLO'
3string = b'hello world'
4print(re.findall(pattern, string, re.I))
--- orig.py 2026-10-17 17:35:23.085995545 -0400 +++ fixed.py 2026-10-17 17:35:23.270993476 -0400 @@ -1,4 +1,4 @@ import re pattern = b'HELLO' string = b'hello world' -print(re.findall(pattern, string, re.I)) +print(re.findall(pattern, string, re.IGNORECASE))
[b'hello']