Alert
์ด ๊ธ์ Claude Code์ ๋์์ ๋ฐ์ ์์ฑ๋์์ต๋๋ค
TL;DR
- ์ ๊ท ํํ์(regex)์ ๋ฌธ์์ด์์ ํจํด์ ์ฐพ๊ธฐ ์ํ ๋ฏธ๋ ์ธ์ด๋ค
- Python์์๋
re๋ชจ๋๋ก ์ฌ์ฉํ๋ฉฐ, ํจํด ๋ฌธ๋ฒ์ ์ดํดํ๋ฉด ๋ก๊ทธ ํ์ฑ, ์ ๋ ฅ ๊ฒ์ฆ, ํ ์คํธ ์ถ์ถ ๋ฑ์ ๋ฐ๋ก ํ์ฉํ ์ ์๋ค- ์ด ๊ธ์ ํจํด ๋ฌธ๋ฒ๋ถํฐ ๋จ๊ณ๋ณ๋ก ๋ฐ๋ผ๊ฐ๋ฉฐ ์ตํ๋ ๊ตฌ์ฑ์ด๋ค
Sources
1. ์ ๊ท ํํ์์ด๋
์ ๊ท ํํ์(Regular Expression, regex)์ ๋ฌธ์์ด์์ ํน์ ํจํด์ ์ฐพ๊ธฐ ์ํ ํํ ์ฒด๊ณ๋ค. ๋ฌธ์์ด ์์์ โ์ด๋ฐ ๋ชจ์์ ํ ์คํธ๋ฅผ ์ฐพ์์คโ๋ผ๊ณ ๋งํ๋ ๋ฏธ๋ ์ธ์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
Python์์๋ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ re ๋ชจ๋์ ์ฌ์ฉํ๋ค.
import re
# "์ซ์๊ฐ ์ฐ์๋ ๋ถ๋ถ"์ ์ฐพ์์ค
re.findall(r'\d+', '์ฃผ๋ฌธ๋ฒํธ 12345, ์๋ 3๊ฐ')
# ['12345', '3']์ด ํ ์ค์ ์ดํดํ๋ ค๋ฉด \d+๊ฐ ๋ญ์ง, findall์ด ๋ญ์ง ์์์ผ ํ๋ค. ๋จผ์ ํจํด ๋ฌธ๋ฒ๋ถํฐ ํ๋์ฉ ๋ฐฐ์๋ณด์.
raw string (r'...')
regex ํจํด์ ํญ์
r'...'ํํ๋ก ์์ฑํ๋ค.r์ ๋ถ์ด๋ฉด Python์ด ๋ฐฑ์ฌ๋์๋ฅผ ์ด์ค์ผ์ดํ ์ฒ๋ฆฌํ์ง ์๊ธฐ ๋๋ฌธ์\d,\b๊ฐ์ regex ๋ฌธ๋ฒ์ ๊ทธ๋๋ก ์ธ ์ ์๋ค.# โ r ์์ด ์ฐ๋ฉด \b๊ฐ ๋ฐฑ์คํ์ด์ค(\x08)๋ก ํด์๋จ re.search('\bword\b', 'a word here') # None # โ raw string re.search(r'\bword\b', 'a word here') # Match
2. ํจํด ๊ธฐ์ด โ ๊ธ์ ํ๋ ๋งค์นญํ๊ธฐ
regex์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ๊ธ์ ํ๋๋ฅผ ๋งค์นญํ๋ ๊ท์น์ด๋ค.
๋ฆฌํฐ๋ด ๋งค์นญ
์ผ๋ฐ ๋ฌธ์๋ ๊ทธ ์์ฒด๋ฅผ ๋งค์นญํ๋ค.
re.findall(r'a', 'banana')
# ['a', 'a', 'a']
re.findall(r'hello', 'say hello to hello world')
# ['hello', 'hello']๋ฉํ ๋ฌธ์
regex์์ ํน๋ณํ ์๋ฏธ๋ฅผ ๊ฐ์ง ๋ฌธ์๋ค์ด ์๋ค. ์ด๊ฒ๋ค์ ๋ฉํ ๋ฌธ์๋ผ ํ๋ค.
. ^ $ * + ? { } [ ] \ | ( )
์ด ๋ฌธ์๋ฅผ ๊ธ์ ๊ทธ๋๋ก ๋งค์นญํ๋ ค๋ฉด ์์ \๋ฅผ ๋ถ์ธ๋ค.
re.findall(r'\.', 'version 3.11.0') # ['.', '.']
re.findall(r'\$', 'price: $100') # ['$']
re.findall(r'\(', 'func(x)') # ['(']. (์ ) โ ์๋ฌด ๊ธ์ ํ๋
.์ ์ค๋ฐ๊ฟ(\n)์ ์ ์ธํ ์๋ฌด ๊ธ์ ํ๋๋ฅผ ๋งค์นญํ๋ค.
re.findall(r'a.c', 'abc adc a1c a c aXXc')
# ['abc', 'adc', 'a1c', 'a c']
# 'aXXc'๋ a์ c ์ฌ์ด์ ๊ธ์๊ฐ 2๊ฐ๋ผ ๋งค์นญ ์ ๋จ๋ฌธ์ ํด๋์ค [...] โ ์ด ์ค์ ํ๋
๋๊ดํธ ์์ ๋์ดํ ๋ฌธ์ ์ค ํ๋๋ฅผ ๋งค์นญํ๋ค.
re.findall(r'[aeiou]', 'hello world')
# ['e', 'o', 'o']
# ๋ฒ์ ์ง์
re.findall(r'[a-z]', 'Hello 123')
# ['e', 'l', 'l', 'o']
re.findall(r'[0-9]', 'abc 123 def')
# ['1', '2', '3']
# ์ฌ๋ฌ ๋ฒ์ ์กฐํฉ
re.findall(r'[a-zA-Z0-9]', 'Hi! 3?')
# ['H', 'i', '3'][^...] โ ์ด๊ฒ ๋นผ๊ณ ์ ๋ถ
^๋ฅผ ๋๊ดํธ ์ ๋งจ ์์ ์ฐ๋ฉด ๋์ดํ ๋ฌธ์๋ฅผ ์ ์ธํ ๋๋จธ์ง๋ฅผ ๋งค์นญํ๋ค.
re.findall(r'[^0-9]', 'abc 123')
# ['a', 'b', 'c', ' ']
re.findall(r'[^aeiou ]', 'hello world')
# ['h', 'l', 'l', 'w', 'r', 'l', 'd']๋ฏธ๋ฆฌ ์ ์๋ ๋ฌธ์ ํด๋์ค
์์ฃผ ์ฐ๋ ํจํด์ ์ถ์ฝํ์ด ์๋ค.
| ์ถ์ฝํ | ์๋ฏธ | ๋๋ฑํ ํํ |
|---|---|---|
\d | ์ซ์ | [0-9] |
\D | ์ซ์๊ฐ ์๋ ๊ฒ | [^0-9] |
\w | ๋จ์ด ๋ฌธ์ (๊ธ์, ์ซ์, _) | [a-zA-Z0-9_] |
\W | ๋จ์ด ๋ฌธ์๊ฐ ์๋ ๊ฒ | [^a-zA-Z0-9_] |
\s | ๊ณต๋ฐฑ (์คํ์ด์ค, ํญ, ์ค๋ฐ๊ฟ) | [ \t\n\r\f\v] |
\S | ๊ณต๋ฐฑ์ด ์๋ ๊ฒ | [^ \t\n\r\f\v] |
re.findall(r'\d', 'abc 123') # ['1', '2', '3']
re.findall(r'\w', 'hi! 3?') # ['h', 'i', '3']
re.findall(r'\s', 'a b\tc\nd') # [' ', '\t', '\n']๋๋ฌธ์๋ ์๋ฌธ์์ ๋ฐ๋๋ผ๊ณ ๊ธฐ์ตํ๋ฉด ๋๋ค. \dโ\D, \wโ\W, \sโ\S.
\w์ ํ๊ธ
Python 3์์
\w๋ ์ ๋์ฝ๋ ๋จ์ด ๋ฌธ์๋ฅผ ํฌํจํ๋ฏ๋ก ํ๊ธ๋ ๋งค์นญ๋๋ค.re.findall(r'\w', '์๋ hello ์ธ๊ณ') # ['์', '๋ ', 'h', 'e', 'l', 'l', 'o', '์ธ', '๊ณ']ASCII๋ง ๋งค์นญํ๊ณ ์ถ์ผ๋ฉด
re.ASCIIํ๋๊ทธ๋ฅผ ์ฌ์ฉํ๋ค. (ํ๋๊ทธ๋ ๋ค์์ ๋ค๋ฃฌ๋ค)
3. ํจํด ํ์ฅ โ ๋ฐ๋ณต (์๋์)
์ง๊ธ๊น์ง๋ ๊ธ์ ํ๋๋ฅผ ๋งค์นญํ๋๋ฐ, ์๋์(Quantifier)๋ฅผ ํตํด ๋ช ๋ฒ ๋ฐ๋ณต๋๋์ง๋ฅผ ์ง์ ํ ์ ์๋ค
* โ 0๋ฒ ์ด์
re.findall(r'ab*c', 'ac abc abbc abbbc')
# ['ac', 'abc', 'abbc', 'abbbc']
# b๊ฐ 0๋ฒ(ac), 1๋ฒ(abc), 2๋ฒ(abbc), 3๋ฒ(abbbc) ๋ชจ๋ ๋งค์นญ+ โ 1๋ฒ ์ด์
re.findall(r'ab+c', 'ac abc abbc abbbc')
# ['abc', 'abbc', 'abbbc']
# b๊ฐ 0๋ฒ์ธ 'ac'๋ ๋งค์นญ ์ ๋จ? โ 0๋ฒ ๋๋ 1๋ฒ
re.findall(r'colou?r', 'color colour')
# ['color', 'colour']
# u๊ฐ ์์ด๋ ๋๊ณ ์์ด๋ ๋จ
re.findall(r'https?://', 'http://a https://b')
# ['http://', 'https://']{m} โ ์ ํํ m๋ฒ
re.findall(r'\d{3}', '1 12 123 1234')
# ['123', '123']
# 1234์์ ์ 3์๋ฆฌ '123'์ด ๋งค์นญ๋จ{m,n} โ m๋ฒ ์ด์ n๋ฒ ์ดํ
re.findall(r'\d{2,4}', '1 12 123 1234 12345')
# ['12', '123', '1234', '1234']์๋์์ ๋ฌธ์ ํด๋์ค ์กฐํฉ
์ฌ๊ธฐ์๋ถํฐ regex๊ฐ ๊ฐ๋ ฅํด์ง๋ค. ์ง๊ธ๊น์ง ๋ฐฐ์ด ๊ฒ๋ค์ ์กฐํฉํด๋ณด์.
# \d+ : ์ซ์๊ฐ 1๋ฒ ์ด์ ์ฐ์
re.findall(r'\d+', '์ฃผ๋ฌธ๋ฒํธ 12345, ์๋ 3๊ฐ')
# ['12345', '3']
# \w+ : ๋จ์ด ๋ฌธ์๊ฐ 1๋ฒ ์ด์ ์ฐ์
re.findall(r'\w+', 'hello world 123')
# ['hello', 'world', '123']
# [a-z]+ : ์๋ฌธ์๊ฐ 1๋ฒ ์ด์ ์ฐ์
re.findall(r'[a-z]+', 'Hello World 123')
# ['ello', 'orld']
# [A-Za-z]+ : ์๋ฌธ์๊ฐ 1๋ฒ ์ด์ ์ฐ์
re.findall(r'[A-Za-z]+', 'Hello World 123')
# ['Hello', 'World']Greedy vs Lazy
์๋์๋ ๊ธฐ๋ณธ์ ์ผ๋ก Greedy(ํ์์ )ํ๋ค. ์กฐ๊ฑด์ ๋ง์กฑํ๋ ์ ์์ ์ต๋ํ ๊ธด ๋ฌธ์์ด์ ์ก๋๋ค.
html = '<b>bold</b> and <i>italic</i>'
re.findall(r'<.*>', html)
# ['<b>bold</b> and <i>italic</i>']
# .* ๊ฐ ๊ฐ๋ฅํ ํ ๊ธธ๊ฒ ๋จน์ด์, ์ฒซ < ๋ถํฐ ๋ง์ง๋ง > ๊น์ง ์ ๋ถ ํ๋๋ก ์กํ์๋์ ๋ค์ ?๋ฅผ ๋ถ์ด๋ฉด Lazy(๊ฒ์ผ๋ฅธ)๊ฐ ๋๋ค. ์กฐ๊ฑด์ ๋ง์กฑํ๋ ์ ์์ ์ต๋ํ ์งง์ ๋ฌธ์์ด์ ์ก๋๋ค.
re.findall(r'<.*?>', html)
# ['<b>', '</b>', '<i>', '</i>']
# .*? ๊ฐ ๊ฐ๋ฅํ ํ ์งง๊ฒ ๋จน์ด์, ๊ฐ < > ์์ ํ๋์ฉ ์ก์| Greedy | Lazy | ๋์ |
|---|---|---|
* | *? | 0๋ฒ ์ด์ (์ต๋ํ ๊ธธ๊ฒ vs ์ต๋ํ ์งง๊ฒ) |
+ | +? | 1๋ฒ ์ด์ (์ต๋ํ ๊ธธ๊ฒ vs ์ต๋ํ ์งง๊ฒ) |
? | ?? | 0~1๋ฒ (์ต๋ํ ๊ธธ๊ฒ vs ์ต๋ํ ์งง๊ฒ) |
{m,n} | {m,n}? | m~n๋ฒ (์ต๋ํ ๊ธธ๊ฒ vs ์ต๋ํ ์งง๊ฒ) |
# ๋ ๋ค๋ฅธ ์์
text = 'aaa'
re.findall(r'a+', text) # ['aaa'] โ Greedy: a๋ฅผ ์ต๋ํ ๊ธธ๊ฒ
re.findall(r'a+?', text) # ['a', 'a', 'a'] โ Lazy: a๋ฅผ ์ต๋ํ ์งง๊ฒ (1๊ฐ์ฉ)4. ํจํด ํ์ฅ โ ์์น (์ต์ปค)
์ต์ปค๋ ๋ฌธ์๋ฅผ ์๋นํ์ง ์๊ณ ์์น๋ง ์ง์ ํ๋ค. โ์ฌ๊ธฐ์ ์์ด์ผ ํ๋คโ๋ ์กฐ๊ฑด์ ๊ฑฐ๋ ๊ฒ์ด๋ค.
^ โ ์์, $ โ ๋
re.search(r'^hello', 'hello world') # Match โ ์์์ด hello
re.search(r'^hello', 'say hello') # None โ ์์์ด ์๋
re.search(r'world$', 'hello world') # Match โ ๋์ด world
re.search(r'world$', 'world hello') # None โ ๋์ด ์๋# ^์ $๋ฅผ ๊ฐ์ด ์ฐ๋ฉด "์ ์ฒด๊ฐ ์ด ํจํด์ด์ด์ผ ํ๋ค"
re.search(r'^\d+$', '12345') # Match โ ์ ์ฒด๊ฐ ์ซ์
re.search(r'^\d+$', '123abc') # None โ ์ซ์๊ฐ ์๋ ๋ถ๋ถ ์์\b โ ๋จ์ด ๊ฒฝ๊ณ
๋จ์ด ๋ฌธ์(\w)์ ๋น๋จ์ด ๋ฌธ์(\W) ์ฌ์ด์ ๊ฒฝ๊ณ๋ฅผ ๋งค์นญํ๋ค. ๊ธ์๋ฅผ ์๋นํ์ง ์๋๋ค.
re.findall(r'\bcat\b', 'cat catalog catfish the cat sat')
# ['cat', 'cat']
# 'catalog', 'catfish'์ cat์ ๋จ์ด ๊ฒฝ๊ณ๊ฐ ์๋๋ผ ๋งค์นญ ์ ๋จ
re.findall(r'cat', 'cat catalog catfish the cat sat')
# ['cat', 'cat', 'cat', 'cat']
# \b ์์ด ์ฐ๋ฉด ๋ถ๋ถ ๋งค์นญ๋ ์ ๋ถ ์กํ# ์ค์ : ํน์ ๋จ์ด๋ง ์ ํํ ์นํ
re.sub(r'\bJava\b', 'Python', 'Java and JavaScript are different')
# 'Python and JavaScript are different'
# JavaScript์ Java๋ ๊ฑด๋๋ฆฌ์ง ์์\A์ \Z โ ์ ๋ ์์/๋
^์ $๋ ๋ค์์ ๋ฐฐ์ธ MULTILINE ํ๋๊ทธ์ ์ํฅ์ ๋ฐ์ง๋ง, \A์ \Z๋ ํญ์ ๋ฌธ์์ด์ ์ ๋ ์์/๋๋ง ์๋ฏธํ๋ค.
5. ํจํด ํ์ฅ โ ๊ทธ๋ฃน
์๊ดํธ ()๋ก ํจํด์ ์ผ๋ถ๋ฅผ ๋ฌถ์ผ๋ฉด ๊ทธ๋ฃน์ด ๋๋ค. ๊ทธ๋ฃน์ ๋ ๊ฐ์ง ์ญํ ์ ํ๋ค: ๋ฌถ์ด์ ์๋์ ์ ์ฉ, ๋งค์นญ๋ ๋ถ๋ถ ์บก์ฒ.
๊ธฐ๋ณธ ๊ทธ๋ฃน
# ๊ทธ๋ฃน ์์ด: ab+ = a ๋ค์์ b๊ฐ 1๋ฒ ์ด์
re.findall(r'ab+', 'ab abb abab')
# ['ab', 'abb', 'ab', 'ab']
# ๊ทธ๋ฃน์ผ๋ก ๋ฌถ๊ธฐ: (ab)+ = 'ab'๊ฐ 1๋ฒ ์ด์
re.findall(r'(ab)+', 'ab abb abab')
# ['ab', 'ab', 'ab']findall๊ณผ ๊ทธ๋ฃน์ ๊ด๊ณ
findall์ ๊ทธ๋ฃน์ด ์์ผ๋ฉด ๊ทธ๋ฃน ๋ด์ฉ๋ง ๋ฐํํ๋ค. ์ ์ฒด ๋งค์นญ์ ๋ณด๋ ค๋ฉด ๋น์บก์ฒ ๊ทธ๋ฃน(?:...)์ ์ฌ์ฉํ๊ฑฐ๋ ๊ทธ๋ฃน์ ์ ๊ฑฐํ๋ค.re.findall(r'(\d+)-(\d+)', 'a1-2 b3-4') # [('1', '2'), ('3', '4')] โ ๊ทธ๋ฃน ํํ re.findall(r'\d+-\d+', 'a1-2 b3-4') # ['1-2', '3-4'] โ ์ ์ฒด ๋งค์นญ
์บก์ฒ ๊ทธ๋ฃน์ผ๋ก ๋ถ๋ถ ์ถ์ถ
m = re.search(r'(\d+)-(\d+)-(\d+)', '์ ํ๋ฒํธ: 010-1234-5678')
m.group() # '010-1234-5678' ์ ์ฒด ๋งค์นญ
m.group(0) # '010-1234-5678' group()๊ณผ ๋์ผ
m.group(1) # '010' ์ฒซ ๋ฒ์งธ ๊ทธ๋ฃน
m.group(2) # '1234' ๋ ๋ฒ์งธ ๊ทธ๋ฃน
m.group(3) # '5678' ์ธ ๋ฒ์งธ ๊ทธ๋ฃน
m.groups() # ('010', '1234', '5678')๋ช
๋ช
๋ ๊ทธ๋ฃน (?P<name>...)
๋ฒํธ ๋์ ์ด๋ฆ์ผ๋ก ์ ๊ทผํ ์ ์๋ค. ํจํด์ด ๋ณต์กํด์ง๋ฉด ๊ฐ๋ ์ฑ์ด ํจ์ฌ ์ข๋ค.
m = re.search(
r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})',
'์ค๋์ 2026-04-12'
)
m.group('year') # '2026'
m.group('month') # '04'
m.groupdict() # {'year': '2026', 'month': '04', 'day': '12'}๋น์บก์ฒ ๊ทธ๋ฃน (?:...)
๋ฌถ๊ธฐ๋ง ํ๊ณ ์บก์ฒ๋ ํ์ง ์๋๋ค. ๊ทธ๋ฃน ๋ฒํธ๋ฅผ ์๋นํ์ง ์๋๋ค.
# http ๋๋ https๋ฅผ ๋ฌถ๋ ์บก์ฒํ์ง ์์
re.findall(r'(?:https?://)\S+', 'visit http://a.com or https://b.com')
# ['http://a.com', 'https://b.com']OR ์ฐ์ฐ์ |
|๋ โ๋๋โ์ด๋ค. ๊ทธ๋ฃน๊ณผ ํจ๊ป ์ฐ๋ฉด ํน์ ๋ถ๋ถ์๋ง OR์ ์ ์ฉํ ์ ์๋ค.
re.findall(r'cat|dog', 'I have a cat and a dog')
# ['cat', 'dog']
# ๊ทธ๋ฃน์ผ๋ก OR ๋ฒ์ ์ ํ
re.findall(r'(?:cat|dog)s?', 'cats and dogs and cat')
# ['cats', 'dogs', 'cat']์ญ์ฐธ์กฐ (Backreference)
์บก์ฒํ ๊ทธ๋ฃน์ ํจํด ์์์ ๋ค์ ์ฐธ์กฐํ ์ ์๋ค.
# ์ฐ์ ์ค๋ณต ๋จ์ด ์ฐพ๊ธฐ
re.search(r'\b(\w+)\s+\1\b', 'the the cat').group()
# 'the the' โ \1์ด ์ฒซ ๋ฒ์งธ ๊ทธ๋ฃน(the)๊ณผ ๊ฐ์ ํ
์คํธ๋ฅผ ๋งค์นญ
# ๋ช
๋ช
๋ ์ญ์ฐธ์กฐ
re.search(r'\b(?P<word>\w+)\s+(?P=word)\b', 'the the cat').group()
# 'the the'# sub์์ ์ญ์ฐธ์กฐ๋ก ์์ ๋ฐ๊พธ๊ธฐ
re.sub(r'(\w+) (\w+)', r'\2 \1', 'hello world')
# 'world hello'
# ๋ช
๋ช
๋ ๊ทธ๋ฃน์ผ๋ก ๋ ์ง ํ์ ๋ณํ: YYYY-MM-DD โ DD/MM/YYYY
re.sub(
r'(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})',
r'\g<d>/\g<m>/\g<y>',
'2026-04-12'
)
# '12/04/2026'6. re ๋ชจ๋ ํต์ฌ ํจ์
ํจํด ๋ฌธ๋ฒ์ ๋ฐฐ์ ์ผ๋, ์ด์ ์ด ํจํด์ ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ํจ์๋ฅผ ์ ๋ฆฌํ๋ค.
ํจ์ ์์ฝ
| ํจ์ | ์ค๋ช | ๋ฐํ๊ฐ |
|---|---|---|
re.search(pattern, string) | ๋ฌธ์์ด ์ ์ฒด๋ฅผ ์ค์บ, ์ฒซ ๋ฒ์งธ ๋งค์นญ | Match ๋๋ None |
re.match(pattern, string) | ๋ฌธ์์ด ์์์์๋ง ๋งค์นญ | Match ๋๋ None |
re.fullmatch(pattern, string) | ๋ฌธ์์ด ์ ์ฒด๊ฐ ํจํด๊ณผ ์ผ์น | Match ๋๋ None |
re.findall(pattern, string) | ๊ฒน์น์ง ์๋ ๋ชจ๋ ๋งค์นญ์ ๋ฆฌ์คํธ๋ก | list[str] |
re.finditer(pattern, string) | ๋ชจ๋ ๋งค์นญ์ ์ดํฐ๋ ์ดํฐ๋ก | iterator[Match] |
re.sub(pattern, repl, string) | ๋งค์นญ๋ ๋ถ๋ถ์ ์นํ | str |
re.split(pattern, string) | ํจํด ๊ธฐ์ค์ผ๋ก ๋ถ๋ฆฌ | list[str] |
re.compile(pattern) | ํจํด์ ๋ฏธ๋ฆฌ ์ปดํ์ผ | Pattern ๊ฐ์ฒด |
search vs match vs fullmatch
text = 'abc123def'
re.search(r'\d+', text) # Match '123' โ ์ด๋์๋ ์ฒซ ๋งค์นญ
re.match(r'\d+', text) # None โ ์์์ด ์ซ์๊ฐ ์๋
re.match(r'[a-z]+', text) # Match 'abc' โ ์์์ด ์๋ฌธ์
re.fullmatch(r'\d+', '123') # Match โ ์ ์ฒด๊ฐ ์ซ์
re.fullmatch(r'\d+', '123a') # None โ ์ ์ฒด๊ฐ ์ผ์นํ์ง ์์match๋ ^๊ฐ ์๋ฌต์ ์ผ๋ก ๋ถ์ด์๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค.
findall vs finditer
re.findall(r'\d+', 'a1 b22 c333')
# ['1', '22', '333']
for m in re.finditer(r'\d+', 'a1 b22 c333'):
print(f"์์น {m.span()}: {m.group()}")
# ์์น (1, 2): 1
# ์์น (4, 6): 22
# ์์น (8, 11): 333finditer๋ Match ๊ฐ์ฒด๋ฅผ ํ๋์ฉ ๋ฐํํ๋ฏ๋ก ์์น ์ ๋ณด๊ฐ ํ์ํ๊ฑฐ๋ ๋์ฉ๋ ๋ฐ์ดํฐ์์ ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ด ์ข๋ค.
sub โ ์นํ
re.sub(r'\d+', 'NUM', 'a1 b22')
# 'aNUM bNUM'
# ํจ์๋ฅผ ๋๊ธฐ๋ฉด ๋งค์นญ๋ง๋ค ๋์ ์นํ ๊ฐ๋ฅ
re.sub(r'\d+', lambda m: str(int(m.group()) * 2), 'a1 b2')
# 'a2 b4'split โ ๋ถ๋ฆฌ
re.split(r'[,;]\s*', 'one, two;three, four')
# ['one', 'two', 'three', 'four']compile โ ํจํด ์ฌ์ฌ์ฉ
pattern = re.compile(r'\b\w{3}\b')
pattern.findall('the cat sat on a mat')
# ['the', 'cat', 'sat', 'mat']
# ๋ฐ๋ณต ์ฌ์ฉ ์ ์ปดํ์ผํด๋๋ฉด ์ฑ๋ฅ์ ์ ๋ฆฌํ๋ค
for line in huge_log_file:
if pattern.search(line):
process(line)Match ๊ฐ์ฒด ์ฃผ์ ๋ฉ์๋
m = re.search(r'(\d+)-(\d+)', 'code: 123-456')
m.group() # '123-456' ์ ์ฒด ๋งค์นญ
m.group(1) # '123' ์ฒซ ๋ฒ์งธ ๊ทธ๋ฃน
m.group(2) # '456' ๋ ๋ฒ์งธ ๊ทธ๋ฃน
m.groups() # ('123', '456') ๋ชจ๋ ๊ทธ๋ฃน
m.start() # 6 ๋งค์นญ ์์ ์์น
m.end() # 13 ๋งค์นญ ๋ ์์น
m.span() # (6, 13) (์์, ๋)7. ํ๋๊ทธ
ํ๋๊ทธ๋ ํจํด์ ๋์ ๋ฐฉ์์ ๋ฐ๊พผ๋ค.
| ํ๋๊ทธ | ์ฝ์ด | ์ธ๋ผ์ธ | ์ค๋ช |
|---|---|---|---|
re.IGNORECASE | re.I | (?i) | ๋์๋ฌธ์ ๋ฌด์ |
re.MULTILINE | re.M | (?m) | ^, $๊ฐ ๊ฐ ์ค์ ์์/๋์๋ ๋งค์นญ |
re.DOTALL | re.S | (?s) | .์ด ์ค๋ฐ๊ฟ(\n)๋ ๋งค์นญ |
re.VERBOSE | re.X | (?x) | ๊ณต๋ฐฑ๊ณผ ์ฃผ์ ํ์ฉ (๊ฐ๋ ์ฑ ํฅ์) |
re.ASCII | re.A | (?a) | \w, \d ๋ฑ์ ASCII ์ ์ฉ์ผ๋ก ์ ํ |
IGNORECASE
re.findall(r'hello', 'Hello HELLO hello', re.I)
# ['Hello', 'HELLO', 'hello']MULTILINE
text = "hello world\nhello python"
re.findall(r'^hello', text) # ['hello'] โ ์ฒซ ์ค๋ง
re.findall(r'^hello', text, re.M) # ['hello', 'hello'] โ ๊ฐ ์คDOTALL
text = '<div>\nhello\n</div>'
re.search(r'<div>.*</div>', text) # None โ .์ด \n ๋งค์นญ ์ ํจ
re.search(r'<div>.*</div>', text, re.S) # Match โ .์ด \n๋ ๋งค์นญVERBOSE โ ํจํด์ ์ฃผ์ ๋ฌ๊ธฐ
๋ณต์กํ ํจํด์ ์ฝ๊ธฐ ์ฝ๊ฒ ๋ง๋ค ์ ์๋ค.
email_pattern = re.compile(r"""
[a-zA-Z0-9._%+-]+ # ์ฌ์ฉ์๋ช
@ # @ ๊ตฌ๋ถ์
[a-zA-Z0-9.-]+ # ๋๋ฉ์ธ
\.[a-zA-Z]{2,} # TLD (.com, .co.kr ๋ฑ)
""", re.VERBOSE)ํ๋๊ทธ ์กฐํฉ๊ณผ ์ธ๋ผ์ธ
# ์ฌ๋ฌ ํ๋๊ทธ๋ฅผ | ๋ก ์กฐํฉ
pattern = re.compile(r'hello', re.IGNORECASE | re.MULTILINE)
# ํจํด ์์์ ์ธ๋ผ์ธ์ผ๋ก ์ง์
re.findall(r'(?i)hello', 'Hello HELLO hello')
# ['Hello', 'HELLO', 'hello']
# ํน์ ๊ทธ๋ฃน์๋ง ์ ์ฉ
re.findall(r'(?i:hello) WORLD', 'Hello WORLD hello WORLD')
# ['Hello WORLD', 'hello WORLD']8. ์ ํ๋ฐฉ ํ์ (Lookahead / Lookbehind)
์ต์ปค์ฒ๋ผ ๋ฌธ์๋ฅผ ์๋นํ์ง ์๊ณ ์กฐ๊ฑด๋ง ํ์ธํ๋ ํจํด์ด๋ค. โ์/๋ค์ ์ด๊ฒ ์๋ ๊ฒฝ์ฐ์๋ง ๋งค์นญํด๋ผโ๋ ์๋ฏธ๋ค.
| ํจํด | ์ด๋ฆ | ์๋ฏธ |
|---|---|---|
(?=Y) | ๊ธ์ ์ ๋ฐฉ ํ์ | ๋ค์ Y๊ฐ ์์ ๋๋ง ๋งค์นญ |
(?!Y) | ๋ถ์ ์ ๋ฐฉ ํ์ | ๋ค์ Y๊ฐ ์์ ๋๋ง ๋งค์นญ |
(?<=Y) | ๊ธ์ ํ๋ฐฉ ํ์ | ์์ Y๊ฐ ์์ ๋๋ง ๋งค์นญ |
(?<!Y) | ๋ถ์ ํ๋ฐฉ ํ์ | ์์ Y๊ฐ ์์ ๋๋ง ๋งค์นญ |
์ ๋ฐฉ ํ์ (Lookahead)
# ๋ค์ 'bar'๊ฐ ์ค๋ 'foo'๋ง ๋งค์นญ
re.findall(r'foo(?=bar)', 'foobar foobaz foo')
# ['foo'] โ foobar์ foo๋ง
# ๋ค์ 'bar'๊ฐ ์ค์ง ์๋ 'foo'๋ง ๋งค์นญ
re.findall(r'foo(?!bar)', 'foobar foobaz foo')
# ['foo', 'foo'] โ foobaz์ foo, ๋จ๋
fooํ๋ฐฉ ํ์ (Lookbehind)
# ์์ '$'๊ฐ ์๋ ์ซ์๋ง ๋งค์นญ
re.findall(r'(?<=\$)\d+', 'price $100, count 50')
# ['100']
# ์์ '$'๊ฐ ์๋ ์ซ์๋ง ๋งค์นญ
re.findall(r'(?<!\$)\d+', 'price $100, count 50')
# ['00', '50']
# 100์์ $๋ค์ 1์ด ๊ฑธ๋ฌ์ง๊ณ ๋๋จธ์ง 00์ด ๋งค์นญ๋จLookbehind ์ ์ฝ
re๋ชจ๋์ lookbehind๋ ๊ณ ์ ๊ธธ์ด๋ง ํ์ฉํ๋ค.(?<=\w+)์ฒ๋ผ ๊ฐ๋ณ ๊ธธ์ด๋ ์๋ฌ๊ฐ ๋๋ค. ๊ฐ๋ณ ๊ธธ์ด๊ฐ ํ์ํ๋ฉด ์๋ํํฐregex๋ชจ๋์ ์ฌ์ฉํด์ผ ํ๋ค.
์ค์ : ๋น๋ฐ๋ฒํธ ๊ฐ๋ ์ฒดํฌ
์ ๋ฐฉ ํ์์ ์ฌ๋ฌ ๊ฐ ์กฐํฉํ๋ฉด โ์ฌ๋ฌ ์กฐ๊ฑด์ ๋์์ ๋ง์กฑโํ๋ ํจํด์ ๋ง๋ค ์ ์๋ค.
# 8์ ์ด์, ๋๋ฌธ์ ํฌํจ, ์๋ฌธ์ ํฌํจ, ์ซ์ ํฌํจ
pattern = r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$'
bool(re.match(pattern, 'Hello123!')) # True
bool(re.match(pattern, 'hello123')) # False (๋๋ฌธ์ ์์)
bool(re.match(pattern, 'HELLO123')) # False (์๋ฌธ์ ์์)
bool(re.match(pattern, 'Helloabc')) # False (์ซ์ ์์)
bool(re.match(pattern, 'Hi1!')) # False (8์ ๋ฏธ๋ง)๊ฐ (?=.*X)๋ โ์ด๋๊ฐ์ X๊ฐ ์์ด์ผ ํ๋คโ๋ ์กฐ๊ฑด์ ์์น๋ฅผ ์๋นํ์ง ์๊ณ ๊ฑด๋ค. ๊ทธ๋์ ์กฐ๊ฑด ์ฌ๋ฌ ๊ฐ๋ฅผ ๋๋ํ ์ธ ์ ์๋ค.
9. ์ค์ ํจํด ๋ชจ์
์์ฃผ ์ฌ์ฉ๋๋ ํจํด๋ค์ ์ ๋ฆฌํ๋ค. ๋ณต์ฌํด์ ๋ฐ๋ก ์ฌ์ฉํ ์ ์๋ค.
# ์ด๋ฉ์ผ
email = r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
re.findall(email, 'contact user@example.com or admin@test.co.kr')
# ['user@example.com', 'admin@test.co.kr']
# URL
url = r'https?://\S+'
re.findall(url, 'visit https://example.com/path?q=1 for more')
# ['https://example.com/path?q=1']
# IPv4 ์ฃผ์
ipv4 = r'\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b'
re.findall(ipv4, 'server: 192.168.1.1, invalid: 999.999.999.999')
# ['192.168.1.1']
# ํ๊ตญ ์ ํ๋ฒํธ
phone_kr = r'0\d{1,2}-\d{3,4}-\d{4}'
re.findall(phone_kr, '์ฐ๋ฝ์ฒ: 010-1234-5678, 02-123-4567')
# ['010-1234-5678', '02-123-4567']
# ๋ ์ง (YYYY-MM-DD)
date_iso = r'\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])'
re.findall(date_iso, '๊ธฐ๊ฐ: 2026-01-15 ~ 2026-04-12')
# ['2026-01-15', '2026-04-12']
# ํ๊ธ๋ง ์ถ์ถ
korean = r'[๊ฐ-ํฃ]+'
re.findall(korean, '์๋
ํ์ธ์ hello ์ธ๊ณ world')
# ['์๋
ํ์ธ์', '์ธ๊ณ']
# HTML ํ๊ทธ ์ ๊ฑฐ
html_strip = r'<[^>]+>'
re.sub(html_strip, '', '<p>Hello <b>world</b></p>')
# 'Hello world'์ค์ ์์์ ํ๊ณ
์ด๋ฉ์ผ, URL ๋ฑ์ ์๋ฒฝํ ๊ฒ์ฆ์ regex๋ง์ผ๋ก๋ ์ด๋ ต๋ค. ์ค๋ฌด์์๋ ์ ์ฉ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(
email-validator,urllib.parse,ipaddress)์ ๋ณํํ๋ ๊ฒ์ด ์ข๋ค.
10. ์ฑ๋ฅ ์ต์ ํ
compile ํ์ฉ
๋ฐ๋ณต ์ฌ์ฉ๋๋ ํจํด์ ๋ฏธ๋ฆฌ ์ปดํ์ผํ๋ฉด ์ฑ๋ฅ์ด ์ข๋ค. re ๋ชจ๋์ด ๋ด๋ถ์ ์ผ๋ก ์ต๋ 512๊ฐ๊น์ง ์บ์ํ์ง๋ง ๋ช
์์ ์ปดํ์ผ์ด ๋ ํ์คํ๋ค.
pattern = re.compile(r'\d{3}-\d{4}')
for line in huge_file:
if pattern.search(line):
process(line)์ฌ์์ ๋ฐฑํธ๋ํน (Catastrophic Backtracking)
์ค์ฒฉ๋ ์๋์๊ฐ ์๋ ํจํด์์ ๋งค์นญ ์คํจ ์ ์ง์์ ์ผ๋ก ์กฐํฉ์ ์๋ํ๋ ํ์์ด๋ค.
# โ ์ํํ ํจํด โ ์
๋ ฅ์ด ๊ธธ์ด์ง๋ฉด ๊ธฐํ๊ธ์์ ์ผ๋ก ๋๋ ค์ง๋ค
bad = r'(a+)+b'
# 'aaaaaaaaaaac'์ ๋ํด ๋งค์นญ ์คํจ๋ฅผ ํ์ธํ๋ ๋ฐ ์ ์ด~์ ๋ถ ์์ํด๊ฒฐ ๋ฐฉ๋ฒ:
# ํจํด ๋จ์ํ
good = r'a+b'
# Possessive ์๋์ (Python 3.11+) โ ๋ฐฑํธ๋ํน ์ฐจ๋จ
good = r'a++b'
# ์์์ ๊ทธ๋ฃน (Python 3.11+)
good = r'(?>a+)b'์ฑ๋ฅ ํ ์ ๋ฆฌ
- ๊ฐ๋ฅํ๋ฉด
str.startswith(),in๊ฐ์ ๋ฌธ์์ด ๋ฉ์๋๋ฅผ ๋จผ์ ๊ณ ๋ คํ๋คfinditer()๋findall()๋ณด๋ค ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ ์ด๋ค- ๋น์บก์ฒ ๊ทธ๋ฃน
(?:...)์ผ๋ก ๋ถํ์ํ ์บก์ฒ๋ฅผ ์ค์ธ๋ค- ๊ตฌ์ฒด์ ์ธ ๋ฌธ์ ํด๋์ค(
[^,\n]+)๋ฅผ.๋ณด๋ค ์ ํธํ๋ค
11. ๋ฒ์ ๋ณ ๋ณ๊ฒฝ์ฌํญ
| ๋ฒ์ | ๋ณ๊ฒฝ์ฌํญ |
|---|---|
| 3.6 | re.Match, re.Pattern์ ํ์
ํํธ์ ์ฌ์ฉ ๊ฐ๋ฅ |
| 3.7 | re.LOCALE์ด ๋ฐ์ดํธ ํจํด ์ ์ฉ์ผ๋ก ์ ํ |
| 3.8 | \N{name} ์ ๋์ฝ๋ ์ด๋ฆ ์ด์ค์ผ์ดํ ์ง์ |
| 3.11 | Possessive ์๋์ (*+, ++, ?+, {m,n}+) ์ถ๊ฐ |
| 3.11 | ์์์ ๊ทธ๋ฃน ((?>...)) ์ถ๊ฐ |
| 3.12 | ์๋ชป๋ ์ด์ค์ผ์ดํ ์ํ์ค์ ๋ํ DeprecationWarning ๊ฐํ |
| 3.14 | ์๋ชป๋ ์ด์ค์ผ์ดํ ์ํ์ค๊ฐ SyntaxWarning, ํฅํ SyntaxError ์์ |
3.11์์ ์ถ๊ฐ๋ Possessive ์๋์์ ์์์ ๊ทธ๋ฃน์ด ๊ฐ์ฅ ํฐ ๋ณํ๋ค.
12. re vs regex (์๋ํํฐ) ๋น๊ต
ํ์ค re ๋ชจ๋๋ก ๋ถ์กฑํ ๊ฒฝ์ฐ ์๋ํํฐ regex ๋ชจ๋(pip install regex)์ ์ฌ์ฉํ ์ ์๋ค.
| ๊ธฐ๋ฅ | re (ํ์ค) | regex (์๋ํํฐ) |
|---|---|---|
| ์ค์น | ๊ธฐ๋ณธ ๋ด์ฅ | pip install regex |
| Possessive ์๋์ | 3.11+ | ๋ชจ๋ ๋ฒ์ |
| ์์์ ๊ทธ๋ฃน | 3.11+ | ๋ชจ๋ ๋ฒ์ |
| ๊ฐ๋ณ ๊ธธ์ด Lookbehind | ๋ถ๊ฐ (๊ณ ์ ๊ธธ์ด๋ง) | ์ง์ |
์ ๋์ฝ๋ ์์ฑ \p{L} | ๋ถ๊ฐ | ์ง์ |
| ํผ์ง ๋งค์นญ (Fuzzy) | ๋ถ๊ฐ | ์ง์ |
| ๊ฒน์นจ ๋งค์นญ (Overlapped) | ๋ถ๊ฐ | overlapped=True |
| ์ฌ๊ท ํจํด | ๋ถ๊ฐ | (?0), (?&name) |
import regex
# ๊ฐ๋ณ ๊ธธ์ด lookbehind (re์์๋ ๋ถ๊ฐ)
regex.findall(r'(?<=\b\w+)\d+', 'pay5 dot3')
# ['5', '3']
# ์ ๋์ฝ๋ ์์ฑ์ผ๋ก ํ๊ธ ์ถ์ถ
regex.findall(r'\p{Hangul}+', '์๋
ํ์ธ์ hello ์ธ๊ณ')
# ['์๋
ํ์ธ์', '์ธ๊ณ']
# ๊ฒน์นจ ๋งค์นญ
regex.findall(r'\w{2}', 'apple', overlapped=True)
# ['ap', 'pp', 'pl', 'le']
# ํผ์ง ๋งค์นญ (ํธ์ง ๊ฑฐ๋ฆฌ 1 ์ด๋ด ํ์ฉ)
regex.search(r'(?:hello){e<=1}', 'helo') # Match์ธ์ regex ๋ชจ๋์ ์ธ๊น
- ๊ฐ๋ณ ๊ธธ์ด lookbehind๊ฐ ํ์ํ ๋
\p{Hangul},\p{Greek}๊ฐ์ ์ ๋์ฝ๋ ์์ฑ์ด ํ์ํ ๋- ํผ์ง ๋งค์นญ(์คํ ํ์ฉ ๊ฒ์)์ด ํ์ํ ๋
- ๊ทธ ์ธ์๋ ํ์ค
re๋ก ์ถฉ๋ถํ๋ค
13. ํํ ํจ์ ๊ณผ ์ฃผ์์ฌํญ
raw string ๋๋ฝ
re.search('\bword\b', 'a word here') # None โ \b๊ฐ ๋ฐฑ์คํ์ด์ค๋ก ํด์
re.search(r'\bword\b', 'a word here') # MatchPython 3.12๋ถํฐ raw string์ด ์๋ ํจํด์ ์๋ชป๋ ์ด์ค์ผ์ดํ์ ๋ํ ๊ฒฝ๊ณ ๊ฐ ๊ฐํ๋์๋ค.
match()๋ ์์๋ง ๋ณธ๋ค
re.match(r'\d+', 'abc123') # None
re.search(r'\d+', 'abc123') # Match '123'โ์ด๋๊ฐ์ ์๋์งโ ํ์ธํ๋ ค๋ฉด ํญ์ search๋ฅผ ์ฌ์ฉํ๋ค.
findall()์ ๊ทธ๋ฃน์ด ์์ผ๋ฉด ๊ทธ๋ฃน๋ง ๋ฐํ
re.findall(r'(\d+)-(\d+)', '1-2 3-4')
# [('1', '2'), ('3', '4')] โ ์ ์ฒด ๋งค์นญ์ด ์๋ ๊ทธ๋ฃน ํํ
re.findall(r'\d+-\d+', '1-2 3-4')
# ['1-2', '3-4'] โ ์ ์ฒด ๋งค์นญDOTALL ์์ด ์ฌ๋ฌ ์ค ๋งค์นญ
text = '<div>\nhello\n</div>'
re.search(r'<div>.*</div>', text) # None
re.search(r'<div>.*</div>', text, re.DOTALL) # Match๋ฆฌํฐ๋ด ๋ฐฑ์ฌ๋์ ๋งค์นญ
ํ
์คํธ์ \ ํ๋๋ฅผ ๋งค์นญํ๋ ค๋ฉด ํจํด์์ \\๊ฐ ํ์ํ๋ค.
re.search(r'\\', r'path\to') # Match