이건 contentElement class attribute로 그 element의 read-only computed style을 return한다.
CSS로 HTML 문서의 element를 지정하고자 할 때 selector를 사용한다.
3가지 일반적인 selector가 있다.
Element type: 예를 들면, 모든 그 HTML 문서 내의 p element를 select하고자 한다면 CSS ruleset에 p selector를 쓴다.
class attribute: . 으로 시작하는 class selector. <h1 class=”heading”>Heading</h1> 을 select하고 싶다면, .heading selector를 쓴다.
id attribute: # 으로 시작하는 id selector. <div id=”dasomoli”> <!– login content –> </div> 라면, #dasomoli를 쓴다.
모든 element를 선택하고 싶으면 Universal Selector (*)를 쓴다. 다음과 같은 예제는 html에 값을 셋팅하고, 모든 자식 element들에 이를 inherit한다.
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
Attribute selector를 사용하면 attribute가 있는지 또는 attribute 값에 따라서 select할 수 있다. 대괄호([, ])를 쓴다.
[attribute]: 그 attribute가 있는 모든 element를 select한다. [href]는 href attribute가 있는 모든 element를 select한다.
[attribute=value]: attribute값이 value인 모든 element를 select한다. [lang=”en”] 은 lang attribute가 en인 모든 element를 select한다.
[attribute^=value]: attribute 값이 value로 시작하는 모든 element를 select한다. [href^=”https://] 는 href attribute가 https:// 로 시작하는 모든 element를 select한다.
[attribute$=value]: attribute 값이 value로 끝나는 모든 element를 select한다. [href$=”.com”] 은 href attribute가 .com으로 끝나는 모든 element를 select한다.
[attribute=value]: attribute 값 안에 value가 들어간 모든 element를 select한다. [href=”co.kr”] 은 href attribute가 co.kr과 들어간 모든 element를 select한다.
Pseudo-class는 특정 상태에 있는 element를 select한다. :keyword 같은 형식으로 쓴다. 대단히 많은 pseudo-class가 있는데, 대부분의 개발자들은 link를 styling할 때 처음 본다. link는 여러 상태를 갖는다.
href를 갖는 link는 :link pseudo-class를 갖는다.
사용자가 link에 마우스를 올렸을 때, :hover pseudo-class가 적용된다.
가 본 link일 때, :visited pseudo-class가 적용된다.
click될 때, :active pseudo-class가 적용된다.
예를 들면,
a:link, a:visited: {
color: blue;
text-decoration: none;
}
a:hover, a:active {
color: red;
text-decoration: dashed underline;
}
순서에 주의해야 하는데, 위의 순서를 바꾸면 hover 효과를 볼 수 없다. love-hate로 외우면 쉽다.
다른 유용한 pseudo-class로 :checked, :disabled, :focus가 있다.
중첩된 자식들의 패턴을 select할 때 도움이 되는 pseudo-class가 있다. :first-child, :last-child, :nth-child, :nth-last-child, :first-of-type, :last-of-type, :nth-of-type, :nth-last-of-type 등등.
예를 들어, 다음과 같이 하면 3줄마다 반복되는 패턴을 만들 수 있다.
li {
display: block;
padding: 16px;
}
li:nth-child(3n-1) {
background: deepskyblue;
color: white;
font-weight: bold;
}
li:nth-child(3n) {
background: skyblue;
color: white;
font-weight: bold;
}
위의 예제에서 3n-1 대신 odd 나 even 같은 keyword를 사용할 수도 있다.
element의 일부를 select하고 싶을 때는 Pseudo-element를 쓴다. ::keyword 형식으로 쓴다.
위에서 설명한 여러 방법을 합쳐서 사용하면 더 강력하다. 예를 들면, primary class인 li element를 select하고 싶다면, li.primary를 쓰면 된다.
ul element의 자식인 모든 li element를 select하고 싶으면. ul li
primary class인 ul element의 direct children인 모든 li를 select하고 싶으면, ul.primary > li
selected class인 li element의 다음 sibling을 select하고 싶으면, li.selected + li
selected class인 li element의 모든 다음 sibling들을 select하고 싶으면, li.selected ~ li
li.selected + li 는 다음 하나만, li.selected ~ li 는 다음부터 전부 다인 것을 주의하라.
CSS specificity는 어느 style이 적용될지를 결정하는 factor이다. 태그에 style attribute로 적용되는 inline style은 가장 높은 specificity 값을 갖는다. 그 다음이 ID selector, 그 다음이 class selector나 attribute selector, 그리고 다음이 element type이다. 일반적으로 이를 표현하는 방법이 ,로 나눈 integer list로 많이 표현한다. inline style은 1, 0, 0, 0이다. id selector는 0, 1, 0, 0. class selector나 attribute selector는 0, 0, 1, 0. h1 같은 element selector는 0, 0, 0, 1이 된다.
예를 들면, li,selected a[href] 는 2 element selectors(li, a), 하나의 class selector(.selected)와 하나의 attribute selector([herf])를 갖으므로, 0, 0, 2, 2가 된다.
다른 예로 #newItem #mainHeading span.smallPrint는 두 ID selector, 하나의 class selector(.smallPrint), 하나의 span element를 갖으므로, 0, 2, 1, 1이 된다.
위의 둘을 비교하면, 두번째 것이 첫번째 것보다 더 specific하다.
그런데 여기에 !important 를 쓰면 가장 우선한다. 위의 표현 방법에 따르면 1, 0, 0, 0, 0이 된다. inline보다 더 우선한다.
예를 들면, 다음과 같은 걸 보자.
<style>
div.media {
display: block;
width: 100%;
float: left;
}
.hide {
display: none;
}
</style>
<div class=”media hide”> 어쩌고 저쩌고 </div>
여기서 div는 media와 hide 둘 중 어느 것이 적용될까. div.media는 0, 0, 1, 1 이다. .hide는 0, 0, 1, 0이다. 따라서 .hide의 display: none을 div.media의 display: block이 override한다. .hide는 적용이 안된다. 이럴 때 아래처럼 하면 된다.
이 문서는 re 모듈로 파이썬에서 정규식(regular expression)을 사용하는 것에 대한 입문 튜토리얼입니다. 라이브러리 레퍼런스의 해당 절보다 좀 더 친절한 소개를 제공합니다.
소개
정규식 (REs, 또는 regexecs, 또는 regex patterns라고 부릅니다) 들은 원래 작은, 파이썬 내부에 내장된 매우 특별화된 프로그래밍 언어이고, re 모듈을 통해 이용 가능하도록 만들어졌습니다. 이 작은 언어를 사용해서 여러분은 여러분이 맞추길 원하는 가능한 문자열 집합의 규칙을 정합니다; 이는 영어 문장, 또는 E-mail 주소, 또는 TeX 명령, 또는 원하는 어떤 것이든 포함할 수 있습니다. 그럼 여러분은 “이 문자열이 이 패턴에 맞을까?” 또는 “이 문자열 어딘가에 이 패턴에 맞는 게 있을까?”와 같은 질문을 할 수 있습니다. 여러분은 또한 다양한 방법으로 문자열을 편집하거나, 부분으로 나누거나 하는데 RE를 사용할 수 있습니다.
정규식 패턴들은 C로 작성된 매칭 엔진에 의해 연속된 바이트코드로 컴파일된 후 실행됩니다. 고급 용도를 위해, 주어진 RE를 엔진이 어떻게 실행할지에 조심스럽게 주의를 기울이거나 더 빨리 실행되는 바이트코드를 만들기 위해 정해진 방법으로 RE를 작성할 필요가 있을 수 있습니다. 최적화는 매칭 엔친의 내부를 잘 알 필요가 있기 때문에 이 문서에서 다루지 않습니다.
정규식 언어는 상대적으로 작고 제한적이라서 정규식을 사용해서 모든 문자열 처리 작업이 가능하진 않습니다. 정규식으로 할 수 있지만, 그 표현식이 너무 복잡한 작업도 있습니다. 이런 경우, 그런 처리를 위한 파이썬 코드를 작성하는 것이 더 나을 수도 있습니다; 파이썬 코드가 정교한 정규식보다는 느리겠지만, 아마 더 이해하긴 쉬울 겁니다.
간단한 패턴
우리는 가장 간단한 정규식을 배우는 것으로 시작할 겁니다. 정규식이 문자열을 처리하는데 사용되므로, 우리는 가장 일반적인 작업으로 시작할 겁니다; 문자 맞추기.
정규식에 깔려 있는 컴퓨터 과학의 자세한 설명(결정적과 비결정적 유한 오토마타)을 위해서는, 컴파일러 작성에 관한 거의 대부분의 아무 교과서나 참고할 수 있습니다.
문자 맞추기
대부분의 글자나 문자들은 간단히 그들 스스로와 맞을 것입니다. 예를 들어, 정규식 test 는 정확히 문자열 test 와 맞습니다. (여러분은 이 RE 가 Test 또는 TEST와 잘 맞도록 대소문자 구별없는 모드를 켤 수 있습니다; 나중에 더 보죠).
이 룰에는 예외가 있습니다; 어떤 문자들은 특별한 메타문자들이고, 그들 자체로 맞추지 않습니다. 대신에, 그들은 기존의 것이 아닌 것과 맞춰질 거라거나 RE의 다른 부분에 그들이 반복되거나 그들의 의미가 바뀌는 것에 의해 영향을 줄거라는 신호를 줍니다. 이 문서의 상당부를 다양한 메타문자들에 대해 이야기하고 그들이 무엇을 하는지에 대해 쓸 겁니다.
여기 메타문자들의 전체 목록입니다; 그들의 의미는 이 사용법의 나머지에서 이야기 될 겁니다.
. ^ $ * + ? { } [ ] | ( )
우리가 볼 첫 메타문자는 [ 와 ] 입니다. 그들은 여러분이 맞추길 원하는 문자들의 집합인 문자 클래스를 지정하는데 사용됩니다. 문자들은 개별적으로 나열될 수도 있고, 두 문자와 그 둘을 나누는 '-'로 범위를 나타낼 수도 있습니다. 예를 들면, [abc]는 a, b, 나 c 문자 중 하나와 맞을 겁니다; 이는 문자의 같은 집합을 표현하는 범위를 사용하는 [a-c] 와 같습니다. 여러분이 소문자만 맞추길 원했다면, 여러분의 RE는[a-z]가 됩니다.
메타문자들은 클래스 안에서는 효력이 없습니다. 예를 들어, [akm$] 는 'a', 'k', 'm' 또는 '$' 중 어느 하나와 맞을 겁니다. '$'는 보통 메타문자입니다만, 문자 클래스 안에서는 그 뜻을 잃게 됩니다.
여러분은 집합을 뒤집음으로써 클래스 안에 나열되지 않은 문자들을 맞출 수 있습니다. 이는 클래스의 첫문자로 '^' 를 넣는 것으로 나타냅니다. 예를 들어, [^5] 는 '5' 를 제외한 다른 글자와 맞춰질 겁니다. 캐럿이 문자 클래스 안의 다른 곳에 있다면, 특별한 뜻이 없습니다. 예를 들어: [5^]는 '5' 나 '^' 중 하나와 맞춰질 겁니다.
아마 가장 중요한 메타문자는 백슬래시 일겁니다. 파이썬의 문자 리터럴에서처럼, 백슬래시는 뒤에 붙는 다양한 문자로, 다양하고 특별한 문자 연속을 표시할 수 있습니다. 이는 또한 모든 메타문자들을 예외 처리하는데 사용되어서 여러분은 여전히 패턴 안에서 그들을 맞출 수 있습니다; 예를 들어, 여러분이 [ 나 를 맞출 필요가 있다면, 여러분은 그들의 특별한 의미를 없애기 위해서 그들 앞에 이걸 붙일 수 있습니다; [ 나 \.
'' 로 시작하는 특별한 문자 연속들 일부는 숫자의 집합, 글자의 집합, 공백 문자가 아닌 다른 것들의 집합처럼 유용한 미리 정의된 문자들의 집합을 나타냅니다.
예를 한번 보죠: w 는 어떤 글자와 숫자로 된 문자와 맞춰집니다. regex 패턴이 바이트들로 표현된다면, 이는 클래스 [a-zA-Z0-9_] 와 동등합니다. regex 패턴이 문자열이라면, w는 unicodedata 모듈에서 제공하는 유니코드 데이터베이스 안의 문자로 표시된 모든 문자와 맞춰질 겁니다. 여러분은 정규식을 컴파일할 때 re.ASCII 플래그를 줌으로써 문자열 패턴 안의 w의 좀 더 제한적인 정의를 사용할 수도 있습니다.
다음 특별한 문자 연속의 목록은 완전하진 않습니다. 문자 연속의 완전한 목록과 유니코드 문자열 패턴의 확장된 클래스 정의를 위해서는, 표준 라이브러리 레퍼런스의 정규식 문법의 마지막 부분을 보세요. 보통 유니코드 버전은 유니코드 데이터베이스 안의 적절한 분류 내에 있는 어떤 문자와 맞춰집니다.
d
10진수 숫자와 맞춰짐; 클래스 [0-9] 와 동등.
D
10진수가 아닌 다른 문자와 맞춰짐; 클래스 [^0-9]와 동등.
s
공백 문자와 맞춰짐; 클래스 [ tnrfv]와 동등.
S
공백 문자가 아닌 문자와 맞춰짐; 클래스 [^ tnrfv]와 동등.
w
글자와 숫자로 된 문자와 맞춰짐; 클래스 [a-zA-Z0-9_]와 동등.
W
글자와 숫자가 아닌 문자와 맞춰짐; 클래스 [^a-zA-Z0-9_]와 동등.
이들 문자 연속은 문자 클래스 안에 포함될 수 있습니다. 예를 들어, [s,.]는 공백 문자나 ',' 또는 '.'과 맞춰지는 문자 클래스입니다.
이 절의 마지막 메타문자는 . 입니다. 이는 개행 문자를 제외한 나머지와 맞춰집니다. 그리고 하나의 개행 문자일 때도 맞추는 다른 모드 (re.DOTALL) 도 있습니다. .은 여러분이 “아무 문자”나 맞추길 원할 때 종종 쓰입니다.
반복되는 것
정규식은 문자열에서 이용가능한 메소드들로 할 수 없는 것을 문자의 다양한 집합과 맞출 수 있도록 되는 것이 첫번째 입니다. 그러나 그것만 regexes의 부가적인 능력이었다면, 그들이 이렇게 많이 발전하지 않았을 겁니다. 다른 능력은 RE의 일부가 정해진 횟수만큼 반복될 것임을 지정할 수 있다는 겁니다.
우리가 볼 반복되는 것들을 위한 첫 메타문자는 *입니다. *는 리터럴 문자 '*'와 맞지 않습니다; 대신, 이전 문자가 정확히 한번 대신, 0번 또는 그 이상 맞춰질 수 있음을 지정합니다.
예를 들어, ca*t는 'ct'(0 'a'문자), 'cat'(1 'a'), 'caaat'(3 'a'문자들), 이렇게요.
* 와 같은 반복은 그리디(greedy)합니다; 하나의 RE가 반복될 때, 매칭 엔진은 가능한한 많이 반복하도록 하려 할 겁니다. 패턴의 나중 부분이 맞지 않으면, 매칭 엔진은 그럼, 뒤로 돌아가서 좀 더 적은 반복으로 다시 시도해 볼 겁니다.
단계별 예제는 이를 좀 더 명확히 할 겁니다. 표현식 a[bcd]*b를 보죠. 이것은 글자 'a', 클래스 [bcd]로부터의 0번 또는 그 이상의 문자들, 그리고 마지막에 'b'로 끝나는 것과 맞습니다. 이제 문자열 'abcbd'에 이 RE를 맞추는 것을 그려봅시다.
단계
맞춰진 것
설명
1
a
RE 안의 a와 맞음.
2
abcbd
엔진이 문자열의 끝인 갈 수 있는 데까지 간 [bcd]*를 맞춤.
3
실패
엔진이 b 맞추기를 시도하지만, 현재 위치가 문자열의 끝, 그래서 실패
4
abcb
돌아옴, 그래서 [bcd]*가 한 글자 적게 맞춰짐.
5
실패
b를 다시 시도, 그러나 현재 위치가 마지막 문자인 'd'.
6
abc
다시 돌아옴, [bcd]*는 bc만 맞춰짐.
6
abcb
b를 다시 시도. 이번엔 현재 위치의 문자가 'b', 그래서 성공.
이제 RE의 끝에 닿았고, 'abcb'와 맞춰졌습니다. 이것은 매칭 엔진이 어떻게 먼저 갈 수 있는 곳까지 가고, 안 맞으면 계속 돌아오면서 RE의 나머지와 계속해서 재시도하는지를 보여줍니다. 이건 [bcd]*가 한번도 안맞을 때까지 돌아오고, 계속 실패했다면, 엔진은 그 문자열이 그 RE와 하나도 안맞는다고 결론내렸을 겁니다.
다른 반복 메타문자는 한번 또는 그 이상과 맞추는 +입니다. * 와 +사이의 차이에 주의하세요; *는 0 또는 그 이상과 맞추므로 반복될 것이 아예 없을 수도 있는 반면, +는 적어도 한번은 있어야 합니다. 비슷한 예를 들면, ca+t는 'cat' (1 'a'), 'caaat' (3 'a'), 와 맞지만, 'ct'와는 맞지 않습니다.
두 개의 반복 수식자가 더 있습니다. 물음표 ?는 1번 아니면 0번과 맞습니다; 여러분은 어떤 것이 옵션으로 있다고 생각할 수 있습니다. 예를 들어, home-?brew는 'homebrew', 아니면 'home-brew'중 하나와 맞습니다.
가장 복잡한 반복 수식자는 {m,n}입니다. 여기서 m과 n은 10진수입니다. 이 수식자는 거기에 최소 m에서 최대 n의 반복이 있어야 함을 뜻합니다. 예를 들어, a/{1,3}b는 'a/b', 'a//b', 그리고 'a///b'와 맞을 겁니다. 그러나 슬래시가 없는 'ab', 또는 4개를 가지는 'a////b'와는 맞지 않습니다.
여러분은 m이나 n 중 하나를 생략할 수 있습니다. 이런 경우, 없는 값은 타당한 값으로 가정됩니다. m을 생략하면 하한이 0으로 처리되는 반면, n을 생략하면 상한이 무한이 됩니다.
환원주의자 성향의 독자들은 다른 세가지 수식자가 모두 이 표기를 이용해서 표현될 수 있음을 알아차렸을 겁니다. {0,}는 *와 같고, {1,}는 +와 동등하고, {0,1}은 ?와 같습니다. 짧고 읽기 쉬우니까 그냥 사용할 때 *, +, 나 ?를 사용하는게 더 좋습니다.
정규식 사용하기
간단한 정규식을 보고난 지금, 우리는 어떻게 이걸 파이썬에서 사용할 수 있을까요? re 모듈이 여러분이 RE를 객체로 컴파일하고, 그걸로 맞추도록 할 수 있는 정규식 엔진으로의 인터페이스를 제공합니다.
정규식 컴파일하기
정규식은 맞춰지는 패턴을 찾는다거나 문자열 치환을 하는 것 같은 다양한 작업을 위한 메소드를 가지는 패턴 객체로 컴파일됩니다.
>>> import re
>>> p = re.compile('ab*')
>>> p
re.compile('ab*')
re.compile() 또한 다양한 특별 기능과 문법 변형을 켜는데 사용되는 옵션으로 flags 인자를 받아들입니다. 우리는 나중에 이용가능한 셋팅으로 넘어갈 겁니다만, 지금은 간단한 예제만 해볼 겁니다:
>>> p = re.compile('ab*', re.IGNORECASE)
RE는 re.compile() 로 하나의 문자열로서 넘어갑니다. RE들은 문자열로서 처리되는데 왜냐하면 정규식은 코어 파이썬 언어의 일부가 아니고, 특별한 문법이 그들을 표현하기 위해서 만들어지지 않았기 때문입니다. (RE가 전혀 필요없는 애플리케이션들이 있으니까 그들을 포함함으로써 언어 사양을 부풀릴 필요는 없습니다.) 대신, re 모듈은 간단히 그냥 socket이나 zlib 모듈처럼 파이썬에 포함된 하나의 C 확장 모듈입니다.
문자열로 된 RE는 파이썬 언어를 더 간결하게 유지하게 해주지만, 다음 절의 주제인 단점도 하나 가지고 있습니다.
백슬래시 괴롭힘
앞서 언급한대로, 정규식은 특별한 형식을 나타내거나 특별 문자를 특별한 뜻의 적용 없이 사용되도록 하기 위해서 백슬래시 문자('')를 사용합니다. 이는 문자열 리터럴 안에서 같은 목적을 위해서 같은 문자를 사용하는 파이썬의 사용법과 충돌합니다.
여러분이 LaTeX 파일에 있을 수 있는 문자열 section과 맞는 RE를 작성하길 원한다고 해봅시다. 프로그램 코드 안에 무엇을 작성해야 할지 그려보기 위해서, 맞추길 원하는 문자열로 시작하세요. 다음, 여러분은 모든 백슬래시나 다른 메타문자들의 앞에 백슬래시를 하나 붙여서 문자열 \section이 되도록 예외 처리를 해야 합니다. re.compile() 로 넘겨져야 하는 결과 문자열은 \section이 될 겁니다. 그러나 이를 파이썬 문자열 리터럴로 표현하기 위해서 백슬래시 둘 모두를 다시 예외 처리해야 합니다.
요컨대, 리터럴 백슬래시를 맞추기 위해서, RE 문자열로 '\\'를 작성해야 합니다. 왜냐하면 정규식은 \가 될테고, 각 백슬래시는 정규 파이썬 문자열 리터럴의 안에서는 \로 표현되어야 하니까요. 백슬래시들이 반복되는 특성의 RE 안에서, 이것은 많이 반복되는 백슬래시를 이끌고, 결과 문자열을 이해하기 어렵게 만듭니다.
해결 방법은 정규식을 위해 파이썬의 무가공(raw) 문자열 표기를 사용하는 겁니다; 백슬래시들은 'r'이 앞에 붙은 문자열 리터럴 안에서는 어떤 특별한 방법으로도 처리되지 않습니다. 그래서 r"n"은 ''와 'n'을 포함하는 두 문자의 문자열인 반면, "n"은 개행문자를 포함하는 한 문자의 문자열입니다. 정규식은 이 무가공 문자열 표기를 사용해서 파이썬 코드로 작성되곤 합니다.
게다가 정규식에서 유효하지만, 파이썬 문자열 리터럴로는 유효하지 않은 특별한 예외 처리 연속들은 이제 무가공 문자열 표기나 백슬래시들의 예외 처리가 되지 않으면 그 연속들이 유효하지 않음을 뜻하는 DeprecationWarning를 만들고 결국 SyntaxError가 될 겁니다.
정규 문자열
무가공 문자열
"ab*"
r"ab*"
"\\section"
r"\section"
"\w+\s+\1"
r"w+s+1"
맞추기 하기
여러분이 컴파일된 정규식을 나타내는 객체를 갖고 나면, 이걸로 무엇을 할까요? 패턴 객체는 여러 메소드와 속성을 갖습니다. 가장 두드러지는 것만 여기서 다룰 겁니다; 완전한 목록은 re 문서를 살펴보세요.
맞는 걸 찾을 수 없으면, match()와 search()는 None을 반환합니다. 그들이 성공하면, 맞춘 것에 대한 정보를 포함하는 match object 인스턴스가 반환됩니다: 맞는 부분 문자열이 어디서 시작하고 끝나는지 등.
여러분은 이에 대해 re모듈과 상호 작용하게 실험함으로써 배울 수 있습니다. 여러분이 tkinter가 사용가능하다면, 여러분은 또한 파이썬 배포판에 포함된 시연 프로그램인 Tools/demo/redemo.py를 보길 원할 수도 있습니다. 이것은 여러분을 RE와 문자열로 들어갈 수 있도록 하고, 어떤 RE가 맞고 틀리는지 보여줍니다. redemo.py는 복잡한 RE를 디버그하려고 할 때, 정말 유용할 수 있습니다.
이 사용법은 그 예제를 위해 표준 파이썬 인터프리터를 사용합니다. 먼저 파이썬 인터프리터를 실행하고, re모듈을 import하고, RE를 컴파일하세요:
>>> importre>>> p = re.compile('[a-z]+')
>>> p
re.compile('[a-z]+')
이제 여러분은 RE [a-z]+에 맞는 다양한 문자열 맞춤을 시도할 수 있습니다. 빈 문자열은 +가 ‘하나 또는 그 이상의 반복’을 뜻하기 때문에 전부 맞지 않습니다. match() 는 이 경우 인터프리터가 아무 출력도 찍지 않게 하는 None을 반환합니다. 여러분은 이를 명확히 하기 위해서 명시적으로 match()의 결과를 찍을 수 있습니다.
>>> p.match("")
>>> print(p.match(""))
None
이제 tempo같은 맞아야 하는 문자열로 시도해 봅시다. 이 경우 match() 는 match object를 반환하므로, 여러분은 이후의 사용을 위해 변수 안에 그 결과를 저장하는 것이 좋습니다.
>>> m = p.match('tempo')
>>> m
<re.Match object; span=(0, 5), match='tempo'>
이제 여러분은 맞는 문자열에 관한 정보를 match object에 질의할 수 있습니다. Match object 인스턴스는 또한 여러 메소드와 속성을 갖습니다; 가장 중요한 것들은 다음과 같습니다:
group() 은 RE와 맞는 부분 문자열을 반환한다. start() 와 end() 는 맞춘 것에 시작과 끝 인덱스를 반환합니다. span() 은 하나의 튜플로 시작과 끝 인덱스를 모두 반환합니다. match()가 그 RE가 문자열의 시작과 맞는지만 확인하기 때문에, start()는 항상 0이 될 겁니다. 그러나, 패턴의 search() 메소드는 문자열을 스캔하므로, 맞는 것이 이 경우 0에서 시작하지 않을 수 있습니다.
리터럴을 무가공 문자열 리터럴로 만드는 r 접두어는 이 예제에서 필요합니다. 왜냐하면 파이썬에서 인식되지 않는, 정규식에서는 반대인, 보통의 “cooked” 문자열 리터럴 내의 예외 처리 시퀀스는 이제 DeprecationWarning을 만들고, 결국 SyntaxError가 될 겁니다. 백슬래시 괴롭힘을 보세요.
>>> iterator = p.finditer('12 drummers drumming, 11 ... 10 ...')
>>> iterator
<callable_iterator object at 0x...>
>>> for match in iterator:
... print(match.span())
...
(0, 2)
(22, 24)
(29, 31)
모듈-레벨 함수
여러분은 패턴 객체를 만들고 그 메소드들을 호출하진 않아도 됩니다: re 모듈 또한 match(), search(), findall(), sub() 등등으로 불리는 탑 레벨 함수를 제공합니다. 이들 함수들은 해당하는 패턴 메소드와 첫번째 인자로 추가된 RE 문자열과 함께 같은 인자를 갖고, 여전히 None이나 match object 인스턴스 중 하나를 반환합니다.
내부적으로 이들 함수는 간단히 여러분을 위한 패턴 객체를 생성하고, 그에 대한 적절한 메소드를 호출합니다. 그들은 또한 캐시 안에 컴파일된 객체를 저장하고, 그래서 같은 RE를 사용하는 이후의 호출들은 그 패턴을 다시 계속해서 파싱할 필요가 없을 겁니다.
이들 모듈 레벨 함수들을 사용해야 할까요? 아니면 패턴을 얻고 여러분 스스로 이들 메소드를 호출해야 할까요? 여러분이 루프 안에서 regex를 접근하고 있다면, 이를 미리 컴파일하는 것이 약간의 함수 호출을 절약해줄 겁니다. 루프의 밖에서는 내부 캐시 덕분에 큰 차이는 없을 겁니다.
컴파일 기호
컴파일 기호로 정규식이 어떻게 동작하는지의 측면을 바꿔봅시다. 기호는 하나는 IGNORECASE와 같이 긴 이름과 I같은 한글자의 짧은 이름, 이 두가지 이름으로 re 모듈 안에서 이용 가능합니다. (여러분이 Perl의 패턴 수식자에 친숙하다면, 한글자 형식은 같은 문자를 사용합니다; 예를 들어, re.VERBOSE의 짧은 형식은 re.X) 비트 OR 처리함으로써 여러 기호를 함께 사용할 수도 있습니다; 예를 들어, re.I | re.M는 I과 M 기호 모두 셋팅합니다.
여기 이용 가능한 기호의 표입니다. 각각의 더 자세한 설명이 뒤에 따릅니다.
기호
의미
ASCII, A
w, b, s 그리고 d와 같은 여러 예외 처리를 각각의 속성으로 ASCII 문자 상에만 맞춤
DOTALL, S
.을 개행문자를 포함하는 아무 문자와 맞춤
IGNORECASE, I
대소문자 구별을 하지 않고 맞춤
LOCALE, L
지역 설정에 맞게 맞춤
MULTILINE, M
^ 와 $에 영향을 주는 여러 줄 맞춤
VERBOSE, X (‘확장’을 위함)
더 명확히, 더 이해하기 쉽게 조직할 수 있는, verbose REs를 켬.
IIGNORECASE
대소문자 구별 없는 맞추기를 수행함; 문자 클래스와 리터럴 문자열들은 대소문자 구별 없이 문자를 맞출 것입니다. 예를 들어, [A-Z] 는 소문자 역시 맞출 것입니다. ASCII 기호가 아스키 문자가 아닌 맞추기를 끄기 위해 사용되기 전까지는 완전한 유니코드 맞추기가 수행될 겁니다. [a-z] 나 [A-Z] 가 IGNORECASE 기호의 조합과 함께 사용되면, 52개의 아스키 문자와 4개의 추가 아스키가 아닌 문자를 맞출 겁니다: ‘İ’ (U+0130, 위에 점이 있는 라틴 문자 I), ‘ı’ (U+0131, 점이 없는 라틴 소문자 i), ‘ſ’ (U+017F, 라틴 소문자 긴 s), 그리고 ‘K’ (U+212A, 켈빈(Kelvin) 기호). Spam 은 'Spam', 'spam', 'spAM', 또는 'ſpam' (유니코드 모드에서만 글자가 맞음)이 맞을 겁니다. 이 소문자 만들기는 현재 지역 설정을 고려하지 않습니다; LOCALE 기호를 설정하면 될 겁니다.
LLOCALE
유니코드 데이터베이스 대신 현재 지역 설정에 따르도록 w, W, b, B 와 대소문자 구별 없는 맞추기를 만듦
지역 설정은 언어 차이를 고려한 프로그램 작성에 도움을 주기 위한 C 라이브러리의 기능입니다. 예를 들어, 여러분이 인코딩된 프랑스 글을 처리할 때, 여러분은 단어를 맞추기 위해서 w+ 를 쓸 수 있도록 하고 싶을 겁니다만, w 는 바이트 패턴으로 문자 클래스 [A-Za-z]만 맞출 겁니다; é 나 ç 에 해당하는 바이트를 맞추지 않을 겁니다. 여러분의 시스템이 적절히 설정되어 있고, 프랑스 지역 설정이 골라져있다면, 그런 C 함수들은 그 프로그램에게 é에 해당하는 바이트도 글자로 고려되어야 한다고 알려줄 겁니다. 정규식을 컴파일할 때 LOCALE 기호를 설정하는 것은 w를 위한 이 C 함수들을 사용하도록 컴파일된 객체의 결과를 만들 겁니다; 이건 더 느립니다만 여러분이 기대하는대로 w+ 가 프랑스 단어를 맞추도록 해줍니다. 이 기호의 사용은 파이썬 3에서는 지역 설정 메카니즘이 매우 신뢰할 수 없어서 막히고, 시간에서 “culture” 하나만 처리하고, 8비트 지역설정으로만 동작합니다. 유니코드 (str) 패턴을 위한 유니코드 맞추기는 파이썬 3에서는 이미 기본으로 켜져 있고, 다른 지역 설정/언어를 처리할 수 있습니다.
MMULTILINE
(^ 과 $ 는 아직 설정되지 않았습니다; 그들은 다른 메타문자들 절에서 소개될 겁니다.)
보통 ^ 는 문자열으 시작에서만 맞춰지고, $ 는 문자열의 끝과 (만약 있다면,) 문자열의 끝에서의 개행 문자 직전이 맞춰집니다. 이 기호가 지정되면, ^는 문자열의 시작과 그 문자열 안에서 각 개행 문자가 바로 붙는 각 줄의 시작에서 맞춰집니다. 비슷하게, $메타문자는 문자열의 끝 아니면 각 개행 문자의 직전인 각 줄의 끝과 맞춰집니다.
SDOTALL
특수 문자 '.' 을 개행 문자를 포함하는 모든 문자와 맞추도록 만듭니다; 이 기호 없이는 '.' 는 개행 문자를 제외한 나머지와 맞춰집니다.
AASCII
w, W, b, B, s 와 S 를 완전한 유니코드 맞추기 대신 아스키만 맞춥니다. 이는 유니코드 패턴에서만 의미가 있고, 바이트 패턴에서는 무시됩니다.
XVERBOSE
이 기호는 여러분을 여러분이 그들을 구성하는 방법을 더 유연하게 해줌으로써 더 읽기 쉬운 정규식을 작성하도록 해줍니다. 이 기호가 지정되면, RE 문자열 내의 공백 기호가 문자 클래스 안이나 예외 처리되지 않은 백슬래시 앞에 붙을 때를 제외하고는 무시됩니다; 이는 RE를 더 명확히 구성하고 들여쓸 수 있게 합니다. 이 기호는 또한 여러분이 RE 내에 엔진에 의해서는 무시될 주석을 쓸 수 있게 합니다; 주석은 문자 클래스 안이나 예외 처리되지 않은 백슬래시 앞이 아닌 곳에서 '#' 에 의해 표기됩니다.
예를 들면, 여기 re.VERBOSE를 사용하는 RE가 있습니다; 보세요. 얼마나 읽기 쉬워졌습니까?
charref = re.compile(r"""
&[#] # Start of a numeric entity reference
(
0[0-7]+ # Octal form
| [0-9]+ # Decimal form
| x[0-9a-fA-F]+ # Hexadecimal form
)
; # Trailing semicolon
""", re.VERBOSE)
위의 예제에서 파이썬의 문자열 리터럴의 자동 이어붙임이 RE를 더 작은 조각으로 나누기 위해서 사용되었습니다만, 여전히 re.VERBOSE를 사용한 버전보다 읽기 어렵습니다.
더 큰 패턴 파워
지금까지 우리는 정규식의 기능 중 일부만 다뤘습니다. 이 절에서 우리는 새로운 메타 문자과 어떻게 맞춰진 글의 일부를 얻어오기 위해 그룹을 사용하는지를 다룰 겁니다.
더 많은 메타 문자
우리가 아직 다루지 않은 여러 메타 문자들이 있습니다. 대부분을 이 절에서 다룰 겁니다.
이야기할 남은 메타 문자들 중 일부는 0-너비 단정문(zero-width assertions)입니다. 그들은 엔진이 문자열을 미리 가보도록 하지 않습니다; 대신 전혀 문자를 소비하지 않고, 간단히 성공하든지 실패합니다. 예를 들어, b 는 현재 위치가 단어 경계에 위치하지 않음을 단정합니다; 그 위치는 전혀 b 에 의해 바뀌지 않습니다. 이것은 0-너비 단정이 절대 반복되면 안됨을 의미합니다. 왜냐하면, 그들이 주어진 위치에서 한번 맞춰지면, 그들은 분명히 무한번 맞춰질 수 있기 때문입니다.
|
대안, 또는 “or” 연산자. A와 B가 정규식이라면, A|B 는 A 아니면 B와 맞는 어떤 문자와 맞을 겁니다. | 는 여러 문자의 문자열을 대체할 때 합리적으로 그를 동작하도록 만들기 위해서 매우 낮은 우선 순위를 갖습니다. Crow|Servo 는 'Crow' 아니면 'Servo'와 맞춰지지만, 'Cro', 'w' 나 'S', 그리고 'ervo'와는 안맞습니다.
리터럴 '|'를 맞추기 위해서는 |를 사용하거나, 문자 클래스 안에 [|]처럼 넣으세요.
^
줄의 시작과 맞습니다. MULTILINE 기호가 설정되기 전까지, 이것은 문자열의 시작과만 맞을 겁니다. MULTILINE 모드에서 이것은 또한 문자열 내의 각 개행문자 직후와 맞습니다.
예를 들어, 여러분이 줄 시작에 있는 단어 From 만 맞추길 원한다면, 사용될 RE는 ^From입니다.
>>> print(re.search('^From', 'From Here to Eternity'))
<re.Match object; span=(0, 4), match='From'>
>>> print(re.search('^From', 'Reciting From Memory'))
None
문자열의 시작과만 맞습니다. MULTILINE 모드가 아니면, A 와 ^ 는 사실상 같습니다. MULTILINE 모드에서 그들은 다릅니다: A 는 여전히 문자열의 시작과만 맞습니다만, ^ 는 문자열 내의 개행 문자 다음의 위치에서만 맞을 수 있습니다.
Z
문자열의 끝과만 맞습니다.
b
단어 경계. 단어의 시작이나 끝에서만 맞는 0-너비 단정입니다. 단어는 글자와 숫자의 연속으로 정의되므로, 단어의 끝은 공백 문자나 글자와 숫자가 아닌 문자로 나타내어 집니다.
다음 예는 완전한 단어인 class 와만 맞춰집니다; 다른 단어 내에 포함된 것은 맞춰지지 않습니다.
>>> p = re.compile(r'bclassb')
>>> print(p.search('no class at all'))
<re.Match object; span=(3, 8), match='class'>
>>> print(p.search('the declassified algorithm'))
None
>>> print(p.search('one subclass is'))
None
이 특별한 시퀀스를 사용할 때 기억해야 할 두가지가 있습니다. 첫째, 이것은 파이썬의 문자열 리터럴과 정규식 시퀀스 사이에 가장 나쁜 충돌입니다. 파이썬의 문자열 리터럴 안에서, b 는 백스페이스 문자인, 아스키 값 8입니다. 여러분이 무가공 문자열을 사용하지 않는다면, 파이썬은 b 를 백스페이스로 변환할 것이고, 여러분의 RE는 기대하는 것처럼 맞지 않을 겁니다. 다음 예제는 우리의 이전 RE와 같은 것을 보입니다만, RE 문자열의 앞에 'r' 을 생략합니다.
>>> p = re.compile('bclassb')
>>> print(p.search('no class at all'))
None
>>> print(p.search('b' + 'class' + 'b'))
<re.Match object; span=(0, 7), match='x08classx08'>
둘째, 이 단정의 사용이 없는 문자 클래스 안에서, b 는 파이썬 문자열 리터럴의 호환성을 위해서 백스페이스 문자를 나타냅니다.
B
b의 반대인, 현재 위치가 단어 경계가 아닐 때 맞는 다른 0-너비 단정
그룹 짓기
종종 여러분은 그냥 어떤 RE가 맞는지 아닌지보다 더 많은 정보를 얻을 필요가 있습니다. 정규식은 종종 다른 용도의 구성 요소와 맞는 여러 하위 그룹으로 나뉜 RE를 작성함으로써 문자열을 나누는데 사용됩니다. 예를 들면, RFC-822 헤더 줄은 헤더 이름과 값이 ':' 로 분리되어 다음과 같이 나뉩니다:
From: author@example.com
User-Agent: Thunderbird 1.5.0.9 (X11/20061227)
MIME-Version: 1.0
To: editor@example.com
이는 헤더 이름과 맞는 그룹 하나와 헤더 값과 맞는 다른 그룹 하나를 갖는, 전체 헤더 줄과 맞는 정규식을 작성함으로써 처리할 수 있습니다.
그룹들은 '(', ')' 메타 문자로 표기됩니다. '(' 와 ')' 는 그들이 수학식에서 하는 것과 상당히 같은 뜻을 갖습니다; 그들은 그들 내부에 표현된 식들을 함께 그룹짓고, *, +, ?, 나 {m,n} 같은 반복 한정자로 한 그룹의 내용을 반복할 수 있습니다. 예를 들어, (ab)* 는 ab의 0 또는 그 이상의 반복과 맞을 겁니다.
>>> p = re.compile('(ab)*')
>>> print(p.match('ababababab').span())
(0, 10)
'(', ')'로 나타내어지는 그룹은 또한 그들이 맞춘 글의 첫번째와 마지막 인덱스를 잡아옵니다; 이것은 group(), start(), end(), 그리고 span()에 인자로 넘김으로써 얻을 수 있습니다. 그룹은 0으로 시작해서 번호가 붙습니다. 그룹 0은 언제나 존재합니다; 이것은 전체 RE입니다. 그래서 match object 메소드들은 모두 그들의 기본 인자로 그룹 0을 갖습니다. 이후에 우리는 어떻게 맞춘 글에서 잡아오지 않는 범위의 그룹을 표현하는지를 볼 겁니다.
>>> p = re.compile('(a)b')
>>> m = p.match('ab')
>>> m.group()
'ab'
>>> m.group(0)
'ab'
하위 그룹들은 왼쪽에서 오른쪽으로 1부터 번호가 붙습니다. 그룹들은 겹쳐질 수 있습니다; 번호를 결정하기 위해서, 그냥 열린 괄호 문자를 왼쪽에서 오른쪽으로 가면서 세세요.
>>> p = re.compile('(a(b)c)d')
>>> m = p.match('abcd')
>>> m.group(0)
'abcd'
>>> m.group(1)
'abc'
>>> m.group(2)
'b'
group()는 그 그룹을 위한 해당하는 값을 포함하는 튜플을 반환하는 경우에 한번에 여러 그룹 번호를 넘길 수 있습니다.
>>> m.group(2,1,2)
('b', 'abc', 'b')
groups() 메소드는 1부터 있는 만큼의 모든 하위 그룹에 대한 문자열을 담고 있는 튜플을 반환합니다.
>>> m.groups()
('abc', 'b')
패턴에서의 역참조는 여러분이 문자열 내의 현재 위치에서도 찾을 수 있을만한, 앞쪽의 잡아온 그룹의 내용을 지정할 수 있도록 합니다. 예를 들어, 1 는 그룹 1의 정확한 내용을 현재 위치에서 찾을 수 있다면 성공하고, 아니라면 실패합니다. 파이썬의 문자열 리터럴 또한 숫자가 붙는 백슬래시를 문자열 내의 임의의 문자를 포함할 수 있도록 하기 위해서 사용한다는 것을 기억하세요. 그러므로 RE 내의 역참조를 결합할 때 무가공 문자열을 사용해야 함을 명심하세요.
예를 들어, 다음 RE는 문자열 내의 두번 반복되는 단어를 찾습니다.
>>> p = re.compile(r'b(w+)s+1b')
>>> p.search('Paris in the the spring').group()
'the the'
이런 역참조는 문자열 내에서 검색을 위해서는 거의 유용하지 않습니다만 — 드물게 이런 방식으로 반복되는 데이터의 글 형식이 있습니다 — 여러분은 곧 문자 치환을 수행할 때 그들이 매우 유용한 것을 발견할 겁니다.
잡아오지 않기와 이름 지은 그룹
정교한 RE는 찾고 싶은 문자열을 잡아 오기 위해서, 또 RE 스스로를 그룹 짓고 구성하도록 하는, 둘 모두를 위해서 많은 그룹을 사용할 수 있습니다. 복잡한 RE에서 그룹 번호의 추적을 유지하는 것은 어려워 집니다. 이 문제를 도와주는 두가지 기능이 있습니다. 둘 모두 정규식 확장을 위한 공통 문법을 사용하기 때문에, 우리는 그걸 먼저 볼 겁니다.
Perl 5는 정규식을 표준화하는 그 강력한 부가기능으로 잘 알려져 있습니다. 이들 새로운 기능을 위해 Perl 개발자들은 표준 RE들로부터 혼란스럽게 다른 Perl의 정규식을 만드는 일 없이 새로운 키 한번의 메타문자나 로 시작하는 새로운 특수 시퀀스를 선택할 수 없었습니다. 예를 들어, 그들이 새로운 메타문자로 &를 골랐다면, 이전 식들은 &가 정규 문자였고 & 나 [&]를 씀으로써 예외 처리되지 않았었음을 가정하게 됩니다.
Perl 개발자들이 선택한 해결책은 확장 문법으로 (?...)를 사용하는 것이었습니다. ?는 반복을 갖지 않아서 괄호 바로 뒤의 ?는 문법 에러였고 그래서 어떤 호환성 문제도 일으키지 않았었습니다. ? 바로 뒤의 문자는 무슨 확장이 사용될 것인지를 나타냅니다. 그래서 (?=foo) 는 어떤 한가지(긍정 예견 단정)이고 (?:foo)는 다른 한가지(하위식 foo를 포함하는 잡아오지 않는 그룹)입니다.
파이썬은 Perl의 확장 중 여럿을 지원하고 Perl의 확장 문법에 표현식 문법을 추가합니다. 물음표 뒤에 첫번째 문자가 P라면 여러분은 파이썬에 지정되는 확장임을 압니다.
우리가 일반 확장 문법을 본 지금, 우리는 복잡한 REs에서 그룹으로 작업하는 것을 간략화하는 기능으로 돌아올 수 있습니다.
어떤 때 여러분은 정규식의 일부를 의미하지만 그룹의 내용을 얻는데는 관심이 없는 그룹을 사용하길 원할 겁니다. 여러분은 잡아오지 않는 그룹을 사용해서 명시적으로 이렇게 만들 수 있습니다: 어떤 다른 정규식으로 여러분이 ...을 대체하는 (?:...)
>>> m = re.match("([abc])+", "abc")
>>> m.groups()
('c',)
>>> m = re.match("(?:[abc])+", "abc")
>>> m.groups()
()
그 그룹이 맞는 내용을 얻을 수 없는 사실을 제외하면, 잡아오지 않는 그룹은 정확히 잡아오는 그룹과 똑같은 동작을 합니다; 여러분은 그 안에 아무거나 넣을 수 있고, *같은 반복 메타문자로 반복할 수도, 다른 그룹 (잡아오거나 잡아오지 않거나) 안에 겹칠 수도 있습니다. (?:...)는 여러분이 모든 다른 그룹의 번호를 붙이는 것을 바꾸는 일 없이 새로운 그룹을 추가할 수 있는 한, 기존의 패턴을 수정할 때 특히 유용합니다. 잡아오는 그리고 잡아오지 않는 그룹 사이의 검색에서 성능 차이가 없다는 점도 언급되는게 좋겠습니다; 다른 것보다 둘다 빠르지 않습니다.
그 이상의 눈에 띄는 기능은 이름 지은 그룹입니다: 번호로 참조되는 대신, 그룹이 이름으로 참조될 수 있습니다.
이름 지은 그룹의 문법은 파이썬 특정 확장 중 하나입니다: (?P<name>...). name 은 분명하게 그 그룹의 이름입니다. 이름 지은 그룹은 잡아오는 그룹과 정확히 같은 동작을 하고, 추가로 그 그룹에 이름을 연관짓습니다. 잡아오는 그룹으로 일하는 match object 메소드들 모두 번호로 그 그룹을 참조하는 정수나 원하는 그 그룹의 이름을 담는 문자열 중 하나를 받아들입니다. 이름 지은 그룹들은 여전히 번호가 붙기 때문에 여러분은 두가지 방법으로 한 그룹의 정보를 얻을 수 있습니다:
>>> p = re.compile(r'(?P<word>bw+b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
>>> m.group(1)
'Lots'
이름 지 그룹들은 그들이 여러분에게 번호를 기억하는 대신 쉽게 기억되는 이름으로 사용되기 때문에 간편합니다. 여기 imaplib 모듈로부터의 예제 RE입니다:
그룹 9를 얻는 것을 기억하는 대신 m.group('zonem')를 얻는 것이 명확히 더 쉬쉽니다.
표현식 내의 역참조를 위한 (...)1같은 문법은 그룹의 번호를 참조합니다. 번호 대신 그룹 이름을 사용하는 변형이 자연스럽게 있습니다. 이는 다른 파이썬 확장입니다: (?P=name)는 현재 점에서 다시 맞을 name 으로 불리는 그룹의 내용을 나타냅니다. 두번 반복되는 단어를 찾는 정규식, b(w+)s+1b 은 b(?P<word>w+)s+(?P=word)b로도 쓰일 수 있습니다:
>>> p = re.compile(r'b(?P<word>w+)s+(?P=word)b')
>>> p.search('Paris in the the spring').group()
'the the'
예견 단정문
다른 0-너비 단정문은 예견 단정문입니다. 예견 단정문은 긍정과 부정 형식 모두로 이용 가능하고, 이렇게 보입니다:
(?=...)긍정 예견 단정문. 이것은 여기서 ...로 표현된 정규식이 현재 위치에서 맞으면 성공하고, 아니면 실패합니다. 그러나 포함된 식이 시도되고 나면 매칭 엔진은 전혀 앞으로 나가지 않습니다; 패턴의 나머지는 단정문이 시작된 바로 거기서 시도됩니다.
(?!...)부정 예견 단정문. 이것은 긍정 단정의 반대입니다: 포함된 식이 문자열 안의 현재 위치에서 맞지 않으면 성공합니다.
더 확실히 하기 위해서, 예견이 유용한 경우를 보죠. 파일 이름을 맞추고 이를 앞쪽 이름과 확장자로 .로 나누기 위한 간단한 패턴을 봅시다. 예를 들어, news.rc에서, news가 앞쪽 이름이고 rc가 파일 이름의 확장자입니다.
이를 맞추기 위한 패턴은 매우 간단합니다:
.*[.].*$
.이 메타문자라서 특별히 다뤄질 필요가 있음을, 그래서 그 지정된 문자만 맞추기 위해서 문자 클래스 안에 있음을 주의하세요. 끝에 붙은 $도 주의하세요; 이것은 문자열의 나머지 모두가 확장 안에 포함되어야 함을 보장하기 위해 붙습니다. 이 정규식은 foo.bar 와 autoexec.bat 와 sendmail.cf 그리고, printers.conf와 맞습니다.
이제 약간 복잡한 문제를 생각해보죠; 여러분이 확장자가 bat가 아닌 파일 이름을 맞추고 싶다면 어떻게 해야 할까요? 이런 틀린 시도는 어떨까요:
.*[.][^b].*$ 첫번째 위의 시도는 확장자의 첫번째 문자가 b가 아닌 것을 요구함으로써 bat를 제외시려고 했습니다. 틀렸습니다. 왜냐하면 패턴이 foo.bar도 못맞추거든요.
.*[.]([^b]..|.[^a].|..[^t])$
여러분이 맞추기 위한 다음 경우를 요구하면서 첫번째 해결책의 패치를 시도하면 식이 좀 더 지저분해집니다: 첫문자가 b가 아니거나; 두번째 문자가 a가 아니거나; 또는 세번째 문자가 t가 아니다. 이것은 foo.bar를 받아들이고 autoexec.bat를 거부합니다만, 세 문자 확장자가 필요하고, sendmail.cf같은 두 문자 확장자로 된 파일 이름은 받아들이지 않을 겁니다. 우리는 이를 고치기 위한 노력으로 패턴을 다시 한번 복잡하게 할 겁니다.
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
세번째 시도에서는 sendmail.cf 같은 세 문자보다 짧은 확장자를 맞출 수 있도록 두번째와 세번째 문자를 모두 옵션으로 만들었습니다.
패턴이 이제 정말로 점점 읽기도 이해하기도 어렵게 복잡해지고 있습니다. 더 나쁜 점은 문제가 바뀌어서 여러분이 확장자 bat와 exe 둘 다 제외하고 싶다면, 그 패턴이 훨씬 더 복잡해지고 헷갈릴겁니다.
부정 예견이 이런 모든 혼란을 차단합니다:
.*[.](?!bat$)[^.]*$ 부정 예견은 다음을 의미합니다: 표현식 bat가 이 지점에서 맞지 않으면, 패턴의 나머지를 시도하세요; bat$가 맞으면, 전체 패턴은 실패할 겁니다. 끝에 붙는 $ 는 확장자가 bat로만 시작하면서 허용될, sample.batch 같은 것을 보장하기 위해 필요합니다. [^.]*는 파일 이름에 여러 개의 점이 있을 때 그 패턴이 동작함을 확실히 합니다.
다른 파일 이름 확장자를 제외하는 것은 이제 쉽습니다; 그냥 단정문 내부에 다른 것을 추가하세요. 다음 패턴은 bat나 exe로 끝나는 파일 이름을 제외합니다:
.*[.](?!bat$|exe$)[^.]*$
문자열 수정하기
이 지점까지 위에서, 우리는 간단히 고정된 문자열을 찾는 것을 수행해 보았습니다. 정규식은 또한 보통 다양한 방법으로 다음 패턴 메소드를 사용해서 문자열을 수정하는데 사용됩니다.
메소드/속성
목적
split()
RE가 맞을 때마다 나눠서 문자열을 리스트로 나눕니다.
sub()
RE가 맞는 곳에서 모든 부분 문자열을 찾아서, 다른 문자열로 그들을 바꿉니다.
subn()
sub()와 같은 것을 합니다만, 새로운 문자열과 바꾼 횟수를 반환합니다.
문자열 나누기
패턴의 split() 메소드는 RE가 맞을 때마다 문자열을 나누고 조각들의 리스트를 반환합니다. 문자열의 split() 메소드와 비슷하지만, 여러분이 나누기 위해 사용하는 구분자에서 더 많은 일반성을 제공합니다; 문자열 split()은 공백 문자나 고정된 문자열로만 나누는 것을 지원합니다. 여러분이 기대하는 대로 모듈 레벨 re.split() 함수 역시 있습니다.
.split(string[, maxsplit=0])
정규식의 맞춤으로 string을 나눔. 잡아오는 괄호가 RE에서 사용되면, 그들의 내용을 반환 리스트의 일부로 반환. maxsplit이 0이 아니면, 최대 maxsplit번 나눔이 이뤄짐.
여러분은 maxsplit의 값을 넘김으로써 나누는 횟수를 제한할 수 있습니다. maxsplit이 0이 아니면, 최대 maxsplit 번의 나눔이 일어나고, 문자열의 나머지는 리스트의 마지막 항목으로 반환됩니다. 다음 예제에서, 구분자는 글자나 숫자가 아닌 문자의 아무 연속이 됩니다.
>>> p = re.compile(r'W+')
>>> p.split('This is a test, short and sweet, of split().')
['This', 'is', 'a', 'test', 'short', 'and', 'sweet', 'of', 'split', '']
>>> p.split('This is a test, short and sweet, of split().', 3)
['This', 'is', 'a', 'test, short and sweet, of split().']
어떤 때 여러분은 구분자들 사이의 텍스트 뿐만 아니라 구분자가 무엇이었는지는 알 필요가 있습니다. 잡아오는 괄호들이 RE내에서 사용되면, 그들의 값들은 리스트의 일부로 반환됩니다. 다음 호출들을 비교해보세요:
>>> p = re.compile(r'W+')
>>> p2 = re.compile(r'(W+)')
>>> p.split('This... is a test.')
['This', 'is', 'a', 'test', '']
>>> p2.split('This... is a test.')
['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']
모듈 레벨 함수 re.split()는 첫 인자로 사용되는 RE가 추가됩니다만, 다른 것은 같습니다.
다른 보통의 작업은 패턴이 모두 맞는 곳을 찾아 다른 문자열로 그들을 바꾸는 겁니다. sub() 메소드는 문자열이나 함수가 될 수 있는, 바꾸는 값과 처리될 문자열을 취합니다.
.sub(replacement, string[, count=0])
string내의 RE가 왼쪽부터 겹쳐지지 않는 곳에 있는 것을 replacement로 바꿈으로써 얻은 문자열을 반환. 패턴이 없다면, 바뀌지 않고 string이 반환됨.
옵션 인자 count는 패턴이 맞는 곳에서 바꿀 최대 횟수; count는 음수가 아닌 정수여야 함. 기본 값 0은 모든 곳을 바꿈을 의미.
여기 sub() 메소드 사용의 간단한 예제가 있습니다. 이것은 색의 이름을 단어 colour로 바꿉니다:
>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'
>>> p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'
subn() 메소드는 같은 동작을 합니다만, 새로운 문자열 값과 수행으로 바뀐 횟수를 가진 2-튜플을 반환합니다:
>>> p = re.compile('(blue|white|red)')
>>> p.subn('colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)
>>> p.subn('colour', 'no colours at all')
('no colours at all', 0)
빈 맞춰짐은 그들이 이전 맞춰진 것과 붙어 있지 않을 때만 바뀝니다.
>>> p = re.compile('x*')
>>> p.sub('-', 'abxd')
'-a-b--d-'
replacement가 문자열이면, 그 안의 어떤 백슬래시 예외도 처리됩니다. 즉, n는 하나의 개행 문자로 바뀌고, r는 캐리지 리턴으로 바뀌고, 그렇게 됩니다. &같은 알려지지 않은 예외처리는 홀로 남습니다. 6같은 역참조는 RE 내에 해당하는 그룹에 맞는 부분 문자열로 바뀝니다. 이는 원래 글의 일부가 결과가 되는 바뀐 문자열 안으로 포함되도록 합니다.
이 예제는 {, }로 둘러싸인 문자열이 뒤에 붙는 단어 section을 맞추고, section을 subsection로 바꿉니다:
(?P<name>...) 문법으로 정의되는 이름 지은 그룹으로 참조되는 문법 또한 존재합니다. g<name>는 name으로 이름 지은 그룹에 의해 맞춰진 부분 문자열을 사용하고, g<number>는 해당하는 그룹 number를 사용합니다. g<2>는 2와 동등하지만, g<2>0 같은 바꾸는 문자열 내에서 모호합니다(20는 리터럴 문자 '0'가 뒤에 붙는 그룹 2의 참조가 아니라, 그룹 20의 참조로 처리될 겁니다). 다음 치환들은 모두 동등합니다만, 바꾸는 문자열의 세가지 변형을 사용합니다.
replacement는 여러분에게 더 많은 제어권을 주는 함수도 될 수 있습니다. replacement가 함수라면, 그 함수는 pattern의 모든 겹치지 않는 맞는 곳마다 불리게 됩니다. 각 호출마다, 그 함수는 맞춰진 것에 대한 match object 인자를 넘기고, 이 정보를 원하는 바꾸는 문자열을 계산하는데 사용하고 반환하는데 사용하고, 이를 반환합니다.
다음 예제에서, 바꾸는 함수는 10진수를 16진수로 변환합니다:
>>> def hexrepl(match):
... "Return the hex string for a decimal number"
... value = int(match.group())
... return hex(value)
...>>> p = re.compile(r'd+')
>>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
'Call 0xffd2 for printing, 0xc000 for user code.'
모듈-레벨 함수 re.sub() 함수를 사용할 때, 그 패턴은 첫번째 인자로 넘겨집니다. 그 패턴은 객체 또는 문자열로서 제공될 수 있습니다; 여러분이 정규식 기호를 지정할 필요가 있다면, 여러분은 첫 파라미터로 패턴 객체를 사용하던가, 패턴 문자열로 내장된 수정자를 사용해야 합니다. 예를 들면, sub("(?i)b+", "x","bbbb BBBB")는 'x x'를 반환합니다.
공통적인 문제들
정규식은 어떤 용도에서 강력한 도구입니다만, 어떤 방법에서 그 동작은 직관적이지 않고, 여러분이 기대하는 방법으로 동작하지 않는 때도 있습니다. 이 절에서는 대부분의 공통적인 위험 중 일부를 지적할 겁니다.
문자열 메소드를 사용하세요
어떤 때는 re 모듈의 사용이 실수입니다. 여러분이 고정된 문자열이나 한 문자 클래스를 맞추고 있고, 여러분이 IGNORECASE 기호 같은 re 기능을 사용하고 있지 않다면, 정규식의 완전한 능력이 필요하지 않을 수 있습니다. 문자열은 고정된 문자열로 수행하는 동작을 위한 여러 메소드를 가지고, 그들은 보통 훨씬 더 빠릅니다. 왜냐하면 그 구현이 크고 더 일반적인 정규식 엔진 대신, 그 목적을 위해 최적화된 하나의 작은 C 루프이기 때문입니다.
하나의 고정 문자열을 다른 하나로 바꾸는 것이 한 예제가 될 수 있습니다; 예를 들어, 여러분은 word를 deed로 바꿀 수 있습니다. re.sub()는 이를 위해 사용할 함수로 보입니다만, replace() 메소드를 고려할 수 있습니다. replace()는 단어들 안의 word 또한 swordfish를 sdeedfish로 바꿈을, 대충의 RE word도 똑같이 함을 주의하세요. (단어의 일부 상의 치환을 피하기 위해서, 그 패턴은 word가 한 쪽에서 단어 경계를 갖기 위해서 bwordb가 되었어야 할 겁니다. 이는 replace()의 능력 너머의 일이 됩니다.)
다른 공통적인 작업은 문자열의 한 문자가 나타날 때마다 지우는 것이나 다른 한 문자로 그를 바꾸는 것입니다. 여러분인 이를 re.sub('n', ' ', S)같은 것으로 할 수 있지만, translate()이 두 작업 모두를 수행할 수 있고, 어떠한 할 수 있는 정규식 동작보다 빠를 겁니다.
요약하면, re 모듈로 바꾸기 전에, 여러분의 문자를 더 빠르고 간단한 문자열 메소드로 풀 수 있는지를 고려하세요.
search()가 맞추기 위해 문자열을 앞으로 훑어 나가는 반면, match() 함수는 문자열의 시작에서 RE 가 맞는지만 체크합니다. 이 차이를 마음 속에 담는 것은 중요합니다. 기억하세요. match()는 0에서 시작할 잘 맞는 것만 알릴 겁니다; 0에서 맞지 않으면 match()는 이를 알리지 않을 겁니다.
어떤 때 여러분은 re.match()를 여러분의 RE 앞에 .* 를 그냥 추가해서 사용하길 유지하고 싶어질 겁니다. 이 유혹을 견디고 대신 re.search()를 사용하세요. 정규식 컴파일러는 맞는 것을 찾기 위한 프로세스의 속도를 높이기 위해서 REs의 분석을 합니다. 이런 분석은 맞는 것의 첫 문자가 무엇이 되어야 할 지를 찾습니다; 예를 들어, Crow로 시작하는 패턴은 'C'로 시작해야만 합니다. 이 분석은 엔진이 첫 문자를 찾는 문자열을 빨리 훑도록, 'C'를 찾았을 때만 전체 맞추는 것을 시도하도록 합니다.
문자열의 끝을 훑도록 하는, .*를 추가하는 것은 이 최적화를 막고, RE의 나머지에 맞는 것을 찾도록 백트래킹 합니다. 대신 re.search()를 사용하세요.
그리디 VS 논-그리디
a*처럼 정규식을 반복할 때, 결과 동작은 가능한한 많은 패턴을 소비하는 것입니다. 이 사실은 여러분이 중괄호로 둘러싸인 HTML 태그 같은 앞뒤가 맞는 구분자 짝을 맞추려고 할 때 종종 여러분을 물어 뜯습니다. 한 HTML 태그를 맞추려는 대충의 패턴은 .*의 그리디한 세계로 인해 동작하지 않습니다.
그 RE는 '<html>' 안의 '<'를 맞추고, .*는 문자열의 나머지를 소비합니다. RE 내에 여전히 더 남아 있긴 하지만, >는 문자열의 끝에서 맞지 않을 수 있습니다. 그래서 정규식 엔진은 >를 찾을 때까지 문자마다 백트랙해야 합니다. 마지막 맞추는 것은 '<html>' 안의 '<'로부터, 여러분이 원치 않는 '</title>' 안의 '>'까지 확장합니다.
이 경우, 해결책은 가능한 더 작은 글을 맞추는 논-그리디 한정자 *?, +?,??, 또는 {m,n}?를 사용하는 겁니다. 위의 예제에서, '>'는 '<'가 맞은 후 바로 시도되고, 실패하면, 엔진은 한번에 한 문자를 나가고, 각 단계마다 '>'를 재시도 합니다. 이것은 그냥 바로 결과를 생성합니다:
>>> print(re.match('<.*?>', s).group())
<html>
(HTML이나 XML을 정규식으로 파싱하는 것은 고통스럽다는 것을 주의하세요. 퀵-앤-더티 패턴들이 보통의 경우를 처리하겠지만, HTML과 XML은 분명한 정규식을 깰 특별한 경우를 가집니다; 여러분이 가능한 모든 경우를 처리하는 정규식을 쓸 수 있긴 하지만, 그 패턴은 매우 복잡할 겁니다. HTML 또는 XML 파서 모듈을 이런 작업에 사용하세요.)
re.VERBOSE 사용하기
이제 여러분은 아마 정규식이 매우 작은 표기지만, 대단히 읽기 쉽진 않음을 알 겁니다. 보통 복잡도의 REs는 그들을 읽고 이해하기 어렵게 만드는, 백슬래시, 괄호, 그리고 메타 문자들의 긴 모음이 될 수 있습니다.
이런 REs를 위해, 정규식을 컴파일 할 때 re.VERBOSE 기호를 지정하는 것은 유용할 수 있습니다. 왜냐하면, 이는 여러분이 정규식을 더 명확히 형식화하도록 해주기 때문입니다.
re.VERBOSE 기호는 여러 효과를 가집니다. 문자 클래스 안에 있지 않은 정규식 내의 공백 문자는 무시됩니다. 이는 dog | cat 같은 식이 더 짧은 읽기 쉬운 dog|cat와 동등하지만, [a b]는 여전히 문자 'a', 'b', 또는 스페이스와 맞음을 의미합니다. 게다가 여러분은 RE 내부에 주석을 달 수도 있습니다; 주석은 #에서 다음 개행문자까지 확장합니다. 세개의 괄호 문자열을 사용할 때, 이는 RE를 더 깔끔하게 형식화할 수 있도록 합니다:
pat = re.compile(r"""
s* # Skip leading whitespace
(?P<header>[^:]+) # Header name
s* : # Whitespace, and a colon
(?P<value>.*?) # The header's value -- *? used to
# lose the following trailing whitespace
s*$ # Trailing whitespace to end-of-line
""", re.VERBOSE)
이는 다음보다 훨씬 읽기 쉽습니다:
pat = re.compile(r"s*(?P<header>[^:]+)s*:(?P<value>.*?)s*$")
피드백
정규식은 복잡한 주제입니다. 이 문서가 이를 이해하는데 도움이 되었나요? 분명하지 않은 부분이 있었거나, 여러분이 부딪힌 여기서 다루지 않은 문제들이 있었나요? 그렇다면, 저자에게 개선을 위해 제안을 보내주세요.
정규식 상의 더 완전한 책은 거의 확실히 제프리 프라이들(Jeffrey Friedl)의 오’레일리(O’Reilly)에서 나온 정규식 마스터하기(Mastering Regular Expressions) 입니다. 불행히도, 그건 오로지 정규식의 Perl과 Java 맛에 집중하고, 파이썬의 것은 전혀 들어있지 않습니다. 그래서 파이썬 프로그래밍의 레퍼런스로는 유용하지 않을 수 있습니다. (첫번째 판은 파이썬의 여러분을 많이 도와주진 않을, 지금은 없어진 regex 모듈을 다뤘었습니다.) 여러분의 도서관에서 구할 수 있는지를 고려해 보세요.