PEP 3101 – 고급 문자열 포매팅
- Author:
- Talin <viridia at gmail.com>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 16-Apr-2006
- Python-Version:
- 3.0
- Post-History:
- 28-Apr-2006, 06-May-2006, 10-Jun-2007, 14-Aug-2007, 14-Sep-2008
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 기존의 ‘%’ 문자열 포매팅 연산자를 대체하기 위한 새로운 내장 문자열 포매팅 연산 체계를 제안합니다.
근거
현재 Python은 문자열 보간 방법을 두 가지 제공합니다:
이 PEP의 주된 범위는 내장 문자열 포매팅 연산(다시 말해, 내장 문자열 타입의 메서드)에 관한 제안입니다.
‘%’ 연산자의 주된 한계는 이항 연산자라서 최대 두 개의 인자만 받을 수 있다는 점입니다. 그중 한 인자는 이미 형식 문자열에 할당되어 있으므로, 나머지 모든 변수는 남은 인자 하나에 몰아넣어야 합니다. 현재는 두 번째 인자로 딕셔너리나 튜플을 사용하는 것이 관행이지만, 많은 사람이 지적했듯이 [3] 이 방식은 유연성이 부족합니다. “전부 아니면 전무” 방식(위치 인자만 사용하거나 이름 있는 인자만 사용하거나 둘 중 하나를 선택해야 한다는 의미)은 지나치게 제약이 크다고 여겨집니다.
이 제안과 string.Template 사이에 겹치는 부분이 일부 있기는 하지만, 둘은 각각 서로 다른 필요를 충족하며 한쪽이 다른 쪽을 불필요하게 만들지는 않는다고 여겨집니다. 이 제안은 ‘%’처럼 한 번만 사용되는 짧은 문자열에 효율적인 메커니즘을 위한 것입니다. 따라서 예를 들어 문자열을 템플릿으로 컴파일하는 것은 이 제안에서 고려하지 않습니다. 다만 이 제안은 효율적인 템플릿 패키지가 그 문법과 일부 하부 포매팅 코드까지 재사용할 수 있도록 형식 문자열과 API를 신중하게 정의합니다.
명세
명세는 다음 부분으로 구성됩니다:
- 내장 문자열 클래스에 추가될 새로운 포매팅 메서드의 명세입니다.
- 기본 포매팅 엔진을 추가 옵션과 함께 사용할 수 있도록 string 모듈에 추가할 함수와 플래그 값의 명세입니다. 여기서 string 모듈은 문자열 처리를 위한 모듈입니다.
- 형식 문자열을 위한 새로운 문법의 명세입니다.
- 객체의 포매팅과 변환을 제어하기 위한 새로운 특수 메서드 집합의 명세입니다.
- 사용자 정의 포매팅 클래스를 위한 API의 명세입니다.
- 포매팅 오류를 처리하는 방식의 명세입니다.
문자열 인코딩에 관한 참고 사항: Python 3.0의 맥락에서 이 PEP를 논의할 때는 모든 문자열이 유니코드 문자열이라고 가정하며, 이 문서에서 ‘문자열’이라는 단어는 일반적으로 Python 3.0 문자열을 가리키는데, 이는 Python 2.x의 유니코드 객체와 같습니다.
Python 2.x의 맥락에서 이 문서의 ‘문자열’이라는 단어는 일반 문자열일 수도 있고 유니코드 객체일 수도 있는 객체를 가리킵니다. 이 PEP에서 설명하는 모든 함수 호출 인터페이스는 문자열과 유니코드 객체 모두에 사용할 수 있으며, 모든 경우에 출력 문자열 타입을 올바르게 추론할 수 있을 만큼 충분한 정보가 있습니다(다시 말해, 별도의 API 두 개가 필요하지 않습니다). 모든 경우에 형식 문자열의 타입이 우선합니다. 즉, 변환 결과는 항상 입력 형식 문자열과 동일한 문자 표현을 담은 객체가 됩니다.
문자열 메서드
내장 문자열 클래스(그리고 2.6에서는 유니코드 클래스도)에 임의 개수의 위치 인자와 키워드 인자를 받는 새로운 메서드 ‘format’이 추가됩니다:
"The story of {0}, {1}, and {c}".format(a, b, c=d)
형식 문자열 안에서 각 위치 인자는 0부터 시작하는 숫자로 식별되므로, 위 예에서 ‘a’는 인자 0이고 ‘b’는 인자 1입니다. 각 키워드 인자는 키워드 이름으로 식별되므로, 위 예제에서 ‘c’는 세 번째 인자를 가리키는 데 사용됩니다.
단일 값을 포매팅하는 전역 내장 함수 ‘format’도 있습니다:
print(format(10.0, "7.3g"))
이 함수는 뒤의 절에서 설명합니다.
형식 문자열
형식 문자열은 문자 데이터와 마크업이 뒤섞여 구성됩니다.
문자 데이터는 형식 문자열에서 출력 문자열로 변경 없이 그대로 옮겨지는 데이터입니다. 마크업은 형식 문자열에서 출력으로 직접 옮겨지지 않으며, 대신 마크업 자리에 출력 문자열에 무엇을 넣어야 하는지를 형식 엔진에 알려 주는 ‘치환 필드’를 정의하는 데 사용됩니다.
문자열 안에서 치환 필드를 나타내는 데에는 중괄호 문자(‘curly braces’)를 사용합니다:
"My name is {0}".format('Fred')
그 결과는 다음 문자열입니다:
"My name is Fred"
중괄호는 두 번 겹쳐 써서 이스케이프할 수 있습니다:
"My name is {0} :-{{}}".format('Fred')
그러면 다음 결과가 만들어집니다:
"My name is Fred :-{}"
중괄호 안의 요소를 ‘필드’라고 합니다. 필드는 단순하거나 복합적일 수 있는 ‘필드 이름’과 선택적인 ‘형식 지정자’로 구성됩니다.
단순 필드 이름과 복합 필드 이름
단순 필드 이름은 이름이거나 숫자입니다. 숫자라면 유효한 10진 정수여야 하고, 이름이라면 유효한 Python 식별자여야 합니다. 숫자는 위치 인자를 식별하는 데 사용되고, 이름은 키워드 인자를 식별하는 데 사용됩니다.
복합 필드 이름은 여러 단순 필드 이름을 하나의 표현식 안에서 조합한 것입니다:
"My name is {0.name}".format(open('out.txt', 'w'))
이 예시는 필드 표현식 안에서 ‘getattr’ 연산자, 즉 ‘점’ 연산자를 사용하는 방법을 보여 줍니다. 점 연산자를 사용하면 입력값의 속성을 필드 값으로 지정할 수 있습니다.
일부 다른 프로그래밍 언어와 달리, 형식 문자열에는 임의의 표현식을 넣을 수 없습니다. 이는 의도된 설계입니다. 사용할 수 있는 표현식의 종류는 일부러 제한되어 있습니다. 지원하는 연산자는 ‘.’ (getattr) 연산자와 ‘[]’ (getitem) 연산자 두 가지뿐입니다. 이 연산자들을 허용하는 이유는 비정상적인 코드가 아니라면 이 연산자들이 보통 부작용을 일으키지 않기 때문입니다.
‘getitem’ 문법의 예시:
"My name is {0[name]}".format(dict(name='Fred'))
형식 문자열 안에서 ‘getitem’을 사용하는 것은 일반적인 용법보다 훨씬 제한적이라는 점에 유의해야 합니다. 위 예시에서 문자열 ‘name’은 ‘name’이라는 이름의 변수가 아니라 실제로 리터럴 문자열 ‘name’입니다. 항목 키를 파싱하는 규칙은 매우 단순합니다. 키가 숫자로 시작하면 숫자로 처리하고, 그렇지 않으면 문자열로 사용합니다.
키는 따옴표로 구분되지 않으므로, 형식 문자열 안에서 임의의 딕셔너리 키(예: 문자열 “10”이나 “:-]”)를 지정할 수는 없습니다.
구현 참고 사항: 이 제안의 구현은 단순 이름이나 점으로 연결된 이름이 유효한 Python 식별자여야 한다는 규칙을 강제하지 않아도 됩니다. 그 대신 식별자가 올바르지 않으면 기반 객체의 getattr 함수가 예외를 발생시키는 동작에 의존합니다. str.format() 함수에는 (‘.’이나 ‘]’, ‘}’ 등을 찾아서) 식별자가 언제 “끝나는지”만 알아내려고 하는 최소한의 파서가 들어갑니다.
형식 지정자
각 필드는 해당 필드의 형식을 조정하는 데 사용할 수 있는 선택적인 ‘형식 지정자’ 집합을 지정할 수도 있습니다. 형식 지정자는 필드 이름 뒤에 오며, 둘은 콜론(‘:’) 문자로 구분됩니다:
"My name is {0:8}".format('Fred')
형식 지정자의 의미와 문법은 포매팅되는 객체의 타입에 따라 달라지지만, 형식 지정자를 재정의하지 않는 모든 객체에 사용되는 표준 형식 지정자 집합이 있습니다.
형식 지정자 자체에도 치환 필드가 포함될 수 있습니다. 예를 들어, 필드 너비 자체가 매개변수인 필드는 다음과 같이 지정할 수 있습니다:
"{0:{1}}".format(a, b)
이러한 ‘내부’ 치환 필드는 치환 필드의 형식 지정자 부분에만 나타날 수 있습니다. 내부 치환 필드 자체는 형식 지정자를 가질 수 없습니다. 따라서 치환 필드를 임의의 깊이로 중첩할 수도 없습니다.
끝에 있는 두 개의 ‘}’는 보통이라면 이스케이프되지만, 이 경우에는 이스케이프되지 않는다는 점에 유의하십시오. 그 이유는 이스케이프를 위한 ‘{{’ 및 ‘}}’ 문법이 포맷 필드 바깥에서 사용될 때만 적용되기 때문입니다. 포맷 필드 안에서는 중괄호 문자가 항상 본래의 의미를 가집니다.
클래스가 표준 형식 지정자를 재정의할 수 있으므로, 형식 지정자의 문법은 열려 있습니다. 이 경우 str.format() 메서드는 첫 번째 콜론과 짝이 맞는 중괄호 사이의 모든 문자를 해당하는 하위 포맷 메서드에 그대로 전달하기만 합니다.
표준 형식 지정자
객체가 자체 형식 지정자를 정의하지 않으면 표준 형식 지정자 집합이 사용됩니다. 이는 기존 ‘%’ 연산자에서 사용하는 형식 지정자와 개념적으로 비슷하지만, 몇 가지 차이점도 있습니다.
표준 형식 지정자의 일반적인 형태는 다음과 같습니다:
[[fill]align][sign][#][0][minimumwidth][.precision][type]
대괄호([])는 선택적 요소를 나타냅니다.
다음으로, 선택적인 정렬 플래그는 다음 중 하나일 수 있습니다:
'<' - Forces the field to be left-aligned within the available
space (This is the default.)
'>' - Forces the field to be right-aligned within the
available space.
'=' - Forces the padding to be placed after the sign (if any)
but before the digits. This is used for printing fields
in the form '+000000120'. This alignment option is only
valid for numeric types.
'^' - Forces the field to be centered within the available
space.
최소 필드 너비를 정의하지 않으면 필드 너비는 항상 채울 데이터와 같은 크기가 되므로, 이 경우 정렬 옵션은 아무 의미가 없다는 점에 유의하십시오.
선택적인 ‘채움(fill)’ 문자는 필드를 최소 너비까지 채우는 데 사용할 문자를 정의합니다. 채움 문자를 지정하는 경우 그 뒤에 반드시 정렬 플래그가 와야 합니다.
‘부호(sign)’ 옵션은 숫자 타입에만 유효하며, 다음 중 하나일 수 있습니다:
'+' - indicates that a sign should be used for both
positive as well as negative numbers
'-' - indicates that a sign should be used only for negative
numbers (this is the default behavior)
' ' - indicates that a leading space should be used on
positive numbers
‘#’ 문자가 있으면 정수는 포맷할 때 ‘대체 형식’을 사용합니다. 즉, 2진수, 8진수, 16진수 출력 앞에 각각 ‘0b’, ‘0o’, ‘0x’ 접두사가 붙습니다.
‘너비(width)’는 최소 필드 너비를 정의하는 10진 정수입니다. 지정하지 않으면 필드 너비는 내용에 따라 결정됩니다.
너비 필드 앞에 0(‘0’) 문자가 있으면 0 채우기가 활성화됩니다. 이는 정렬 타입을 ‘=’로, 채움 문자를 ‘0’으로 지정한 것과 같습니다.
‘정밀도(precision)’는 부동소수점 변환에서 소수점 뒤에 몇 자리를 표시할지 나타내는 10진수입니다. 숫자가 아닌 타입에서는 이 필드가 최대 필드 크기, 즉 필드 내용에서 몇 개의 문자를 사용할지를 나타냅니다. 정수 변환에서는 정밀도가 무시됩니다.
마지막으로, ‘타입(type)’은 데이터를 어떻게 표현할지 결정합니다.
사용할 수 있는 정수 표현 타입은 다음과 같습니다:
'b' - Binary. Outputs the number in base 2.
'c' - Character. Converts the integer to the corresponding
Unicode character before printing.
'd' - Decimal Integer. Outputs the number in base 10.
'o' - Octal format. Outputs the number in base 8.
'x' - Hex format. Outputs the number in base 16, using
lower-case letters for the digits above 9.
'X' - Hex format. Outputs the number in base 16, using
upper-case letters for the digits above 9.
'n' - Number. This is the same as 'd', except that it uses the
current locale setting to insert the appropriate
number separator characters.
'' (None) - the same as 'd'
사용 가능한 부동 소수점 표현 유형은 다음과 같습니다.:
'e' - Exponent notation. Prints the number in scientific
notation using the letter 'e' to indicate the exponent.
'E' - Exponent notation. Same as 'e' except it converts the
number to uppercase.
'f' - Fixed point. Displays the number as a fixed-point
number.
'F' - Fixed point. Same as 'f' except it converts the number
to uppercase.
'g' - General format. This prints the number as a fixed-point
number, unless the number is too large, in which case
it switches to 'e' exponent notation.
'G' - General format. Same as 'g' except switches to 'E'
if the number gets to large.
'n' - Number. This is the same as 'g', except that it uses the
current locale setting to insert the appropriate
number separator characters.
'%' - Percentage. Multiplies the number by 100 and displays
in fixed ('f') format, followed by a percent sign.
'' (None) - similar to 'g', except that it prints at least one
digit after the decimal point.
객체는 표준 형식 지정자를 대체하는 자체 형식 지정자를 정의할 수 있습니다. 한 예로 ‘datetime’ 클래스가 있으며, 이 클래스의 형식 지정자는 strftime() 함수의 인자와 비슷한 모습일 수 있습니다:
"Today is: {0:%a %b %d %H:%M:%S %Y}".format(datetime.now())
모든 내장 타입에서 빈 포맷 명세는 str(value)와 동등한 결과를 만듭니다. 자체 형식 지정자를 정의하는 객체도 이 관례를 따를 것을 권장합니다.
명시적 변환 플래그
명시적 변환 플래그는 포맷 필드 값을 포맷하기 전에 변환하는 데 사용됩니다. 이를 사용하면 타입별 포매팅 동작을 재정의하여, 값을 더 일반적인 제네릭 타입인 것처럼 포맷할 수 있습니다. 현재 두 가지 명시적 변환 플래그가 인식됩니다.:
!r - convert the value to a string using repr().
!s - convert the value to a string using str().
이러한 플래그는 형식 지정자 앞에 놓입니다:
"{0!r:20}".format("Hello")
앞의 예에서 문자열 “Hello”는 따옴표와 함께 최소 20자 너비의 필드에 출력됩니다.
사용자 정의 Formatter 클래스는 추가 변환 플래그를 정의할 수 있습니다. 잘못된 변환 플래그가 지정되면 내장 포매터는 ValueError를 발생시킵니다.
타입별 포맷 제어
각 Python 타입은 __format__ 메서드를 정의하여 자기 인스턴스의 서식화를 제어할 수 있습니다. __format__ 메서드는 형식 지정자를 해석하고, 값을 서식화하고, 그 결과 문자열을 반환하는 일을 담당합니다.
새로운 전역 내장 함수 ‘format’은 len()과 str()이 각자의 특수 메서드를 그저 호출하는 것과 비슷하게, 이 특수 메서드를 그저 호출합니다.:
def format(value, format_spec):
return value.__format__(format_spec)
이 함수는 “None” 값으로 호출해도 안전합니다(Python에서 “None” 값은 객체이며 메서드를 가질 수 있기 때문입니다.)
‘str’, ‘int’, ‘float’, ‘object’(객체)를 포함한 여러 내장 타입이 __format__ 메서드를 정의합니다. 즉, 이 타입들 중 하나를 상속하면 여러분의 클래스는 자신을 포맷하는 방법을 알게 됩니다.
object.__format__ 메서드가 가장 단순합니다. 이 메서드는 객체를 문자열로 변환한 다음 format을 다시 호출할 뿐입니다.:
class object:
def __format__(self, format_spec):
return format(str(self), format_spec)
‘int’와 ‘float’의 __format__ 메서드는 형식 지정자에 따라 숫자 서식화를 수행합니다. 경우에 따라 이러한 포맷 작업은 다른 타입에 위임될 수 있습니다. 예를 들어 ‘int’ 포매터가 ‘f’(‘float’를 의미) 포맷 유형을 만나면, 값을 float로 캐스팅한 후 format()을 다시 호출하기만 하면 됩니다.
어떤 클래스든 __format__ 메서드를 재정의하여 해당 타입에 대한 사용자 정의 포맷을 제공할 수 있습니다.:
class AST:
def __format__(self, format_spec):
...
Python 2.x 사용자를 위한 참고: ‘format_spec’ 인자는 원래 형식 문자열의 타입에 따라 문자열 객체 또는 유니코드(unicode) 객체가 됩니다. __format__ 메서드는 지정자 매개변수의 타입을 검사하여 문자열 객체와 유니코드 객체 중 어느 것을 반환할지 결정해야 합니다. 적절한 타입의 객체를 반환하는 것은 __format__ 메서드의 책임입니다.
앞에서 언급한 ‘명시적 변환’ 플래그는 __format__ 메서드에 전달되지 않는다는 점에 유의하십시오. 그 대신, 플래그가 지정한 변환은 __format__을 호출하기 전에 수행되어야 합니다.
사용자 정의 포맷
필드 서식을 타입별로 사용자 정의하는 것만으로는 충분하지 않은 경우가 있습니다. 예를 들어 스프레드시트 애플리케이션은 값이 너무 커서 사용 가능한 공간에 들어가지 않을 때 해시 기호 ‘#’을 표시합니다.
더 강력하고 유연한 서식 지정을 원한다면 ‘string’(문자열) 모듈에 있는 ‘Formatter’ 클래스를 통해 하부 서식 엔진에 접근할 수 있습니다. 이 클래스는 일반적인 str.format 메서드로는 접근할 수 없는 추가 옵션을 받습니다.
애플리케이션은 Formatter 클래스의 서브클래스를 만들어 자체적으로 맞춤화한 서식 지정 동작을 만들 수 있습니다.
이 PEP는 Formatter 클래스가 정의하는 모든 메서드와 속성을 정확하게 명세하려 하지 않습니다. 대신 이들은 초기 구현에서 정의되고 문서화될 것입니다. 하지만 이 PEP는 Formatter 클래스에 대한 일반적인 요구 사항을 명세하며, 이는 아래에 나열되어 있습니다.
string.format()은 서식 지정에 Formatter 클래스를 직접 사용하지는 않지만, 둘 다 동일한 내부 구현을 사용합니다. string.format()이 Formatter 클래스를 직접 사용하지 않는 이유는 “string”(문자열)이 내장 타입이어서 그 메서드를 모두 C로 구현해야 하는 반면, Formatter는 Python 클래스이기 때문입니다. Formatter는 string.format()이 사용하는 것과 같은 C 함수들을 감싸는 확장 가능한 래퍼를 제공합니다.
Formatter 메서드
Formatter 클래스는 초기화 인자를 받지 않습니다:
fmt = Formatter()
Formatter 클래스의 공개 API 메서드는 다음과 같습니다:
-- format(format_string, *args, **kwargs)
-- vformat(format_string, args, kwargs)
‘format’은 주요 API 메서드입니다. 이 메서드는 서식 템플릿과 임의의 위치 인자 및 키워드 인자 집합을 받습니다. ‘format’은 ‘vformat’을 호출하는 래퍼일 뿐입니다.
‘vformat’은 실제 서식 지정 작업을 수행하는 함수입니다. 이 함수는 *args와 **kwds 문법을 사용해 딕셔너리를 개별 인자로 풀었다가 다시 묶는 대신, 미리 정의된 인자 딕셔너리를 전달하고 싶은 경우를 위해 별도의 함수로 노출됩니다. ‘vformat’은 형식 템플릿 문자열을 문자 데이터와 치환 필드로 나누는 작업을 수행합니다. 이 함수는 필요에 따라 ‘get_positional’ 및 ‘get_index’ 메서드를 호출합니다(아래에서 설명합니다).
Formatter는 다음과 같은 재정의 가능한 메서드를 정의합니다:
-- get_value(key, args, kwargs)
-- check_unused_args(used_args, args, kwargs)
-- format_field(value, format_spec)
‘get_value’는 주어진 필드 값을 가져오는 데 사용됩니다. ‘key’ 인자는 정수 또는 문자열입니다. 정수이면 ‘args’에 있는 위치 인자의 인덱스를 나타내고, 문자열이면 ‘kwargs’에 있는 이름 있는 인자를 나타냅니다.
‘args’ 매개변수는 ‘vformat’에 전달된 위치 인자의 리스트로 설정되고, ‘kwargs’ 매개변수는 위치 인자의 딕셔너리로 설정됩니다.
복합 필드 이름의 경우, 이 함수들은 필드 이름의 첫 번째 구성 요소에 대해서만 호출되며, 이후 구성 요소는 일반적인 속성 접근 및 인덱싱 연산을 통해 처리됩니다.
예를 들어 필드 표현식 ‘0.name’은 ‘key’ 인자를 0으로 하여 ‘get_value’를 호출하게 합니다. ‘name’ 속성은 ‘get_value’가 반환된 후 내장 함수 ‘getattr’을 호출하여 조회됩니다.
인덱스나 키워드가 존재하지 않는 항목을 가리키면 IndexError/KeyError가 발생해야 합니다.
‘check_unused_args’는 필요한 경우 사용되지 않은 인자에 대한 검사를 구현하는 데 사용됩니다. 이 함수의 인자는 형식 문자열에서 실제로 참조된 모든 인자 키의 집합(위치 인자는 정수, 이름 있는 인자는 문자열)과 vformat에 전달된 args와 kwargs에 대한 참조입니다. 사용되지 않은 인자의 집합은 이 매개변수들로부터 계산할 수 있습니다. ‘check_unused_args’는 검사에 실패하면 예외를 발생시키는 것으로 간주됩니다.
‘format_field’는 단순히 전역 내장 함수 ‘format’을 호출합니다. 이 메서드는 서브클래스가 재정의할 수 있도록 제공됩니다.
이 함수들이 서로 어떻게 연관되는지 더 잘 이해할 수 있도록, vformat의 일반적인 동작을 설명하는 의사 코드를 아래에 제시합니다:
def vformat(format_string, args, kwargs):
# Output buffer and set of used args
buffer = StringIO.StringIO()
used_args = set()
# Tokens are either format fields or literal strings
for token in self.parse(format_string):
if is_format_field(token):
# Split the token into field value and format spec
field_spec, _, format_spec = token.partition(":")
# Check for explicit type conversion
explicit, _, field_spec = field_spec.rpartition("!")
# 'first_part' is the part before the first '.' or '['
# Assume that 'get_first_part' returns either an int or
# a string, depending on the syntax.
first_part = get_first_part(field_spec)
value = self.get_value(first_part, args, kwargs)
# Record the fact that we used this arg
used_args.add(first_part)
# Handle [subfield] or .subfield. Assume that 'components'
# returns an iterator of the various subfields, not including
# the first part.
for comp in components(field_spec):
value = resolve_subfield(value, comp)
# Handle explicit type conversion
if explicit == 'r':
value = repr(value)
elif explicit == 's':
value = str(value)
# Call the global 'format' function and write out the converted
# value.
buffer.write(self.format_field(value, format_spec))
else:
buffer.write(token)
self.check_unused_args(used_args, args, kwargs)
return buffer.getvalue()
Formatter 클래스의 실제 알고리즘(C로 구현될 예정입니다)은 여기에 제시된 것과 다를 수 있다는 점에 유의하십시오. (실제 구현은 아예 ‘클래스’가 아닐 가능성이 높습니다. 그 대신 vformat이 다른 재정의 가능한 메서드들을 인자로 받는 C 함수를 호출하기만 할 수도 있습니다.) 이 코드 예제의 주된 목적은 재정의 가능한 메서드들이 호출되는 순서를 보여 주는 것입니다.
포매터 사용자 정의
이 절에서는 Formatter 객체를 사용자 정의하는 몇 가지 대표적인 방법을 설명합니다.
다른 형식 문자열 문법을 지원하려면 ‘vformat’ 메서드를 재정의하여 형식 문자열을 파싱하는 방식을 바꿀 수 있습니다.
흔히 요구되는 것 중 하나는 ‘기본’ 네임스페이스를 지원하는 것입니다. 그러면 format() 메서드에 키워드 인자를 전달할 필요 없이 이미 존재하는 네임스페이스의 값을 사용할 수 있습니다. 이는 다음과 같이 get_value()를 재정의하면 쉽게 할 수 있습니다:
class NamespaceFormatter(Formatter):
def __init__(self, namespace={}):
Formatter.__init__(self)
self.namespace = namespace
def get_value(self, key, args, kwds):
if isinstance(key, str):
try:
# Check explicitly passed arguments first
return kwds[key]
except KeyError:
return self.namespace[key]
else:
Formatter.get_value(key, args, kwds)
예를 들어 이를 이용하면 전역 변수에 접근할 수 있는 포매팅 함수를 쉽게 만들 수 있습니다:
fmt = NamespaceFormatter(globals())
greeting = "hello"
print(fmt.format("{greeting}, world!"))
locals() 딕셔너리에도 비슷한 기법을 적용하여 지역 변수 딕셔너리에 접근할 수 있습니다.
호출 스택을 조사하여 지역 변수와 전역 변수 모두에 자동으로 접근할 수 있는 ‘똑똑한’ 네임스페이스 포매터를 만드는 것도 가능합니다. 여러 Python 버전과의 호환성이 필요하므로 이러한 기능은 표준 라이브러리에 포함되지 않습니다. 하지만 누군가가 이를 위한 레시피를 만들어 공개할 것으로 예상합니다.
또 다른 사용자 정의 방식은 ‘format_field’ 메서드를 재정의하여 내장 타입이 포매팅되는 방식을 바꾸는 것입니다. (내장 타입이 아닌 경우에는 해당 타입에 __format__ 특수 메서드를 정의하기만 하면 됩니다.) 예를 들어 숫자 포매팅을 재정의하여 필요할 때 과학적 표기법으로 출력하게 할 수 있습니다.
오류 처리
포매팅 중에 발생할 수 있는 예외는 두 부류입니다. 포매터 코드 자체에서 발생하는 예외와 사용자 코드(예: 필드 객체의 ‘getattr’ 함수)에서 발생하는 예외입니다.
일반적으로 포매터 코드 자체에서 발생하는 예외는 “ValueError” 종류입니다. 즉, 형식 문자열의 실제 “값(value)”에 오류가 있다는 뜻입니다. (항상 그런 것은 아닙니다. 예를 들어 string.format() 함수의 첫 번째 매개변수로 문자열이 아닌 값이 전달될 수 있으며, 이 경우 TypeError가 발생합니다.)
이렇게 내부에서 발생하는 ValueError 예외에 딸린 텍스트는 형식 문자열 안에서 예외가 발생한 위치와 예외의 성격을 알려 줍니다.
사용자 코드에서 발생한 예외의 경우, 문자열 안에서 예외가 발생한 위치를 파악하는 데 도움이 되도록 트레이스 레코드와 더미 프레임이 트레이스백 스택에 추가됩니다. 삽입된 트레이스백은 오류가 다음 위치에서 발생했음을 나타냅니다:
File "<format_string>;", line XX, in column_YY
여기서 XX와 YY는 각각 문자열 안의 줄 위치 정보와 문자 위치 정보를 나타냅니다.
대안 문법
당연히 가장 논란이 많은 쟁점 중 하나는 형식 문자열의 문법이며, 특히 필드를 나타내는 데 쓰이는 마크업 관례입니다.
여러 제안을 빠짐없이 나열하기보다는 이미 가장 널리 쓰이는 것들을 다루겠습니다.
- 셸 변수 문법:
$name과$(name)(일부 변형에서는${name}). 이는 아마 현존하는 가장 오래된 관례일 것이며, Perl을 비롯한 많은 곳에서 사용됩니다. 중괄호 없이 사용할 때는 유효하지 않은 문자가 나올 때까지 어휘적으로 스캔하여 변수의 길이를 결정합니다.이 방식은 일반적으로 보간이 암묵적으로 일어나는 경우에 사용됩니다. 즉, 어떤 문자열이든 보간 변수를 포함할 수 있고 특별한 치환 함수를 호출할 필요가 없는 환경입니다. 이러한 경우에는 보간 동작이 실수로 일어나지 않도록 막는 것이 중요하므로, 보간이 일어나야 할 때를 알리는 데 ‘$’(평소에는 비교적 드물게 쓰이는 문자)를 사용합니다.
하지만 저자는 포매팅을 명시적으로 호출하는 경우에는 실수로 인한 보간을 막는 데 그만큼 신경 쓸 필요가 없으며, 이 경우 더 가볍고 덜 번거로운 문법을 사용할 수 있다고 생각합니다.
- printf와 그 사촌들(‘%’). 필드 인덱스를 추가하여 필드를 순서와 상관없이 보간할 수 있게 한 변형들을 포함합니다.
- 그 밖의 괄호만 사용하는 변형들. MUSH 같은 여러 MUD(Multi-User Dungeon)는 문자열 보간에 대괄호(예:
[name])를 사용해 왔습니다. Microsoft .Net 라이브러리는 중괄호({})를 사용하며, 이 제안의 문법과 매우 비슷한 문법을 사용합니다. 다만 형식 지정자의 문법은 상당히 다릅니다. [4] - 백쿼트 사용. 이 방법은 문법적으로 어수선함이 최소화된다는 장점이 있지만, 함수 호출 문법이 주는 여러 장점(복잡한 표현식 인자, 사용자 정의 포매터 등)이 없습니다.
- 그 밖의 변형으로는 Ruby의
#{}, PHP의{$name}등이 있습니다.
문법의 몇몇 특정 측면에 대해서는 추가 설명이 필요합니다:
1) 이스케이프를 위한 백슬래시 문자. 이 PEP의 최초 버전에서는 괄호를 이스케이프할 때 괄호를 두 번 쓰는 대신 백슬래시를 사용했습니다. 이 방식이 동작했던 이유는 Python 문자열 리터럴에서 \n 같은 표준 백슬래시 시퀀스에 해당하지 않는 백슬래시는 수정되지 않고 그대로 남기 때문입니다. 그러나 이 방식은 어느 정도 혼란을 일으켰고, 여러 겹의 재귀적 이스케이프가 필요한 상황, 즉 중괄호 앞에 리터럴 백슬래시를 두기 위해 \\\\{ 같은 표기를 써야 하는 상황을 초래할 수 있었습니다.
2) 형식 지정자의 구분자로 콜론 문자(‘:’)를 사용하는 것. 이는 단순히 .Net이 그렇게 사용하기 때문에 선택되었습니다.
대안 기능 제안
속성 접근 제한: 이 PEP의 이전 버전은 “{0}._private”처럼 밑줄로 시작하는 속성에 접근하는 기능을 제한했습니다. 그러나 이는 디버깅할 때 유용한 기능이므로 해당 제한은 제거되었습니다.
일부 개발자는 ‘getattr’ 및 ‘getitem’ 접근 기능을 완전히 없애야 한다고 제안했습니다. 그러나 이는 큰 dict를 (**kwargs 문법을 사용해 개별 키워드 인자로 펼치지 않고) 하나의 인자로 전달한 다음 형식 문자열에서 dict 항목을 개별적으로 참조할 수 있는 기능을 강력히 요구한 또 다른 개발자 집단의 필요와 상충합니다.
형식 문자열에서 허용되는 표현식의 범위를 넓히자는 제안도 있었습니다. 그러나 대부분의 경우 포매팅 함수에 전달하기 전에 매개변수에 같은 표현식을 실행하면 같은 효과를 얻을 수 있으므로, 이는 TOOWTDI의 정신에 어긋나는 것으로 여겨졌습니다. 데이터가 풍부한 환경에서 임의의 서식화를 수행하기 위해 형식 문자열을 사용하는 경우에는 Genshi [5] 또는 Cheetah [6] 같은, 이 목적에 특화된 템플릿 엔진을 사용할 것을 권장합니다.
그 밖의 많은 기능은 기본 구현에 기능을 내장하는 대신 Formatter를 서브클래싱하여 쉽게 구현할 수 있기 때문에 검토 후 기각되었습니다. 여기에는 대체 문법, 형식 문자열 내 주석 등 많은 것이 포함됩니다.
보안 고려 사항
역사적으로 문자열 서식화는 웹 기반 애플리케이션에서 보안 취약점의 흔한 원인이었으며, 특히 문자열 서식화 시스템이 형식 문자열에 임의의 표현식을 삽입하도록 허용하는 경우에 그러했습니다.
잠재적인 보안 취약점을 만들지 않는 방식으로 문자열 서식화를 사용하는 가장 좋은 방법은 신뢰할 수 없는 출처에서 온 형식 문자열을 절대 사용하지 않는 것입니다.
그렇게 할 수 없다면, 차선책은 문자열 포매팅에 부작용이 없도록 보장하는 것입니다. Python의 개방적인 특성 때문에 사소하지 않은 연산이 이러한 속성을 갖는다고 보장하는 것은 불가능합니다. 이 PEP가 하는 일은 형식 문자열 안의 표현식 종류를, 눈에 보이는 부작용이 드물고 Python 개발자 문화에서도 강하게 지양되는 것들로 제한하는 것입니다. 예를 들어 속성 접근이 허용되는 이유는, 단지 속성에 접근하는 것만으로 눈에 보이는 부작용이 생기는 코드를 작성하는 것은 병적인 것으로 여겨지기 때문입니다(코드에 더 빠른 조회를 위해 캐시 항목을 생성하는 것과 같은 보이지 않는 부작용이 있는지는 상관없습니다.)
샘플 구현
이 PEP의 이전 버전에 대한 구현은 Patrick Maupin과 Eric V. Smith가 작성하였으며, 다음 위치의 pep3101 샌드박스에서 찾을 수 있습니다:
하위 호환성
기존 메커니즘을 그대로 두면 하위 호환성을 유지할 수 있습니다. 새 시스템은 기존 문자열 포매팅 기법의 어떤 메서드 이름과도 충돌하지 않으므로, 이전 시스템을 폐기할 때가 올 때까지 두 시스템이 공존할 수 있습니다.
참고 문헌
Copyright
This document has been placed in the public domain.