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 모듈에 추가할 함수와 플래그 값의 사양입니다.
- 형식 문자열을 위한 새로운 구문의 사양입니다.
- 객체의 형식 지정과 변환을 제어하는 새로운 특수 메서드 집합의 사양입니다.
- 사용자 정의 형식 지정 클래스용 API의 사양입니다.
- 형식 지정 오류를 처리하는 방법의 사양입니다.
문자열 인코딩에 관한 참고 사항: Python 3.0의 맥락에서 이 PEP를 논의할 때는 모든 문자열이 유니코드 문자열이며, 이 문서에서 ‘string’이라는 단어는 일반적으로 Python 3.0 문자열을 가리키는 것으로 가정합니다. 이는 Python 2.x의 유니코드 객체와 동일합니다.
Python 2.x의 맥락에서 이 문서의 ‘string’이라는 단어는 일반 문자열이나 유니코드 객체일 수 있는 객체를 가리킵니다. 이 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’ 문자는 필드를 최소 너비까지 채우는 데 사용할 문자를 정의합니다. ‘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
‘#’ 문자가 있으면 정수는 형식 지정에 ‘alternate form’을 사용합니다. 즉, 이진수, 8진수 및 16진수 출력에는 각각 ‘0b’, ‘0o’ 및 ‘0x’가 접두사로 붙습니다.
‘width’는 최소 필드 너비를 정의하는 십진 정수입니다. 지정하지 않으면 필드 너비는 내용에 따라 결정됩니다.
width 필드 앞에 0(‘0’) 문자가 있으면 0으로 채우기가 활성화됩니다. 이는 정렬 유형 ‘=’과 채우기 문자 ‘0’을 사용하는 것과 같습니다.
‘precision’은 부동 소수점 변환에서 소수점 뒤에 표시해야 하는 자릿수를 나타내는 십진수입니다. 숫자가 아닌 유형에서는 이 필드가 최대 필드 크기를 나타내며, 다시 말해 필드 내용에서 사용할 문자 수를 나타냅니다. 정수 변환에서는 정밀도가 무시됩니다.
마지막으로 ‘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”인 경우에도 안전하게 호출할 수 있습니다(파이썬에서 “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’를 의미함)를 발견하면 값을 부동 소수점 형식으로 변환한 다음 format()을 다시 호출할 수 있습니다.
어떤 클래스든 해당 형식에 대한 사용자 지정 형식 지정을 제공하도록 __format__ 메서드를 재정의할 수 있습니다.:
class AST:
def __format__(self, format_spec):
...
Python 2.x 참고: ‘format_spec’ 인자는 원래 형식 문자열의 유형에 따라 문자열 객체 또는 유니코드 객체입니다. __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’가 호출되도록 합니다. ‘get_value’가 반환된 후 내장 ‘getattr’ 함수를 호출하여 ‘name’ 속성을 조회합니다.
인덱스 또는 키워드가 존재하지 않는 항목을 가리키면 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 사용자 정의
이 절에서는 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 Dungeons)는 문자열 보간을 수행하기 위해 대괄호(예:
[name])를 사용해 왔습니다. Microsoft .Net 라이브러리는 중괄호({})와 이 제안의 구문과 매우 유사한 구문을 사용하지만, 형식 지정자의 구문은 상당히 다릅니다. [4] - 백쿼팅입니다. 이 방법은 구문상의 번잡함이 최소화된다는 장점이 있지만, 함수 호출 구문의 여러 장점(복잡한 표현식 인자, 사용자 지정 포매터 등)이 부족합니다.
- 다른 변형으로는 Ruby의
#{}, PHP의{$name}등이 있습니다.
구문의 몇 가지 구체적인 측면에는 추가 설명이 필요합니다.
1) 이스케이프를 위한 백슬래시 문자입니다. 이 PEP의 원래 버전은
대괄호를 이스케이프하기 위해 두 배로 쓰는 대신 백슬래시를 사용했습니다. 이는 \n과 같은 표준 백슬래시 시퀀스에 해당하지 않는 Python 문자열 리터럴 내의 백슬래시가 수정되지 않은 채로 남기 때문에 가능했습니다. 그러나 이로 인해 상당한 혼란이 발생했고, 대괄호 앞에 리터럴 백슬래시를 넣기 위해 \\\\{와 같이 여러 번 재귀적으로 이스케이프해야 할 가능성이 생겼습니다.
2) 구분자로 콜론 문자(‘:’)를 사용하는 것은 서식 지정자입니다. 이는 단순히 .Net에서 그렇게 사용하기 때문에 선택되었습니다.
대안 기능 제안입니다.
속성 접근 제한: PEP의 이전 버전에서는 앞에 밑줄이 붙은 속성에 접근하는 기능을 제한했습니다. 예를 들어 “{0}._private”와 같습니다. 그러나 이는 디버깅할 때 유용한 기능이므로 제거되었습니다.
일부 개발자는 ‘getattr’ 및 ‘getitem’ 접근 기능을 완전히 제거해야 한다고 제안했습니다. 그러나 이는 큰 딕셔너리를 하나의 인자로 전달한 다음(개별 키워드 인자로 평탄화하지 않고 **kwargs 구문을 사용하여) 서식 문자열에서 딕셔너리 항목을 개별적으로 참조할 수 있어야 한다고 강력하게 주장한 다른 개발자 집단의 요구와 충돌합니다.
서식 문자열에서 허용되는 표현식의 집합을 확장하자는 제안도 있었습니다. 그러나 이는 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.