오늘 배울 룩어라운드(주변을 엿보기)는 정규식에서 가장 신기하면서도 실전에서 자주 쓰는 기술이에요. 한마디로 "이 뒤에 무엇이 오는지", "이 앞에 무엇이 있는지"를 조건으로만 확인하고, 정작 그 부분은 결과에 포함하지 않는 거예요. 예를 들어 5000원에서 "뒤에 원이 붙은 숫자"만 골라내되 숫자만 가져오고 은 빼는, 그런 야무진 일을 해줘요. 말로만 들으면 아리송하니 예제로 바로 보여드릴게요. 완전 처음이어도 따라 치면 "아, 이래서 편하구나" 하실 거예요.

15.1 룩어라운드가 뭐예요?

지금까지 배운 정규식은 매치되는 글자를 먹으면서(소비하면서) 지나갔어요. \d+5000을 잡으면 그 5000은 결과에 들어가죠. 그런데 룩어라운드는 조건은 확인하되 그 부분을 안 먹어요. "여기 뒤에 이런 게 있나?"만 슬쩍 보고, 있으면 통과, 없으면 탈락시켜요. 확인만 하고 지나가니 결과에는 안 남는 거예요.


왜 이게 좋은지 예로 감을 잡아볼게요. 만약 룩어라운드 없이 \d+원이라고 쓰면 매치 결과에 까지 딸려 들어와요. 그럼 나중에 을 또 잘라내야 하죠. 반면 룩어라운드로 "뒤에 원이 있는지"만 확인하면, 조건은 맞추면서도 결과에는 숫자만 남아요. 이렇게 "조건은 걸되 결과는 깨끗하게"가 룩어라운드의 핵심 매력이에요. 조건에 쓰인 글자는 다음 매치를 찾을 때 다시 검사 대상이 될 수 있어서, 겹쳐서 확인하는 일도 가능해져요.


종류는 크게 둘로 나뉘어요. 뒤쪽을 보는 전방탐색과 앞쪽을 보는 후방탐색이에요. 각각 "있어야 함"(긍정)과 "없어야 함"(부정) 버전이 있고요. 표기를 정리하면 전방 긍정은 (?=...), 전방 부정은 (?!...), 후방 긍정은 (?<=...), 후방 부정은 (?<!...)예요. 물음표와 등호, 느낌표의 조합이라 처음엔 헷갈리는데, 예제로 하나씩 만나보면 자연스럽게 익어요.

15.2 뒤에 특정 글자가 오는 것만 (전방탐색)

[정규식 15] 앞뒤를 엿보는 룩어라운드

상황을 볼게요. 5000원처럼 뒤에 이 붙은 숫자만, 그것도 숫자 부분만 뽑고 싶어요. 패턴은 이래요.


\d+(?=원)


\d+는 숫자 덩어리를 잡는 부분이고, 그 뒤의 (?=원)이 전방탐색이에요. "이 숫자 바로 뒤에 원이 있어야 해. 단, 그 원은 결과에 안 넣어"라는 뜻이에요. 대상은 가격 5000원 그리고 300개예요. 실행해볼게요.


import re


re.findall(r'\d+(?=원)', '가격 5000원 그리고 300개')


결과는 이래요.


['5000']


5000만 깔끔하게 나왔죠? 은 조건으로만 확인됐을 뿐 결과엔 안 들어갔어요. 300은 뒤가 라서 "원이 있어야 함" 조건에 걸려 탈락했고요. 만약 전방탐색 없이 \d+원이라고 썼다면 5000원이 통째로 잡혀서 을 또 떼어내야 했을 거예요. 룩어라운드가 그 수고를 없애준 거죠.

15.3 앞에 특정 글자가 오는 것만 (후방탐색)

이번엔 반대로 을 보는 경우예요. 상황은 $50처럼 달러 기호 뒤의 숫자만 잡되, $는 빼고 싶어요. 패턴은 이래요.


(?<=\$)\d+


앞의 (?<=\$)가 후방탐색이에요. "이 숫자 바로 앞에 달러 기호가 있어야 해. 단, 그 기호는 결과에 안 넣어"라는 뜻이에요. 여기서 \$의 역슬래시는 $가 정규식에서 특별한 뜻(줄 끝)을 갖기 때문에 그 능력을 꺼서 평범한 글자로 만든 거예요. 대상은 cost $50 and 30 units예요. 실행해볼게요.


re.findall(r'(?<=\$)\d+', 'cost $50 and 30 units')


결과는 이래요.


['50']


50만 나왔어요. 앞에 $가 있는 숫자니까요. 30은 앞에 달러 기호가 없어서 탈락했고요. 이렇게 후방탐색은 접두 기호(앞에 붙는 표시)를 결과에서 깔끔히 빼내고 싶을 때 아주 유용해요. 가격표, 태그, 특정 기호로 시작하는 값을 다룰 때 자주 만나게 될 거예요.


참고로 후방탐색에도 "없어야 함" 버전인 부정 후방탐색 (?<!...)가 있어요. "앞에 이런 게 있으면 안 돼"라는 뜻이죠. 전방탐색의 긍정·부정, 후방탐색의 긍정·부정, 이렇게 네 식구가 대칭을 이뤄요. 표기가 물음표로 시작하는 건 같고, 앞을 볼 땐 <가 들어가고, "없어야 함"일 땐 등호 대신 느낌표가 들어간다고 기억하면 네 가지가 정리돼요. 파이썬의 후방탐색은 확인할 길이가 고정되어야 한다는 작은 제약이 있는데, 위 예제처럼 기호 한 글자를 보는 경우엔 걱정할 일이 없어요.

15.4 뒤에 특정 글자가 안 오는 것만 (부정 전방탐색)

이번엔 "있어야 함"이 아니라 "없어야 함"이에요. 상황은 뒤에 안 오는 숫자를 잡는 거예요. 패턴은 이래요.


\d+(?!개)


(?!개)가 부정 전방탐색이에요. "이 숫자 뒤에 개가 있으면 안 돼"라는 뜻이죠. 대상은 5000원 300개예요. 실행하면 결과가 조금 의외예요.


re.findall(r'\d+(?!개)', '5000원 300개')


결과는 이래요.


['5000', '30']


5000이야 뒤가 이니 당연히 통과예요. 그런데 300개에서 30이 왜 나왔을까요? 여기가 이 예제의 묘미예요. 탐욕적인 \d+가 처음엔 300 전부를 잡으려 했는데, 그러면 바로 뒤가 라서 "개가 없어야 함" 조건에 걸려요. 그래서 한 글자 뱉고(백트래킹, 되돌아가기) 30으로 줄여보니, 이번엔 뒤가 0이라 조건을 통과한 거예요. 마지막 0 하나는 뒤가 라서 결국 탈락했고요. 부정 룩어라운드는 이렇게 백트래킹 때문에 부분 매치가 생길 수 있으니, 결과가 예상과 다를 때 이 과정을 떠올리면 이해가 돼요.

15.5 천 단위 콤마는 어떻게 넣나요?

룩어라운드가 소비를 안 한다는 성질을 활용한 유명한 실전 기법이 있어요. 바로 큰 숫자에 천 단위 콤마를 넣는 거예요. 12345671,234,567로 바꾸는 그거요. 패턴과 치환은 이래요.


re.sub(r'(\d)(?=(\d{3})+$)', r'\1,', '1234567')


결과는 이래요.


'1,234,567'


원리를 풀면 이래요. (\d)로 숫자 한 개를 잡고, (?=(\d{3})+$)는 "이 뒤에 세 자리씩 딱 떨어지는 숫자가 끝까지 남아 있어야 함"을 확인해요. 그런 자리마다 \1,로 그 숫자 뒤에 콤마를 끼워 넣는 거죠. 전방탐색이 자릿수를 세기만 하고 먹지 않으니까, 같은 숫자를 여러 번 확인하며 정확한 위치에만 콤마를 넣을 수 있어요. 소비하지 않는 특성이 없으면 이런 처리는 훨씬 까다로워요. 룩어라운드가 왜 실전에서 사랑받는지 보여주는 대표 예제예요.


이 패턴에서 $는 앞에서 배운 "문자열 끝"이라는 뜻이에요. "세 자리씩 묶음이 끝까지 딱 맞게 남아 있는 자리"를 찾으려고 끝을 기준으로 센 거죠. 처음 보면 (\d)(?=(\d{3})+$)가 암호 같지만, "숫자 하나 뒤에 세 자리 묶음이 끝까지 남았으면 콤마" 이 한 문장으로 읽으면 뜻이 통해요. 콤마 넣기 같은 실전 작업을 룩어라운드로 이렇게 짧게 끝낼 수 있다는 게 정말 매력적이에요.

15.6 오늘 배운 것 정리

룩어라운드는 앞뒤 조건을 확인만 하고 그 부분을 결과에 넣지 않는 기술이에요. 뒤를 보는 전방탐색은 긍정 (?=...), 부정 (?!...)이고, 앞을 보는 후방탐색은 긍정 (?<=...), 부정 (?<!...)예요. \d+(?=원)으로 원 앞 숫자만, (?<=\$)\d+로 달러 뒤 숫자만 뽑았고, 부정 전방탐색 \d+(?!개)에서는 백트래킹 때문에 30 같은 부분 매치가 생길 수 있다는 것도 봤어요. 마지막으로 소비하지 않는 성질을 활용해 1234567에 콤마를 넣어 1,234,567을 만들어봤고요. 기호가 좀 많지만 "확인만 하고 안 먹는다"는 핵심 하나만 붙잡으면 나머지는 자연스럽게 따라와요.