Как правильно использовать re.sub для поиска и замены слов в файле

ЗАДАЧА

Заменить в файле все слова, которые начинаются с 'l_' или 's_' или 'i_' или 'b_' или 'h_' или 'sA_' или 'iA_' или 'lA_' или 'bA_' или 'nA_' или 'f' или 'n' (пример - l_ChangePlane) на случайный набор английских букв. Сохранив (если имеется) "<< >>", "()". Но если в слове присутствует "@usr_", то надо сохранить "@". Нельзя изменять слова "job_name" и "blknum_f" Если слова повторяются, они должны быть заменены одним "шифром" .

ПРИМЕРЫ

Было: iA_Hours<<i_DefTool>>
Должно стать: poiuybrntfsgybrn<< pqbcfrzdenmkgcasr>>

Было: @usr_SpecCharChange(job_name)
Должно стать: @oplaghftybndrtcy(job_name)

Что не так?

СЛОВА, НАЧИНАЮЩИЕСЯ С "l_", "s_", "i_" и т.д. ОСТАЮТСЯ НЕЗАМЕНЁННЫМИ Было: Ai_hours
Должно стать: jhfytryqnegtfgco (или другой рандомный набор букв) .

КОД


import random
import re

changed_vars = dict()
special_words = ['blknum_f', 'job_name']
prefixes = ['l_', 's_', 'i_', 'b_', 'h_', 'sA_', 'iA_', 'lA_', 'bA_', 'nA_', 'n_']
suffixes = ['_f']


def generate_word(length=16):
    alphabet = 'qwertyuiopasdfghjklzxcvbnm'
    return ''.join(random.choice(alphabet) for _ in range(length))

# Заменяет переменную на случайное слово, если она не была заменена ранее.
def replace_var(var, changed_vars):
    if var.startswith('@usr_'):
        base = '@'
        var = var[1:]  # удаляем @, чтобы не заменять его
    else:
        base = ''

    if var not in changed_vars:
        changed_vars[var] = generate_word()

    return base + changed_vars[var]

# Обрабатывает строку, заменяя переменные внутри кавычек "<<>>", круглых скобок "()", а также переменные, соответствующие условиям, и переменные, начинающиеся с "@usr_".
def process_line(line, changed_vars):
    def replacer(match):
        var = match.group(0)
        return replace_var(var, changed_vars)

    if line not in special_words:
    
    # Обрабатываем переменные с кавычками << >>
        line = re.sub(r'<<.*?>>', lambda m: '<<' + replace_var(m.group(0)[2:-2], changed_vars) + '>>', line)

    # Обрабатываем переменные в круглых скобках
        line = re.sub(r'\(.*?\)', lambda m: '(' + replace_var(m.group(0)[1:-1], changed_vars) + ')', line)

    # Обрабатываем переменные, соответствующие условиям
     # ЭТО УСЛОВИЕ НЕ ВЫПОЛНЯЕТСЯ И СЛОВА, НАЧИНАЮЩИЕСЯ С "l_", "s_", "i_" и т.д. ОСТАЮТСЯ НЕЗАМЕНЁННЫМИ 
    line = re.sub(r"(?<!@usr_)(\bl_\b|\bs_\b|\bi_\b|\bb_\b|\bh_\b|\bsA_\b|\biA_\b|\blA_\b|\bbA_\b|\bnA_\b|\b_f\b|\bn_\b)", replacer, line)
        
    # Обрабатываем переменные, начинающиеся с @usr_
        line = re.sub(r'@usr_\w+', replacer, line)

        return line


result = []
changed_vars = dict()
with open("путь", "r", encoding="utf-8") as file:
    for line in file:
        processed_line = process_line(line, changed_vars)
        result.append(processed_line)

with open("путь", 'w', encoding="utf-8") as file:
    file.writelines(result)
    print(result)

Для поиска и замены переменных использованы регулярные выражения (re.sub).


Ответы (1 шт):

Автор решения: Serhii Fomenko

Ваш код не работает потому, что ваше регулярное выражение неправильное:

r"(?<!@usr_)(\bl_\b|\bs_\b|\bi_\b|\bb_\b|\bh_\b|\bsA_\b|\biA_\b|\blA_\b|\bbA_\b|\bnA_\b|\b_f\b|\bn_\b)"

Если это расшифровать: <граница слова><одно из условий><граница слова>. Оно будет совпадать с любым словом, которое равно условию, но не со словом, где после условия есть другие буквы.

Например, эти слова - s_, sA_ будут заменены, но не эти s_hello, sA_hi.

Вам необходимо использовать что-то вроде этого:

r'\b(l_|s_|i_|b_|h_|sA_|iA_|lA_|bA_|nA_|n_|_f|usr_)[a-zA-Z]*\b'

Что можно расшифровать: <граница слова><одно из условий><остальные буквы или пустая строка><граница слова>.

И, наконец, итоговый результат:

import random
import re
import string

search_pattern = re.compile(
    r'\b(l_|s_|i_|b_|h_|sA_|iA_|lA_|bA_|nA_|n_|_f|usr_)[a-zA-Z]*\b'
)


def replace(target_string: str) -> str:
    def replacer(match_obj: re.Match) -> str:
        key = match_obj.group()
        sub_str_len = len(match_obj.group())
        if key not in cache:
            cache[key] = ''.join(
                random.choices(string.ascii_lowercase, k=sub_str_len)
            )
        return cache[key]

    cache = {}
    return search_pattern.sub(replacer, target_string)
→ Ссылка