43.4 문자열 바꾸기

이번에는 정규표현식으로 특정 문자열을 찾은 뒤 다른 문자열로 바꾸는 방법을 알아보겠습니다. 문자열을 바꿀 때는 sub 함수를 사용하며 패턴, 바꿀 문자열, 문자열, 바꿀 횟수를 넣어줍니다. 여기서 바꿀 횟수를 넣으면 지정된 횟수만큼 바꾸며 바꿀 횟수를 생략하면 찾은 문자열을 모두 바꿉니다.

  • re.sub('패턴', '바꿀문자열', '문자열', 바꿀횟수)

다음은 문자열에서 'apple' 또는 'orange'를 찾아서 'fruit'로 바꿉니다.

>>> re.sub('apple|orange', 'fruit', 'apple box orange tree')    # apple 또는 orange를 fruit로 바꿈
'fruit box fruit tree'

또는, 문자열에서 숫자만 찾아서 다른 문자로 바꿀 수도 있겠죠? 다음은 숫자만 찾아서 'n'으로 바꿉니다.

>>> re.sub('[0-9]+', 'n', '1 2 Fizz 4 Buzz Fizz 7 8')    # 숫자만 찾아서 n으로 바꿈
'n n Fizz n Buzz Fizz n n'

sub 함수는 바꿀 문자열 대신 교체 함수를 지정할 수도 있습니다. 교체 함수는 매개변수로 매치 객체를 받으며 바꿀 결과를 문자열로 반환하면 됩니다. 다음은 문자열에서 숫자를 찾은 뒤 숫자를 10배로 만듭니다.

  • 교체함수(매치객체)
  • re.sub('패턴', 교체함수, '문자열', 바꿀횟수)
>>> def multiple10(m):        # 매개변수로 매치 객체를 받음
...     n = int(m.group())    # 매칭된 문자열을 가져와서 정수로 변환
...     return str(n * 10)    # 숫자에 10을 곱한 뒤 문자열로 변환해서 반환
...
>>> re.sub('[0-9]+', multiple10, '1 2 Fizz 4 Buzz Fizz 7 8')
'10 20 Fizz 40 Buzz Fizz 70 80'

mutiple10 함수에서 group 메서드로 매칭된 문자열을 가져와서 정수로 바꿉니다. 그리고 숫자에 10을 곱한 뒤 문자열로 변환해서 반환했습니다.

교체 함수의 내용이 간단하다면 다음과 같이 람다 표현식을 만들어서 넣어도 됩니다.

>>> re.sub('[0-9]+', lambda m: str(int(m.group()) * 10), '1 2 Fizz 4 Buzz Fizz 7 8')
'10 20 Fizz 40 Buzz Fizz 70 80'

43.4.1  찾은 문자열을 결과에 다시 사용하기

이번에는 정규표현식으로 찾은 문자열을 가져와서 결과에 다시 사용해보겠습니다. 먼저 정규표현식을 그룹으로 묶습니다. 그러고 나면 바꿀 문자열에서 \\숫자 형식으로 매칭된 문자열을 가져와서 사용할 수 있습니다.

  • \\숫자

다음은 'hello 1234'에서 hello는 그룹 1, 1234는 그룹 2로 찾은 뒤 그룹 2, 1, 2, 1 순으로 문자열의 순서를 바꿔서 출력합니다.

>>> re.sub('([a-z]+) ([0-9]+)', '\\2 \\1 \\2 \\1', 'hello 1234')    # 그룹 2, 1, 2, 1 순으로 바꿈
'1234 hello 1234 hello'

이번에는 조금 더 응용해보겠습니다. 다음은 '{ "name": "james" }''<name>james</name>' 형식으로 바꿉니다.

>>> re.sub('({\s*)"(\w+)":\s*"(\w+)"(\s*})', '<\\2>\\3</\\2>', '{ "name": "james" }')
'<name>james</name>'

외계어처럼 보이지만 부분부분 나눠서 보면 어렵지 않습니다. 이 정규표현식에서 그룹은 4개입니다. 맨 처음 ({\s*){와 공백을 찾으므로 '{ '을 찾습니다. 그리고 마지막 (\s*})은 공백과 }를 찾으므로 ' }'를 찾습니다. 중간에 있는 "(\w+)":\s*"(\w+)":을 기준으로 양 옆의 namejames를 찾습니다. 바꿀 문자열은 '<\\2>'과 같이 그룹 2 name과 그룹 3 james만 사용하고 그룹 1 '{ ', 그룹 4 '} '는 버립니다.

그림 43-1 정규 표현식으로 찾은 문자열을 사용

만약 그룹에 이름을 지었다면 \\g<이름> 형식으로 매칭된 문자열을 가져올 수 있습니다(\\g<숫자> 형식으로 숫자를 지정해도 됩니다).

  • \\g<이름>
  • \\g<숫자>
>>> re.sub('({\s*)"(?P<key>\w+)":\s*"(?P<value>\w+)"(\s*})', '<\\g<key>>\\g<value></\\g<key>>', '{ "name": "james" }')
'<name>james</name>'

지금까지 정규표현식에 대해 배웠습니다. 정규표현식은 특수 문자가 많이 쓰이고, 복잡해 보여서 많은 사람들이 어려워하는 분야입니다. 지금 당장은 모든 내용을 외울 필요는 없습니다. 나중에 정규표현식이 필요할 때 다시 돌아와서 찾아보면 됩니다. 여기서 소개한 정규표현식 패턴은 핵심 정리에 정리되어 있습니다.

참고 | raw 문자열 사용하기

정규표현식의 특수 문자를 판단하려면 \를 붙여야 합니다. 여기서 문자열 앞에 r을 붙여주면 원시(raw) 문자열이 되어 \를 붙이지 않아도 특수 문자를 그대로 판단할 수 있습니다. 따라서 raw 문자열에서는 \\숫자, \\g<이름>, \\g<숫자>\숫자, \g<이름>, \g<숫자> 형식처럼 \를 하나만 붙여서 사용할 수 있습니다.

r'\숫자 \g<이름> \g<숫자>'

>>> re.sub('({\s*)"(\w+)":\s*"(\w+)"(\s*})', r'<\2>\3</\2>', '{ "name": "james" }')
'<name>james</name>'