[Python] 정규식의 r prefix 의미

Yongjun Park·2022년 1월 31일
2

파이썬 알고리즘 인터뷰를 읽던 중 다음과 같은 코드를 발견하였다.

def mostCommonWord(self, paragraph: str, banned: List[str]) -> str:
    words = re.sub(r'[^\w]', ' ', paragraph)
    ...

re.sub(r'[^\w]', ' ', paragraph)를 차근차근 뜯어보자.


re.sub

프로토타입

re.sub(pattern, repl, string, count=0, flags=0)
  • pattern : 정규식
  • repl : 치환할 문자열
  • string : src 문자열

기본 예제

s = 'Hello, World 2!'
s = re.sub('[^a-z0-9]', '', s)

print(s) # HelloWorld2
s = 'Hello, World 2!'
s = re.sub('[^a-z0-9]', ' ', s)

print(s) # Hello  World 2 

[^\w]

정규식의 기초 중에 기초에 대해서 알아보자.

  • [abc] : a || b || c
  • [a-c] : a ~ c ([abc]와 같다)
  • [a-zA-Z0-9] : 모든 알파벳 대/소문자와 숫자
  • . : 모든 문자 1개
    • [p.g]에는 pig, pug 모두 대응되지만, piig는 대응되지 않는다.

\

  • [\d] = [0-9]
  • [\w] = [0-9a-zA-Z]

^

not이라는 의미를 가진다.

  • [^\d] = [^0-9] = [\D]
  • [^\w] = [^0-9a-zA-Z] = [\W]

지금까지 배운 정규식은 단일 문자에 대해 각각 적용된다는 점을 명심하길 바란다.

하나 이상의 문자, 즉 문자열에 대한 패턴을 탐색하는 기법은 이번 글의 범위를 벗어났기 때문에 자세히는 설명하지 않겠다.

간단히만 설명하자면,
[abc]는 하나의 문자가 a | b | c 인지 판별하는 것이라면,
abc는 src string에 3글자 'abc'라는 문자열이 존재하는지 판별하는 것을 의미한다.

반복 패턴에 대해 더 자세히 알고 싶은 분들은 아래 링크를 참고하길 바랍니다.
1. +, *, ?에 대하여
2. 예제로 익히기


r prefix

아래 예제를 보면 아주 직관적이고 쉽게 이해할 수 있다!

print("Hello World!\n")
print(r"Hello World!\n")
>> Hello World!
>> 
>> Hello World!\n

문자열 앞에 붙이는 rraw string의 의미를 가지며, 구체적인 의미는 \탈출 문자로 보지 않고, 그냥 아무 역할도 하지 않는 평범한 문자열로 간주하여 처리하겠다는 뜻이다.

정규식에서 r prefix의 활용

Because of the way Python treats characters that are not valid escape characters, not all of those double backslashes are necessary.

For example, \s==\\s however the same is not true for \b and \\b. (출처)

혼동할 수 있는 사실이 하나 있는데, 정규식 함수에서 파싱할 때 raw string은 적용이 안되는 것으로 이해해야 하는 것이 아니라 단지 탈출문자로의 치환이 일어나지 않는 것 뿐이라는 점이다.
파이썬 탈출문자 종류

\s는 탈출문자에 해당하지 않기 때문에 \sr'\s' 사이에는 차이가 존재하지 않는다.

하지만, '\n'은 탈출문자에 해당하기 때문에 '\n'은 New Line으로 치환되는 반면,
r'\n'은 문자 그대로 '\n'으로 치환된 뒤에 정규식 함수의 pattern 문자열로 들어간다.

따라서 정규식의 pattern 문자열에서 \ 문자를 사용하는 경우에는 혹시 모를 오류를 방지하기 위해 pattern을 raw string으로 변경하는 사전 작업을 관습적으로 하는 것이다.

추가적으로 말하자면, r'[^\w]'의 예에서는 굳이 r prefix를 붙이지 않아도 문제 없이 작동하긴 한다.


종합

def mostCommonWord(self, paragraph: str, banned: List[str]) -> str:
    words = re.sub(r'[^\w]', ' ', paragraph)
    ...

의 의미는, paragraph의 각각 문자를 돌면서 [0-9a-zA-Z]에 해당하지 않는 문자가 발견된다면 그 문자를 Space Bar로 변환한다는 뜻이다. 아마 단어별로 split하기 편하게 전처리하는 작업이 아닐까 싶다.

끝내기 전에, 정규식 대응 여부를 직접 테스트하며 공부해볼 수 있는 사이트를 하나 소개한다.
https://regex101.com/


출처 : 박상길 저, 파이썬 알고리즘 인터뷰, p.151

이번 글에서 활용된 알고리즘 문제는 Leetcode 819. Most Common Word에서 볼 수 있습니다.

profile
추상화되었던 기술을 밑단까지 이해했을 때의 쾌감을 잊지 못합니다

0개의 댓글