Python 정규 표현식

정규 표현식은 텍스트 패턴을 일치시키는 강력한 언어입니다. 이 페이지에서는 Python 실습에 충분한 정규 표현식 자체에 대한 기본적인 소개를 제공하며 Python에서 정규 표현식이 작동하는 방식을 보여줍니다. Python 're' 모듈은 정규 표현식을 지원합니다.

Python에서 정규 표현식 검색은 일반적으로 다음과 같이 작성됩니다.

match = re.search(pat, str)

re.search() 메서드는 정규 표현식 패턴과 문자열을 가져와서 문자열 내에서 해당 패턴을 검색합니다. 검색이 성공하면 search()는 match 객체를 반환하거나 그렇지 않으면 None을 반환합니다. 따라서, 다음 예와 같이 'word' 패턴을 검색하는 다음 예와 같이 일반적으로 검색 직후에 검색이 성공했는지 테스트하기 위한 if 문이 나옵니다. 뒤에 3글자가 표시됩니다 (자세한 내용은 아래 참조).

import re

str = 'an example word:cat!!'
match = re.search(r'word:\w\w\w', str)
# If-statement after search() tests if it succeeded
if match:
  print('found', match.group()) ## 'found word:cat'
else:
  print('did not find')

match = re.search(pat, str) 코드는 'match'라는 변수에 검색결과를 저장합니다. 그런 다음 if 문이 일치를 테스트합니다. true이면 검색이 성공하고 match.group()은 일치하는 텍스트입니다 (예: 'word:cat'). 그렇지 않고 일치가 false인 경우 (더 구체적으로는 없음) 검색이 실패하고 일치하는 텍스트가 없는 것입니다.

'r' 패턴 문자열 시작 부분에서 python 'raw'를 지정합니다. 문자열이며 변경 없이 백슬래시를 통과합니다. 이는 정규 표현식에 매우 편리합니다 (Java에는 이 기능이 꼭 필요합니다!). 항상 'r'로 된 패턴 문자열을 작성하는 것이 좋습니다. 그냥 습관입니다.

기본 패턴

정규 표현식의 강점은 고정 문자뿐만 아니라 패턴도 지정할 수 있다는 것입니다. 다음은 단일 문자와 일치하는 가장 기본적인 패턴입니다.

  • a, X, 9, < : 일반 문자는 정확히 일치합니다. 특별한 의미가 있어 일치하지 않는 메타 문자는 다음과 같습니다. . ^ $ * + ? { [ ] \ | ( ) (아래 세부정보 참고)
  • . (마침표) -- 줄바꿈 '\n'을 제외한 모든 단일 문자와 일치합니다.
  • \w -- (소문자 w)는 '단어'와 일치합니다. 문자: 문자, 숫자 또는 언더바[a-zA-Z0-9_]. 비록 'word'는 는 이를 나타내는 연상 기호이며, 전체 단어가 아닌 단일 단어 문자와만 일치합니다. \W (대문자 W)는 단어가 아닌 모든 문자와 일치합니다.
  • \b -- 단어와 비단어 사이의 경계
  • \s -- (소문자 s)는 공백, 줄바꿈, return, 탭, 형식 [ \nR\t\f]와 같은 단일 공백 문자와 일치합니다. \S (대문자 S)는 공백이 아닌 모든 문자와 일치합니다.
  • \t, \n, \r -- 탭, 줄바꿈, 반환
  • \d -- 십진수 [0-9] (일부 이전 정규식 유틸리티는 \d를 지원하지 않지만 모두 \w 및 \s를 지원합니다.)
  • ^ = start, $ = end -- 문자열의 시작 또는 끝 부분과 일치합니다.
  • \ -- '특수성'을 금지합니다. 나타냅니다. 예를 들어 \를 사용합니다. 슬래시를 매칭하려면 \\를 사용하면 됩니다. '@' 기호와 같이 문자에 특별한 의미가 있는지 확실하지 않은 경우 문자 앞에 슬래시(\@)를 붙일 수 있습니다. \c와 같이 유효한 이스케이프 시퀀스가 아닌 경우 Python 프로그램이 오류와 함께 중단됩니다.

기본 예

농담: 눈이 세 개인 돼지를 뭐라고 부르나요? 개구르르!

문자열 내 패턴에 대한 정규 표현식 검색의 기본 규칙은 다음과 같습니다.

  • 검색은 처음부터 끝까지 문자열을 통해 진행되며 발견된 첫 번째 일치 항목에서 중지됩니다.
  • 모든 패턴이 일치해야 하지만 모든 문자열이 일치하지는 않습니다.
  • match = re.search(pat, str)가 성공하면 Match는 None이 아니며, 특히 match.group()이 일치하는 텍스트입니다.
  ## Search for pattern 'iii' in string 'piiig'.
  ## All of the pattern must match, but it may appear anywhere.
  ## On success, match.group() is matched text.
  match = re.search(r'iii', 'piiig') # found, match.group() == "iii"
  match = re.search(r'igs', 'piiig') # not found, match == None

  ## . = any char but \n
  match = re.search(r'..g', 'piiig') # found, match.group() == "iig"

  ## \d = digit char, \w = word char
  match = re.search(r'\d\d\d', 'p123g') # found, match.group() == "123"
  match = re.search(r'\w\w\w', '@@abcd!!') # found, match.group() == "abc"

반복

+ 및 * 를 사용하여 패턴에서 반복을 지정하면

  • + -- 1번 이상 왼쪽에 패턴이 있음. 예: 'i+' = 하나 이상의 i
  • * -- 왼쪽에 패턴이 0번 이상 표시됨
  • ? -- 왼쪽에 있는 패턴과 일치하는 항목을 0개 또는 1개 찾습니다.

맨 왼쪽 & 최대 규모

먼저 검색은 패턴과 일치하는 가장 왼쪽에 있는 항목을 찾고 두 번째로 가능한 한 많은 문자열을 사용합니다. 즉, +와 * 를 최대한 많이 사용합니다 (+ 및 * 를 '그리디'라고 함).

반복의 예

  ## i+ = one or more i's, as many as possible.
  match = re.search(r'pi+', 'piiig') # found, match.group() == "piii"

  ## Finds the first/leftmost solution, and within it drives the +
  ## as far as possible (aka 'leftmost and largest').
  ## In this example, note that it does not get to the second set of i's.
  match = re.search(r'i+', 'piigiiii') # found, match.group() == "ii"

  ## \s* = zero or more whitespace chars
  ## Here look for 3 digits, possibly separated by whitespace.
  match = re.search(r'\d\s*\d\s*\d', 'xx1 2   3xx') # found, match.group() == "1 2   3"
  match = re.search(r'\d\s*\d\s*\d', 'xx12  3xx') # found, match.group() == "12  3"
  match = re.search(r'\d\s*\d\s*\d', 'xx123xx') # found, match.group() == "123"

  ## ^ = matches the start of string, so this fails:
  match = re.search(r'^b\w+', 'foobar') # not found, match == None
  ## but without the ^ it succeeds:
  match = re.search(r'b\w+', 'foobar') # found, match.group() == "bar"

이메일 예

'xyz alice-b@google.com 보라색 원숭이' 문자열에서 이메일 주소를 찾으려 한다고 가정해 보겠습니다. 이것을 실행 예제로 사용하여 더 많은 정규 표현식 기능을 시연해 보겠습니다. 다음은 r'\w+@\w+' 패턴을 사용하여 시도해 보겠습니다.

  str = 'purple alice-b@google.com monkey dishwasher'
  match = re.search(r'\w+@\w+', str)
  if match:
    print(match.group())  ## 'b@google'

이 경우 \w가 '-'와 일치하지 않으므로 전체 이메일 주소가 검색되지는 않습니다. 또는 '.' 포함되어 있습니다. 이 문제는 아래의 정규 표현식 기능을 사용하여 수정하겠습니다.

대괄호

대괄호를 사용하여 문자 집합을 나타낼 수 있으므로 [abc] 는 'a'와 일치합니다. 또는 'b' 또는 'c'. 코드 \w, \s 등도 대괄호 안에서 작동합니다. 단, 점 (.)은 문자 그대로의 점을 의미합니다. 이메일 문제의 경우 대괄호를 사용하면 '.'를 쉽게 추가할 수 있습니다. 및 '-' 문자 집합에 r'[\w.-]+@[\w.-]+' 패턴을 사용하여 @ 기호 주위에 나타날 수 있는 문자 집합에 대한 검색 전체 이메일 주소를 가져옵니다.

  match = re.search(r'[\w.-]+@[\w.-]+', str)
  if match:
    print(match.group())  ## 'alice-b@google.com'
드림 (대괄호 기능 더보기) 대시를 사용하여 범위를 나타낼 수도 있으므로 [a-z] 는 모든 소문자와 일치합니다. 범위를 지정하지 않고 대시를 사용하려면 대시를 마지막에 입력합니다. 예를 들어 [abc-]. 대괄호 세트 시작 부분의 업햇 (^)은 이 값을 반전하므로 [^ab] 는 'a'를 제외한 모든 문자를 의미합니다. 또는 'b'.

그룹 추출

'그룹' 기능을 사용하면 일치하는 텍스트 중 일부를 선택할 수 있습니다. 이메일 문제의 경우 사용자 이름과 호스트를 별도로 추출하려고 한다고 가정해 보겠습니다. 이렇게 하려면 r'([\w.-]+)@([\w.-]+)'와 같이 사용자 이름과 호스트를 패턴으로 묶은 괄호( )를 추가합니다. 이 경우 괄호는 패턴이 일치하는 항목을 변경하지 않고 대신 논리적 '그룹'을 설정합니다. 찾을 수 있습니다. 성공적인 검색에서 match.group(1)은 왼쪽 첫 번째 괄호에 해당하는 일치 텍스트가, match.group(2)는 왼쪽 두 번째 괄호에 해당하는 텍스트입니다. 일반 match.group()은 여전히 평소처럼 전체 일치 텍스트입니다.

  str = 'purple alice-b@google.com monkey dishwasher'
  match = re.search(r'([\w.-]+)@([\w.-]+)', str)
  if match:
    print(match.group())   ## 'alice-b@google.com' (the whole match)
    print(match.group(1))  ## 'alice-b' (the username, group 1)
    print(match.group(2))  ## 'google.com' (the host, group 2)

정규 표현식을 사용하는 일반적인 워크플로는 괄호 그룹을 추가하여 찾으려는 대상에 대한 패턴을 작성하고 원하는 부분을 추출하는 것입니다.

Findall

findall()은 re 모듈에서 가장 강력한 단일 함수일 것입니다. 위에서 패턴에 대한 첫 번째 일치 항목을 찾기 위해 re.search() 를 사용했습니다. findall()은 *모든* 일치 항목을 찾아서 각 문자열이 하나의 일치 항목을 나타내는 문자열 목록으로 반환합니다.
  ## Suppose we have a text with many email addresses
  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'

  ## Here re.findall() returns a list of all the found email strings
  emails = re.findall(r'[\w\.-]+@[\w\.-]+', str) ## ['alice@google.com', 'bob@abc.com']
  for email in emails:
    # do something with each found email string
    print(email)

파일이 있는 findall

파일의 경우 루프를 작성하여 파일의 줄을 반복한 다음 각 줄에서 findall()을 호출할 수 있습니다. 대신 findall()을 사용하여 반복 작업을 수행하도록 할 수 있습니다. 훨씬 좋습니다. 전체 파일 텍스트를 findall()에 피드하고 모든 일치 항목 목록을 한 번에 반환하도록 하세요 (f.read()는 파일의 전체 텍스트를 단일 문자열로 반환한다는 점을 기억하세요).

  # Open file
  f = open('test.txt', encoding='utf-8')
  # Feed the file text into findall(); it returns a list of all the found strings
  strings = re.findall(r'some pattern', f.read())

findall 및 그룹스

괄호 ( ) 그룹 메커니즘은 findall()과 결합할 수 있습니다. 패턴에 2개 이상의 괄호 그룹이 포함된 경우 문자열 목록을 반환하는 대신 findall()은 *튜플* 목록을 반환합니다. 각 튜플은 패턴의 한 일치 항목을 나타내며, 튜플 내부에는 group(1), group(2) .. 데이터가 있습니다. 따라서 2개의 괄호 그룹이 이메일 패턴에 추가된 경우 findall()은 튜플의 목록을 반환합니다.각 길이 2에는 사용자 이름과 호스트가 포함됩니다. 예를 들면 ('alice', 'google.com')을 사용합니다.

  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
  tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str)
  print(tuples)  ## [('alice', 'google.com'), ('bob', 'abc.com')]
  for tuple in tuples:
    print(tuple[0])  ## username
    print(tuple[1])  ## host

튜플 목록이 있으면 이를 루프 처리하여 각 튜플에 대해 몇 가지 계산을 수행할 수 있습니다. 패턴에 괄호가 포함되어 있지 않으면 findall()은 앞의 예와 같이 찾은 문자열의 목록을 반환합니다. 패턴에 단일 괄호 쌍이 포함되어 있으면 findall()은 단일 그룹에 해당하는 문자열 목록을 반환합니다. (애매한 선택적 기능: 경우에 따라 패턴에 괄호 ( ) 그룹이 있지만 추출하지 않을 수 있습니다. 이 경우 시작 부분에 ?:로 괄호를 작성합니다(예: (?: ) 및 왼쪽 괄호는 그룹 결과로 계산되지 않습니다.

RE 워크플로 및 디버그

정규 표현식 패턴은 단 몇 개의 문자로 많은 의미를 담을 수 있지만, 패턴이 매우 많기 때문에 패턴을 디버깅하는 데 많은 시간을 할애할 수 있습니다. 런타임을 설정하여 패턴을 실행하고 일치하는 항목을 쉽게 인쇄할 수 있습니다. 예를 들어 작은 테스트 텍스트에서 실행하고 findall()의 결과를 출력합니다. 패턴과 일치하지 않는 경우 패턴을 약하게 하고 일부를 제거하여 일치를 너무 많이 만듭니다. 아무것도 일치하지 않으면 진전이 불가능합니다. 구체적으로 살펴봐야 할 것이 없기 때문입니다. 너무 많이 일치하게 되면 조금 더 조여서 원하는 결과를 얻을 수 있습니다.

옵션

re 함수는 패턴 일치 동작을 수정하는 옵션을 사용합니다. 옵션 플래그는 search() 또는 findall() 등에 추가 인수로 추가됩니다. 예: re.search(pat, str, re.IGNORECASE)

  • IGNORECASE -- 일치 시 대소문자 차이를 무시하므로 'a' 'a' 모두와 일치 'A'를 입력합니다.
  • DOTALL -- 점 (.)이 줄바꿈과 일치하도록 허용합니다. 일반적으로 줄바꿈 이외의 모든 항목과 일치합니다. .* 가 모든 항목과 일치한다고 생각하지만 기본적으로 줄의 끝을 벗어나지 않습니다. \s (공백)에는 줄바꿈이 포함되므로 줄바꿈이 포함될 수 있는 일련의 공백을 일치시키려면 \s*만 사용하면 됩니다.
  • 여러 줄 -- 여러 줄로 구성된 문자열 내에서 ^ 및 $ 기호를 각 행의 시작과 끝부분에 일치시킬 수 있도록 허용합니다. 일반적으로 ^/$ 는 전체 문자열의 시작과 끝 부분과만 일치합니다.

그리디 vs. 비 그리디 (선택사항)

이 섹션은 연습에서 필요하지 않은 고급 정규 표현식 기술을 보여주는 선택적 섹션입니다.

<b>foo</b> 태그가 있는 텍스트가 있다고 가정해 보겠습니다. <i>기타</i>

각 태그를 '(<.*>)' 패턴과 일치시키려고 한다고 가정해 보겠습니다. -- 먼저 어떤 항목이 일치합니까?

결과는 약간 놀랍지만 .* 의 탐욕 측면으로 인해 전체 '<b>foo</b>와 일치합니다. <i>등</i>' 하나의 큰 경기로 볼 수 있습니다. 문제는 .* 가 첫 번째 > ('탐욕'이라고도 함)

? 끝에 .*? 또는 .+?로 변경하여 탐욕이 없는 것으로 변경합니다. 이제는 가능한 한 빨리 멈춥니다. 따라서 '(<.*?>)' 패턴은 '<b>'만 가져옵니다. '</b>'은(는) 첫 번째 일치 항목으로, 를 두 번째 일치로 처리하고 각 <..> 차례로 페어링됩니다. 스타일은 일반적으로 .*? 바로 뒤에 몇 가지 구체적인 마커(이 경우 >)가 옵니다. 이 경우 .*? 확장이 강제로 연장됩니다

*? 확장자가 Perl에서 유래되었으며 Perl의 확장을 포함하는 정규식은 Perl 호환 정규식 -- pcre로 알려져 있습니다. Python에는 pcre 지원 기능이 포함되어 있습니다. 많은 명령줄 유틸리티 등에는 pcre 패턴을 허용하는 플래그가 있습니다.

'X에서 멈추는 경우를 제외하고 모든 문자'라는 개념을 코딩하는 데 오래되었지만 널리 사용되는 기법입니다. 는 대괄호 스타일을 사용합니다. 위의 경우 패턴을 작성할 수 있지만 모든 문자를 가져오려면 .* 대신 >가 아닌 모든 문자를 건너뛰는 [^>]* 를 사용합니다. 선행 ^ 기호는 대괄호 집합을 '반전'하므로 대괄호 안에 있지 않은 모든 문자와 일치합니다.

대체 (선택사항)

re.sub(pat, replacement, str) 함수는 지정된 문자열에서 패턴의 모든 인스턴스를 검색하여 바꿉니다. 대체 문자열에는 '\1', '\2'가 포함될 수 있습니다. 는 원래 일치 텍스트에서 group(1), group(2) 등의 텍스트를 참조합니다.

다음은 모든 이메일 주소를 검색하여 사용자 (\1)를 유지하지만 호스트로 yo-yo-dyne.com을 포함하도록 변경하는 예입니다.

  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
  ## re.sub(pat, replacement, str) -- returns new string with all replacements,
  ## \1 is group(1), \2 group(2) in the replacement
  print(re.sub(r'([\w\.-]+)@([\w\.-]+)', r'\1@yo-yo-dyne.com', str))
  ## purple alice@yo-yo-dyne.com, blah monkey bob@yo-yo-dyne.com blah dishwasher

운동

정규 표현식을 연습하려면 아기 이름 운동을 참조하세요.