해결책
실제 텍스트를 파싱하려면 흔히 하나의 정규식으로 H1이나 (AH2b+EUH3) 같은 코드로 이루어진 구조적 항목을 인식하고 그 뒤에 오는 메시지를 캡처해야 한다. 한 줄로 쓰면 그 패턴은 빽빽한 구두점의 벽이 되었다. 중첩된 그룹, 전방 탐색, 수량자가 주석을 넣을 여지도 없이 늘어서고, 변경할 때마다 문자열 전체를 풀어헤쳐야 했다. 뻔한 해결책에는 각각 비용이 따랐다. 한 줄짜리를 유지하면 모든 수정이 위험해지고, 상세 모드를 직접 작성하면 같은 하위 패턴이 나타날 때마다 중복되며, 정규식 생성 라이브러리를 추가하면 새 문법을 배우고 의존성까지 떠안게 된다.
취한 방법은 패턴을 코드로 취급하는 것이었다. 작성자는 각 논리 단위를 한 번씩 정의했다. [A-Z]*H 같은 코드 조각, 더하기 기호로 구분된 코드를 감싸는 멀티코드, 그리고 전체 매처를 각각 모든 줄에 주석이 달린 원시 상세 모드 문자열로 정의하고, {code} 자리 표시자를 통해 더 작은 패턴에서 더 큰 패턴을 조합했다. re.VERBOSE는 공백과 해시 주석을 무시하므로 각 조각은 위에서 아래로 읽힌다. 괄호와 선택지가 글리프 수프가 아니라 눈에 보이는 구조가 된다. f-string은 빌드 시점에 해석되므로 re 모듈에 도달하는 것은 여전히 하나의 평범한 패턴 문자열이다.
효과는 한 줄 정규식이 아픈 바로 그 지점에서 나타난다. 각 조각이 한 번만 정의되므로 문법이 바뀌면 그것을 포함하는 모든 패턴이 갱신된다. 조각들은 결합되기 전에 re.findall로 단독 테스트할 수 있다. 그리고 스택에 새로 들어온 것은 없다. 작성자는 오래 공존해 온 표준 라이브러리 기능 두 가지를 사용했으며, 그 조합을 알아보는 데 3년이 걸렸다고 인정한다. 글은 함정도 기록한다. 해시와 공백은 이스케이프하거나 문자 클래스를 써야 하고, 중괄호 수량자는 f-string 안에서 두 번 써야 하며, 인라인 (?x) 플래그는 호출자가 플래그를 제어할 때도 상세 모드가 작동하게 유지한다.
이룬 것
정규식을 코드처럼 구조화한다. 각 조각을 주석이 달린 상세 모드 문자열로 한 번씩 정의하고 f-string으로 최종 패턴을 조합한다
re.VERBOSE는 이미 공백과 주석을 무시하므로 각 조각은 표준 라이브러리만 사용해 문서화된 코드처럼 읽힌다.
f-string 보간은 빌드 시점에 조각을 이어 붙여, 어떤 re 호출이든 변경 없이 받아들이는 평범한 패턴 문자열을 남긴다.
각 조각이 한 번만 정의되므로 문법이 바뀌면 반복된 인라인 복사본 대신 조합된 모든 패턴이 갱신된다.
조각들은 조합 전에 단독으로 테스트할 수 있어 실패가 조각 사이의 연결 지점으로 국소화된다.
의존성이 없고 새로운 DSL도 없으므로 이 접근은 설정 없이 어떤 Python 3.6+ 코드베이스에도 적용된다.
하나의 패턴이 유지보수를 거부할 때는 라이브러리를 추가하지 말고, 이미 가진 기능을 결합하라. 가독성을 위한 상세 모드 주석, 재사용을 위한 보간을 의존성 없이 함께 쓰는 것이다.
그 후
2022년 5월에 게시되어 Hacker News에서 304점과 90개의 댓글을 얻었다. 반응은 이 글을 계시로 받아들이는 쪽과 정규식을 재사용 가능한 하위 패턴으로 나누는 것이 이미 흔한 관행이었다고 지적하는 쪽으로 갈렸다. 댓글 작성자들은 보간 없이 재사용하기 위한 더 강력한 메커니즘으로 PCRE와 Perl의 DEFINE 서브루틴 블록, 그리고 서드파티 regex 모듈을 지목했고, 작성자는 DEFINE으로 같은 파서를 작성한 보너스 섹션을 덧붙였다. 글은 그런 단서들이 기록된 채로, 그리고 핵심 주장 — 의존성 없는 조합 가능한 주석 달린 패턴 — 은 그대로인 채로 남아 있다.
근거 확인하기
출처
- The unreasonable effectiveness of f-strings and re.VERBOSE death.andgravity.com · death.andgravity.com
- The unreasonable effectiveness of f-strings and re.verbose — Hacker News discussion Hacker News · news.ycombinator.com ·