전체 페이지뷰

2016년 12월 6일 화요일

Regular expression with Python, look behind

look behind


Positive look behind(긍정형 후방 탐색)

Look behind는 look ahead와 정확히 반대방향의 연산이라고 생각하시면 됩니다.
앞서 말한 것과 같이 (?<=regex)의 문법으로 표시됩니다.

이번에는 McClane 이라는 성을 갖는 John을 찾는 예를 들어서 설명해 보겠습니다.

>>> pattern = re.compile(r'(?<=John\s)McLane')
>>> result = pattern.finditer("I would rather go out with John McLane than with John Smith or John Bon Jovi")
>>> for i in result:
print(i.start(), i.end())

32 38

?<=John\s : John+공백이 있는 패턴이 나타나면 후방에서 McClane을 매칭시키라..

그래서 McClane의 인덱스인 32,38이 나타납니다.

그런데 파이썬 re 모듈에서는 전방탐색과 후방탐색에 큰 차이가 있습니다. 기술적인 문제로 후방탐색은 길이가 정해진 패턴에서만 사용 가능합니다.(길이가 정해지지 않은 패턴에 후방 탐색을 사용하려면 re 모듈 보다는 https://pypi.python.org/pypi/regex 를 참고하시기 바랍니다)

후방 탐색 시 길이가 정해져 있는 패턴에만 사용 가능하므로, 뒤 쪽 길이에 영향을 주는 quntifier나 backreference도 사용할 수 없다는 뜻입니다. 변형은 가능하나 길이가 똑같은 경우에 한정됩니다.

길이가 다른 패턴에 사용하려면 어떻게 되나 보겠습니다.
>>> pattern = re.compile(r'(?<=(John|Jonathan)\s)McLane')
Traceback (most recent call last):
  Fil...

    raise error("look-behind requires fixed-width pattern")
sre_constants.error: look-behind requires fixed-width pattern

후방검색에 길이가 다른 두 리터럴을 사용했으므로 예외가 발생했습니다.

자, 이제 후방검색을 실제 현실의 문제를 푸는데 사용하는 예를 들어봅시다.

어떤 트윗 상에 존재하는 Twitter 사용자명을 추출하고 싶다고 해 봅시다. 정규식을 사용하기 위해서는 트위터 사용자명이 어떻게 표현되는지 알아야 합니다. 트위터 서포트 사이트에서 다음과 같은 내용을 찾았습니다.

"A username can only contain alphanumeric characters (letters A-Z, numbers 0-9)
with the exception of underscores, as noted above. Check to make sure your desired
username doesn't contain any symbols, dashes, or spaces."

오직 영숫자만 허용되면 일체의 공백, 심볼 등이 허용되지 않는 것을 알 수 있습니다.

책에 나온 Packt Publishing Tweet을 보겠습니다.

먼저 사용자명에 해당하는 캐릭터셋을 구성합니다.
[\w_]
언더스코어로 이어지는 영문자입니다. 그리고 단어 경계와 @심볼을 넣어 특정해줍니다.
\B@[\w_]+
단어 경계를 넣은 이유는 이메일같은 것들과 혼동되지 않게 하기 위해서입니다.
(\B와 \b의 차이를 알고 싶으신 분은 여기를 참고하세요)
이 기준에 따르면,

@vromer0 은 유효
iam@vromer0 은 무효
@vromer0.org 는 무효가 됩니다.

>>> pattern = re.compile(r'\B@[\w_]+')
>>> pattern.findall("Know your Big Data = 5 for $50 on eBooks and 40% off all eBooks until Friday #bigdata #hadoop @HadoopNews packtpub.com/bigdataoffers")
['@HadoopNews']

그러나 우리는 @심볼을 제외한 리터럴을 얻고 싶습니다. 이 시점에서 후방검색이 유용합니다.

>>> pattern = re.compile(r'(?<=\B@)[\w_]+')
>>> pattern.findall("Know your Big Data = 5 for $50 on eBooks and 40% off all eBooks until Friday #bigdata #hadoop @HadoopNews packtpub.com/bigdataoffers")
['HadoopNews']

Negative look behind(부정형 후방 탐색)

주어진 정규식에 매치되지 않을 때만 후방을 탐색합니다.
(?<!regex) 의 식으로 표현됩니다.

메카니즘과 제한점까지도 긍정형 후방탐색과 같습니다.
성이 Doe인 사람 중에 이름이 John이 아닌 사람만을 검색하는 예를 들어 봅시다.

>>> pattern = re.compile(r'(?<!John\s)Doe')
>>> results = pattern.finditer("John Doe, Calvin Doe, Hobbes Doe")
>>> for result in results:
print (result.start(), result.end())

17 20
29 32

Look around and groups

Group내에서 look around를 이용할 때 또 이점이 있습니다. 우리는 필요하지 않은 정보로 그룹을 오염시키길 원치 않기 때문에, look around를 아주 좋은 수단으로 쓸 수 있습니다.

이런 정보가 있다고 해봅시다.
INFO 2013-09-17 12:13:44,487 authentication failed
끝부분 영문자 부분의 정보를 필요로 할 때,

\w+\s[\d-]+\s[\d:,]+\s(.*\sfailed)

와 같이 작성해 볼수 있습니다. 끝부분 영문이 authentication failed 일 때와 다른 fail 일때를 구분해 보고자 한다면,

\w+\s[\d-]+\s[\d:,]+\s(.*(?<!authentication\s)failed)

라고 바꿔볼 수 있습니다. Authentication이 아닐 경우에만 failed를 매칭시킨다는 말입니다.

>>> pattern = re.compile(r'\w+\s[\d-]+\s[\d:,]+\s(.*(?<!authentication\s)failed)')
>>> pattern.findall("INFO 2013-09-17 12:13:44,487 authentication failed")
[]
>>> pattern.findall("INFO 2013-09-17 12:13:44,487 something else failed")
['something else failed']


댓글 없음:

댓글 쓰기