[Python] 정규 표현식(3) : 핵심 모음

3 분 소요


정규식 관련된 문제를 풀다보니 내가 모르고 있는 부분이 많다는 느꼈다. 그래서 정규식을 잘 활용할 수 있는 기능들을 이 곳에 모아 정리해보고자 한다!


👩 Anchors: 문자열의 시작(^)과 끝($)

  ^Hello       Hello로 시작하는 모든 문자열을 매칭
  Bye$          Bye로 끝나는 모든 문자열과 매칭
  ^Hello Bye$   정확히 Hello Bye와 일치하는 문자열과 매칭


👩 Quantifier: 수량자(+*?{})

  asd+       as와 1 이상의 d를 포함한 문자열과 매칭
  asd*       as와 0 이상의 d를 포함한 문자열과 매칭
  asd?       as와 0 혹은 1개의 d를 포함한 문자열과 매칭
  a{2}sd     2개의 a 그리고 sd를 포함한 문자열과 매칭
  asd{3, }   as와 3 이상의 d를 포함한 문자열과 매칭
  asd{1, 4}  as와 1 이상 4 이하의 d를 포함한 문자열과 매칭
  a(sd)+     a와 1 이상의 sd를 포함한 문자열과 매칭 


👩 or: | & []

  [as]d   a 또는 s 그리고 d를 포함한 문자열과 매칭
  (a|s)d  위와 동일


👩 특수문자 그대로 사용하기: \

특수문자^.[$(|*+?{\를 그대로 사용하기 위해서는 문자 앞에 \(역슬래시)를 붙여 사용한다.

 '$\d'   숫자로 시작하는 문자열과 매칭
 '\$\d'  $문자와 숫자 하나를 포함하는 문자열과 매칭


👩 플래그

  re.I | re.IGNORECASE   알파벳 문자에 대해 대소문자를 구분하지 않는다.
  re.M | re.MULTILINE    문자열 전체가 아니라  별로 패턴을 매칭한다.


👩 Grouping & Capturing: ()

소괄호는 캡쳐 그룹을 생성한다. 그렇다면 캡쳐 그룹은 어떤 때 쓰이는 것일까?

str = "my computer, my mouse, my speaker, my camera, my audio"

print(re.findall('my ([a-z]+)', str))
print(re.findall('my [a-z]+', str))

캡쳐 그룹을 사용하는 것과 사용하지 않는 것의 차이를 확인해보자!!

 re.findall('my ([a-z]+)', str)  ['computer', 'mouse', 'speaker', 'camera', 'audio']
  re.findall('my [a-z]+', str)    ['my computer', 'my mouse', 'my speaker', 'my camera', 'my audio']
  • 캡쳐 그룹을 통해서 my _물건_물건들을 뽑아 올 수 있다.
  • 캡쳐를 사용하지 않는다면 my 그리고 1개 이상의 소문자를 포함한 문자열과 매칭된 결과물이 반환된다.

🙄 그렇다면 캡쳐를 하지 않고 단순히 그룹 형태의 패턴을 이용하고 싶다면 어떻게 해야 할까??

 a(sb)         캡쳐 그룹을 생성한다.
  a(?:sb)       캡쳐 그룹 생성을 무시한다.
  a(?<name>bc)  캡쳐 그룹에 이름을 지정하고 ${name} 통해 사용할  있다.
  • 캡쳐된 문자열들은 index를 통해 사용할 수 있다.
  • 그룹 이름을 지정했다면 해당 이름을 통해 사용할 수 있다.


👩 범위: []

  [asb]      a 또는 s 또는 b를 포함하는 문자열과 매칭
  [a-z]      알파벳 소문자 하나와 매칭
  [0-9]%     0 이상 9 이하 숫자 하나와 % 포함한 문자열과 매칭
  [a-zA-Z]   대소문자 상관없이 알파벳 하나와 매칭
  [^a-zA-Z]  영문이 아닌 문자와 매칭
  • ^부정표현으로 사용된다.
  • [] 안에서는 특수문자가 적용하지 않으므로 특수문자를 \ 없이, 즉 excape시키지 않고 그냥 사용할 수 있다.


👩 Greedy & Lazy match: ?

보통 +, *, {}는 가능한 최대한 많이 매칭하려고 한다. 다음의 예를 살펴보자!

  str = "<computer> <mouse> <speaker> <camera> <audio>"

  print(re.findall('<.+>', str)) # ['<computer> <mouse> <speaker> <camera> <audio>']
  print(re.findall('<.+?>', str)) # ['<computer>', '<mouse>', '<speaker>', '<camera>', '<audio>']

나는 제일 처음 <>마다 들어있는 단어를 확인하고 싶지만 위와 같이 정규식을 작성할 경우 다음과 같은 결과가 나온다.

즉, 가능한 최대한 많이 매칭되기 때문에 첫 번째 괄호에서 마지막 괄호까지 모든 문자열이 매칭되는 것이다. 이를 ?를 통해서 해결할 수 있다.

정리하면 다음과 같다!!

  <.+>    <> 사이 가능한 모든 문자와 매칭된다.
  <.+?>   <> 사이 하나 이상의 문자와 매칭되고, 가능한 짧게 매칭된다.


👩 단어 경계 메타 문자: \b & \B

asdfasbdsf as efefedsas aasa

여기서 \b단어의 경계 위치를 가리킨다. 위 문자에서 \b|asdfasbdsf| |as| |efefedsas| |aasa|이다.

\B는 \b와 반대인 a|s|d|f|a|s|b|d|s|f a|s e|f|e|f|e|d|s|a|s a|a|s|a가 된다.

  '\ba[a-z]+\b'   (asdfasbdsf) (as) efefedsas (aasa)
  '\Ba[a-z]+\B'   asdf(asbds)f as efefedsas a(as)a


👩 역참조: \1

  '([asb])\1'                 '\1'  번째 캡쳐 그룹과 동일한 패턴을 의미한다. => '([asb])([asb])'
  '([asb])([qwe])\2\1'        '\2'  번째 캡쳐 그룹과 동일하고 '\1'  번째 캡쳐 그룹과 동일하다.
  '(?<name>[a-z]+)\k<name>'   '\k<name>' 캡쳐 그룹 name과 동일한 패턴을 매칭한다.


👩 Lookaround: Lookahead(?=), Lookbehind(?<=)

Lookahead는 꼬릿말을 확인하는 용도이고, Lookbehind는 머릿말을 확인하는 용도이다!

  a(?=b)    b가 바로 뒤에 있는 a와 매칭된다. 여기서 b는 포함되지 않는다.
  (?<=a)b   a가 바로 앞에 있는 b와 매칭된다. 여기서 a는 포함되지 않는다.

여기에 부정을 합칠 수 있다.

  a(?!b)    b가 바로 뒤에 없는 a와 매칭된다. 여기서도 b는 포함되지 않는다.
  (?<=a)b   a가 바로 앞에 없는 b와 매칭된다. 여기서도 a는 포함되지 않는다.


📃 참고

댓글남기기