Как я могу сделать несколько замен с помощью регулярных выражений в Python?
Я могу использовать этот код ниже, чтобы создать новый файл с заменой a
с aa
используя регулярные выражения.
import re
with open("notes.txt") as text:
new_text = re.sub("a", "aa", text.read())
with open("notes2.txt", "w") as result:
result.write(new_text)
Мне было интересно, я должен использовать эту линию, new_text = re.sub("a", "aa", text.read())
несколько раз, но заменить строку другими буквами, которые я хочу изменить, чтобы изменить более одной буквы в моем тексте?
Это так a
->aa
,b
-> bb
а также c
-> cc
,
Поэтому я должен написать эту строку для всех букв, которые я хочу изменить, или есть более простой способ. Возможно создать "словарь" переводов. Должен ли я поместить эти буквы в массив? Я не уверен, как позвонить им, если я это сделаю.
10 ответов
Ответ, предложенный @nhahtdh, действителен, но я бы поспорил менее питонно, чем канонический пример, который использует код менее непрозрачный, чем его манипуляции с регулярным выражением, и использует преимущества встроенных структур данных Python и функции анонимных функций.
Словарь переводов имеет смысл в этом контексте. Фактически, именно так и поступает Поваренная книга Python, как показано в этом примере (скопировано с ActiveState http://code.activestate.com/recipes/81330-single-pass-multiple-replace/)
import re
def multiple_replace(dict, text):
# Create a regular expression from the dictionary keys
regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
# For each match, look-up corresponding value in dictionary
return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
if __name__ == "__main__":
text = "Larry Wall is the creator of Perl"
dict = {
"Larry Wall" : "Guido van Rossum",
"creator" : "Benevolent Dictator for Life",
"Perl" : "Python",
}
print multiple_replace(dict, text)
Так что в вашем случае вы могли бы сделать диктат trans = {"a": "aa", "b": "bb"}
а затем передать его в multiple_replace
вместе с текстом, который вы хотите перевести. По сути, все, что делает эта функция, - это создание одного огромного регулярного выражения, содержащего все ваши регулярные выражения для перевода, а затем, когда оно найдено, передача лямбда-функции в regex.sub
выполнить перевод словаря поиска.
Вы можете использовать эту функцию при чтении из вашего файла, например:
with open("notes.txt") as text:
new_text = multiple_replace(replacements, text.read())
with open("notes2.txt", "w") as result:
result.write(new_text)
Я на самом деле использовал этот точный метод в производстве, в случае, когда мне нужно было перевести месяцы года с чешского на английский для задачи поиска в Интернете.
Как указал @nhahtdh, одним из недостатков этого подхода является то, что он не свободен от префиксов: словарные ключи, являющиеся префиксами других словарных ключей, вызовут сбой метода.
Вы можете использовать группу захвата и обратную ссылку:
re.sub(r"([characters])", r"\1\1", text.read())
Поместите символы, которые вы хотите удвоить между []
, Для случая нижнего регистра a
, b
, c
:
re.sub(r"([abc])", r"\1\1", text.read())
В строке замены вы можете ссылаться на все, что соответствует группе захвата ()
с \n
запись где n
некоторое положительное целое число (0 исключено). \1
относится к первой группе захвата. Есть другое обозначение \g<n>
где n
может быть любым неотрицательным целым числом (допускается 0); \g<0>
будет ссылаться на весь текст, соответствующий выражению.
Если вы хотите удвоить все символы, кроме новой строки:
re.sub(r"(.)", r"\1\1", text.read())
Если вы хотите удвоить все символы (включая новую строку):
re.sub(r"(.)", r"\1\1", text.read(), 0, re.S)
Вы можете использовать pandas
библиотека и replace
функция. Представляю один пример с пятью заменами:
df = pd.DataFrame({'text': ['Billy is going to visit Rome in November', 'I was born in 10/10/2010', 'I will be there at 20:00']})
to_replace=['Billy','Rome','January|February|March|April|May|June|July|August|September|October|November|December', '\d{2}:\d{2}', '\d{2}/\d{2}/\d{4}']
replace_with=['name','city','month','time', 'date']
print(df.text.replace(to_replace, replace_with, regex=True))
И измененный текст:
0 name is going to visit city in month
1 I was born in date
2 I will be there at time
Вы можете найти пример здесь
Ни одно из других решений не работает, если ваши шаблоны сами по себе являются регулярными выражениями.
Для этого вам понадобится:
def multi_sub(pairs, s):
def repl_func(m):
# only one group will be present, use the corresponding match
return next(
repl
for (patt, repl), group in zip(pairs, m.groups())
if group is not None
)
pattern = '|'.join("({})".format(patt) for patt, _ in pairs)
return re.sub(pattern, repl_func, s)
Что можно использовать как:
>>> multi_sub([
... ('a+b', 'Ab'),
... ('b', 'B'),
... ('a+', 'A.'),
... ], "aabbaa") # matches as (aab)(b)(aa)
'AbBA.'
Обратите внимание, что это решение не позволяет вам помещать группы захвата в регулярные выражения или использовать их для замены.
Используя советы о том, как создать "строковый" класс, мы можем сделать объект идентичным строке, но для дополнительного sub
метод:
import re
class Substitutable(str):
def __new__(cls, *args, **kwargs):
newobj = str.__new__(cls, *args, **kwargs)
newobj.sub = lambda fro,to: Substitutable(re.sub(fro, to, newobj))
return newobj
Это позволяет использовать шаблон компоновщика, который выглядит лучше, но работает только для заранее определенного числа замен. Если вы используете его в цикле, больше нет смысла создавать дополнительный класс. Например
>>> h = Substitutable('horse')
>>> h
'horse'
>>> h.sub('h', 'f')
'forse'
>>> h.sub('h', 'f').sub('f','h')
'horse'
Я обнаружил, что мне пришлось изменить код Эммета Дж. Батлера, изменив лямбда-функцию для использования myDict.get(mo.group (1), mo.group (1)). Оригинальный код не работал для меня; использование myDict.get() также обеспечивает преимущество значения по умолчанию, если ключ не найден.
OIDNameContraction = {
'Fucntion':'Func',
'operated':'Operated',
'Asist':'Assist',
'Detection':'Det',
'Control':'Ctrl',
'Function':'Func'
}
replacementDictRegex = re.compile("(%s)" % "|".join(map(re.escape, OIDNameContraction.keys())))
oidDescriptionStr = replacementDictRegex.sub(lambda mo:OIDNameContraction.get(mo.group(1),mo.group(1)), oidDescriptionStr)
Если вы имеете дело с файлами, у меня есть простой код Python об этой проблеме. Больше информации здесь.
import re
def multiple_replace(dictionary, text):
# Create a regular expression from the dictionaryary keys
regex = re.compile("(%s)" % "|".join(map(re.escape, dictionary.keys())))
# For each match, look-up corresponding value in dictionaryary
String = lambda mo: dictionary[mo.string[mo.start():mo.end()]]
return regex.sub(String , text)
if __name__ == "__main__":
dictionary = {
"Wiley Online Library" : "Wiley",
"Chemical Society Reviews" : "Chem. Soc. Rev.",
}
with open ('LightBib.bib', 'r') as Bib_read:
with open ('Abbreviated.bib', 'w') as Bib_write:
read_lines = Bib_read.readlines()
for rows in read_lines:
#print(rows)
text = rows
new_text = multiple_replace(dictionary, text)
#print(new_text)
Bib_write.write(new_text)
Я не знаю, почему большинство решений пытаются составить один шаблон регулярного выражения вместо многократной замены. Этот ответ только для полноты.
При этом вывод этого подхода отличается от вывода комбинированного подхода регулярных выражений. А именно, повторные замены могут изменять текст с течением времени. Однако следующая функция возвращает тот же результат, что и вызов unix sed:
def multi_replace(rules, data: str) -> str:
ret = data
for pattern, repl in rules:
ret = re.sub(pattern, repl, ret)
return ret
Применение:
RULES = [
(r'a', r'b'),
(r'b', r'c'),
(r'c', r'd'),
]
multi_replace(RULES, 'ab') # output: dd
При тех же входных данных и правилах другие решения выведут «bc». В зависимости от вашего варианта использования вы можете заменять строки последовательно или нет. В моем случае я хотел перестроить поведение sed. Также обратите внимание, что порядок правил имеет значение. Если вы измените порядок правил, этот пример также вернет «bc».
Это решение быстрее, чем объединение шаблонов в одно регулярное выражение (в 100 раз). Итак, если ваш вариант использования позволяет это, вы должны предпочесть метод повторной замены.
Конечно, вы можете скомпилировать шаблоны регулярных выражений:
class Sed:
def __init__(self, rules) -> None:
self._rules = [(re.compile(pattern), sub) for pattern, sub in rules]
def replace(self, data: str) -> str:
ret = data
for regx, repl in self._rules:
ret = regx.sub(repl, ret)
return ret
В моем контексте я могу использовать только выражения и re.sub, поэтому вот однострочное решение (фактически полученное из других решений):
import re
xx = "a&b<c"
yy = re.sub('&((amp)|(lt));', lambda x: ' and ' if x.group(2) else (' less ' if x.group(3) else ''), xx)
Основываясь на отличном ответе Эрика , я придумал более общее решение, способное обрабатывать группы захвата и обратные ссылки:
import re
from itertools import islice
def multiple_replace(s, repl_dict):
groups_no = [re.compile(pattern).groups for pattern in repl_dict]
def repl_func(m):
all_groups = m.groups()
# Use 'i' as the index within 'all_groups' and 'j' as the main
# group index.
i, j = 0, 0
while i < len(all_groups) and all_groups[i] is None:
# Skip the inner groups and move on to the next group.
i += (groups_no[j] + 1)
# Advance the main group index.
j += 1
# Extract the pattern and replacement at the j-th position.
pattern, repl = next(islice(repl_dict.items(), j, j + 1))
return re.sub(pattern, repl, all_groups[i])
# Create the full pattern using the keys of 'repl_dict'.
full_pattern = '|'.join(f'({pattern})' for pattern in repl_dict)
return re.sub(full_pattern, repl_func, s)
Пример. Вызов выше с
s = 'This is a sample string. Which is getting replaced. 1234-5678.'
REPL_DICT = {
r'(.*?)is(.*?)ing(.*?)ch': r'\3-\2-\1',
r'replaced': 'REPLACED',
r'\d\d((\d)(\d)-(\d)(\d))\d\d': r'__\5\4__\3\2__',
r'get|ing': '!@#'
}
дает:
>>> multiple_replace(s, REPL_DICT)
'. Whi- is a sample str-Th is !@#t!@# REPLACED. __65__43__.'
Для более эффективного решения можно создать простую оболочку для предварительного вычисленияgroups_no
иfull_pattern
, например
import re
from itertools import islice
class ReplWrapper:
def __init__(self, repl_dict):
self.repl_dict = repl_dict
self.groups_no = [re.compile(pattern).groups for pattern in repl_dict]
self.full_pattern = '|'.join(f'({pattern})' for pattern in repl_dict)
def get_pattern_repl(self, pos):
return next(islice(self.repl_dict.items(), pos, pos + 1))
def multiple_replace(self, s):
def repl_func(m):
all_groups = m.groups()
# Use 'i' as the index within 'all_groups' and 'j' as the main
# group index.
i, j = 0, 0
while i < len(all_groups) and all_groups[i] is None:
# Skip the inner groups and move on to the next group.
i += (self.groups_no[j] + 1)
# Advance the main group index.
j += 1
return re.sub(*self.get_pattern_repl(j), all_groups[i])
return re.sub(self.full_pattern, repl_func, s)
Используйте его следующим образом:
>>> ReplWrapper(REPL_DICT).multiple_replace(s)
'. Whi- is a sample str-Th is !@#t!@# REPLACED. __65__43__.'