PEP 520 – 클래스 속성 정의 순서 보존
- Author:
- Eric Snow <ericsnowcurrently at gmail.com>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 07-Jun-2016
- Python-Version:
- 3.6
- Post-History:
- 07-Jun-2016, 11-Jun-2016, 20-Jun-2016, 24-Jun-2016
- Resolution:
- Python-Dev message
Table of Contents
- 초록
- 동기
- 배경
- 사양
- 왜 튜플입니까?
- 읽기 전용 속성을 사용하지 않는 이유는 무엇입니까?
- “__attribute_order__”를 사용하지 않는 이유는 무엇입니까?
- “dunder” 이름을 무시하지 않는 이유는 무엇입니까?
- 빈 튜플 대신 None인 이유는 무엇입니까?
- 속성을 설정하지 않는 대신 None을 사용하는 이유는 무엇입니까?
- 수동으로 설정한 값을 제한하는 이유는 무엇입니까?
- 타입이 아닌 객체에서 __definition_order__을 숨기지 않는 이유는 무엇입니까?
- __slots__은 어떻게 됩니까?
- __definition_order__이 꼭 필요한 이유는 무엇입니까?
- C-API 타입 지원
- 호환성
- 변경 사항
- 기타 Python 구현
- 구현
- 대안
- 참고 문헌
- Copyright
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
Note
컴팩트 딕셔너리가 3.6에 도입되었기 때문에 __definition_order__는 제거되었습니다. 이제 cls.__dict__가 대부분 동일한 역할을 수행합니다.
초록
클래스 정의 구문은 본질적으로 순서가 있습니다. 따라서 그곳에 정의된 클래스 속성에도 순서가 있습니다. 이러한 순서는 가독성 향상에 도움이 될 뿐만 아니라 때로는 중요합니다. 클래스 정의 외부에서 해당 순서를 자동으로 사용할 수 있다면, 추가적인 보일러플레이트(메타클래스를 사용하거나 속성 순서를 수동으로 열거하는 작업 등) 없이 속성 순서를 사용할 수 있습니다. 이 정보가 이미 존재한다는 점을 고려하면 속성의 정의 순서에 접근할 수 있으리라는 기대는 합리적입니다. 그러나 현재 Python은 클래스 정의의 속성 순서를 보존하지 않습니다.
이 PEP는 클래스 정의 본문에 속성이 도입되는 순서를 보존함으로써 이를 변경합니다. 이제 해당 순서는 클래스의 __definition_order__ 속성에 보존됩니다. 이를 통해 원래의 정의 순서를 검사할 수 있으며, 예를 들어 클래스 데코레이터가 이를 활용할 수 있습니다.
또한 이 PEP는 기본 클래스 정의 네임스페이스가 기본적으로 순서가 유지되도록(예: OrderedDict) 요구합니다. 오래 유지되는 클래스 네임스페이스인 __dict__는 dict로 남습니다.
동기
클래스 정의의 속성 순서는 이름 순서에 의존하는 도구에 유용할 수 있습니다. 그러나 정의 순서를 자동으로 사용할 수 없다면 이러한 도구는 사용자에게 추가 요구 사항을 부과해야 합니다. 예를 들어 이러한 도구를 사용하려면 클래스에서 특정 메타클래스를 사용해야 할 수 있습니다. 이러한 요구 사항만으로도 도구 사용을 단념하게 되는 경우가 많습니다.
이 PEP를 활용할 수 있는 도구에는 다음이 포함됩니다.
- 문서 생성기
- 테스트 프레임워크
- CLI 프레임워크
- 웹 프레임워크
- 구성 생성기
- 데이터 직렬화기
- 열거형 팩토리(제가 처음 이 기능을 동기 부여받은 계기)
배경
class 문을 사용하여 클래스를 정의하면 클래스 본문은 네임스페이스 내에서 실행됩니다. 현재 해당 네임스페이스의 기본값은 dict입니다. 메타클래스가 __prepare__()를 정의하면 이를 호출한 결과가 클래스 정의 네임스페이스로 사용됩니다.
실행이 완료되면 정의 네임스페이스가 새 dict로 복사됩니다. 그런 다음 원래의 정의 네임스페이스가 폐기됩니다. 새 복사본은 클래스의 네임스페이스로 저장되며, 읽기 전용 프록시를 통해 __dict__로 노출됩니다.
클래스 속성 정의 순서는 정의 네임스페이스에 이름이 삽입된 순서로 표현됩니다. 따라서 정의 네임스페이스를 collections.OrderedDict와 같은 순서가 있는 매핑으로 전환하면 정의 순서에 접근할 수 있습니다. 위에서 설명한 것처럼 메타클래스와 __prepare__를 사용하면 이를 실현할 수 있습니다. 실제로 이것이 __prepare__를 사용하는 가장 일반적인 사용 사례입니다.
이 시점에서 나중에 정의 순서에 접근하기 위해 필요한 유일한 작업은 정의 네임스페이스가 폐기되기 전에 이를 클래스에 저장하는 것입니다. 다시 말해, 이는 메타클래스를 사용하여 수행할 수 있습니다. 그러나 이는 그러한 메타클래스를 사용하는 클래스에서만 정의 순서가 보존된다는 의미입니다. 여기에는 두 가지 실질적인 문제가 있습니다:
첫째, 메타클래스를 사용해야 합니다. 메타클래스는 코드에 추가적인 복잡성 수준을 도입하며, 일부 경우(예: 충돌)에는 문제가 됩니다. 따라서 기회가 있을 때 메타클래스의 필요성을 줄이는 것은 가치가 있습니다. PEP 422 및 PEP 487에서 이를 자세히 논의합니다. 기본 클래스 정의 네임스페이스에 순서가 있는 매핑(예를 들어 최소한 CPython에서는 OrderedDict)을 사용하면 사실상 __prepare__()의 필요성을 없앨 수 있는 기회가 있습니다.
둘째, OrderedDict 기반 메타클래스를 사용하도록 선택한 클래스만 정의 순서에 접근할 수 있습니다. 이는 정의 순서에 대한 보편적인 접근이 중요한 경우에 문제가 됩니다.
사양
제1부:
- 모든 클래스에는
__definition_order__속성이 있습니다. __definition_order__는 식별자로 이루어진tuple(또는None)입니다.__definition_order__는 항상 설정됩니다:- 클래스 본문을 실행하는 동안 클래스 정의 네임스페이스에 이름이 삽입된 순서가 튜플에 저장됩니다.
__definition_order__가 클래스 본문에서 정의된 경우, 이는 식별자의tuple이거나None이어야 하며, 다른 값은TypeError를 발생시킵니다.- 클래스 정의가 없는 클래스(예: 내장 클래스)는
__definition_order__가None으로 설정됩니다. __prepare__()가OrderedDict(또는 그 서브클래스)가 아닌 것을 반환한 클래스는__definition_order__가None으로 설정됩니다(#2가 적용되는 경우는 제외).
변경하지 않는 사항:
dir()는__definition_order__에 의존하지 않습니다.- 디스크립터와 사용자 정의
__getattribute__메서드는__definition_order__에 관해 제약을 받지 않습니다.
제2부:
- 기본 클래스 정의 네임스페이스가 이제 순서가 있는 매핑입니다(예:
OrderdDict). cls.__dict__는 변경되지 않으며,dict를 둘러싼 읽기 전용 프록시로 남습니다.
순서가 있는 dict를 보유한 Python 구현에서는 아무것도 변경할 필요가 없다는 점에 유의하십시오.
다음 코드는 1부와 2부 모두에 대해 대략적으로 동등한 의미를 보여 줍니다.:
class Meta(type):
@classmethod
def __prepare__(cls, *args, **kwargs):
return OrderedDict()
class Spam(metaclass=Meta):
ham = None
eggs = 5
__definition_order__ = tuple(locals())
왜 튜플입니까?
튜플을 사용하는 것은 클래스의 속성이 정의된 순서를 노출한다는 사실을 반영합니다. __definition_order__가 설정될 때에는 이미 정의가 완료된 상태이므로 값의 내용과 순서는 변경되지 않습니다. 따라서 이러한 불변성 상태를 전달하는 타입을 사용합니다.
읽기 전용 속성을 사용하지 않는 이유는 무엇입니까?
__definition_order__를 읽기 전용 속성으로 만드는 데에는 (cls.__dict__가 그러한 것처럼) 타당한 근거가 몇 가지 있습니다. 무엇보다도 읽기 전용 속성은 해당 속성이 “완료된” 상태임을 전달하며, 이는 __definition_order__에 정확히 부합합니다. 이는 특정한 한 번의 이벤트(클래스 정의 본문의 실행) 상태를 나타내므로, 값을 대체할 수 있도록 허용하면 해당 속성이 원래 클래스 본문에 대응한다는 확신이 줄어듭니다. 또한 불변성을 기본값으로 삼는 접근 방식은 데이터를 더 쉽게 이해하는 데 도움이 되는 경우가 많습니다.
그러나 이 경우에는 Python에서 오랫동안 이어져 온 선례에 반대할 강력한 이유가 여전히 없습니다. Guido에 따르면:
I don't see why it needs to be a read-only attribute. There are
very few of those -- in general we let users play around with
things unless we have a hard reason to restrict assignment (e.g.
the interpreter's internal state could be compromised). I don't
see such a hard reason here.
또한 쓰기 가능한 __definition_order__를 사용하면 동적으로 생성된 클래스(예: Cython으로 생성된 클래스)도 __definition_order__를 올바르게 설정할 수 있다는 점에 유의하십시오. 읽기 전용 속성의 의미를 포기할 필요 없이 type()이나 C-API 같은 특정 클래스 생성 도구를 통해 이를 처리할 수 있음은 분명합니다. 그러나 쓰기 가능한 속성을 사용한다면 이는 더 이상 문제가 되지 않습니다.
“__attribute_order__”를 사용하지 않는 이유는 무엇입니까?
__definition_order__는 클래스 정의 본문을 중심으로 합니다. 클래스 네임스페이스(__dict__)를 정의 후 처리하는 사용 사례는 별개의 문제입니다. __definition_order__는 클래스 정의를 넘어서는 기능에 초점을 맞춘 이름으로는 상당히 오해의 소지가 있습니다.
“dunder” 이름을 무시하지 않는 이유는 무엇입니까?
“__”로 시작하고 끝나는 이름은 인터프리터가 사용하도록 예약되어 있습니다. 실제로 이러한 이름은 __definition_order__의 사용자와는 관련이 없어야 합니다. 대신 거의 모든 사용자에게 이러한 이름은 단지 불필요한 요소일 뿐이며, 대다수 사용자가 동일한 추가 작업(dunder 이름 필터링)을 수행하게 만듭니다. dunder 이름이 중요한 경우에는 클래스 정의에서 __definition_order__를 수동으로 설정할 수 있으므로, 일반적인 경우를 더 간단하게 만들 수 있습니다.
그러나 __definition_order__에서 dunder 이름을 제외하면 정의 순서에서 해당 이름이 차지한 위치를 복구할 수 없게 됩니다. 기본적으로 dunder 이름을 제거하면 dunder 이름을 관례에 맞지 않게 사용하는 클래스에 의도치 않은 문제가 발생할 수 있습니다. 이 경우에는 안전을 우선하여 클래스 정의의 모든 이름을 보존하는 편이 낫습니다. dunder 이름은 쉽게 필터링할 수 있으므로 이는 큰 문제가 아닙니다.:
(name for name in cls.__definition_order__
if not (name.startswith('__') and name.endswith('__')))
실제로 일부 애플리케이션 컨텍스트에서는 “_”로 시작하는 모든 이름 무시, 모든 메서드 제외, 디스크립터만 포함 등 유사한 필터링을 적용할 다른 기준이 있을 수 있습니다. 결국 dunder 이름은 예외적으로 취급할 만큼 특별한 경우가 아닙니다.
__name__과 __qualname__같은 dunder 이름 몇 개는 컴파일러에 의해 기본적으로 삽입된다는 점에 유의하십시오. 따라서 이러한 이름은 엄밀히 말해 클래스 정의 본문의 일부가 아니더라도 포함됩니다.
빈 튜플 대신 None인 이유는 무엇입니까?
__definition_order__를 추가하는 핵심 목표는 이 PEP 이전에 손실된 클래스 정의 정보를 보존하는 것입니다. 한 가지 결과는 __definition_order__가 원래의 클래스 정의를 의미한다는 것입니다. None을 사용하면 정의 순서가 없는 클래스를 명확히 구분할 수 있습니다. 빈 튜플은 정의 문에서 생성되었지만 그곳에서 어떤 속성도 정의하지 않은 클래스임을 명확히 나타냅니다.
속성을 설정하지 않는 대신 None을 사용하는 이유는 무엇입니까?
속성이 없으면 __definition_order__의 소비자가 None을 처리할 때보다 더 복잡한 처리가 필요합니다.
수동으로 설정한 값을 제한하는 이유는 무엇입니까?
__definition_order__를 클래스 본문에서 수동으로 설정하면 해당 값이 사용됩니다. __definition_order__의 소비자가 해당 값에 대해 일관된 기대를 가질 수 있도록 식별자의 튜플(또는 None)이어야 합니다. 이는 이 기능의 유용성을 극대화하는 데 도움이 됩니다.
수동으로 설정한 __definition_order__에 임의의 이터러블을 허용하고 이를 튜플로 변환할 수도 있습니다. 그러나 모든 이터러블에서 정의 순서를 유추할 수 있는 것은 아닙니다(예: set). 따라서 튜플을 요구하는 방식을 선택합니다.
타입이 아닌 객체에서 __definition_order__을 숨기지 않는 이유는 무엇입니까?
Python은 클래스의 인스턴스에서 조회할 때 클래스별 속성을 숨기기 위해 많은 노력을 기울이지 않습니다. __definition_order__를 객체에서 조회할 때 숨겨지는 클래스 전용 속성으로 간주하는 것이 타당할 수 있지만, 그와 관련한 선례를 세우는 것은 이 PEP의 목표를 넘어섭니다.
__slots__은 어떻게 됩니까?
__slots__는 클래스 정의 본문의 다른 이름과 마찬가지로 __definition_order__에 추가됩니다. 실제 슬롯 이름은 정의 네임스페이스에서 이름으로 설정되지 않으므로 __definition_order__에 추가되지 않습니다.
__definition_order__이 꼭 필요한 이유는 무엇입니까?
정의 순서는 __dict__에 보존되지 않으므로 클래스 정의 실행이 완료되면 손실됩니다. 클래스가 본문에서 마지막 작업으로 해당 속성을 명시적으로 설정할 수도 있습니다. 그러나 그렇게 하면 독립적인 데코레이터는 그렇게 한 클래스만 사용할 수 있었을 것입니다. 대신 __definition_order__는 클래스 본문에서 얻은 이 한 비트의 정보를 보존하므로 어디서나 사용할 수 있습니다.
C-API 타입 지원
C로 정의된 대부분의 Python 타입(예: 내장 타입, 확장 모듈)에는 정의 순서와 대략 동등한 개념이 있다고 볼 수 있습니다. 따라서 그러한 타입에 __definition_order__를 자동으로 설정할 수도 있을 것입니다. 이 PEP는 그러한 지원을 도입하지 않습니다. 그러나 이를 금지하지도 않습니다. 그러나 __definition_order__는 일반적인 속성 할당을 통해 언제든 설정할 수 있으므로 C-API에서 특별히 처리할 필요가 없습니다.
구체적인 경우:
- 내장 타입
- PyType_Ready
- PyType_FromSpec
호환성
이 PEP는 하위 호환성을 깨뜨리지 않지만, 누군가 클래스 정의 네임스페이스로 dict에 엄격히 의존하는 경우는 예외입니다. issubclass(OrderedDict, dict)가 참이므로 이는 문제가 되지 않습니다.
변경 사항
클래스 구문 외에도 다음 항목에서 새로운 동작을 노출합니다.
- builtins.__build_class__
- types.prepare_class
- types.new_class
또한 builtins.type()의 3개 인자를 받는 형식에서는 전달되는 네임스페이스에 __definition_order__를 포함할 수 있습니다. 이는 클래스 본문에서 __definition_order__가 명시적으로 정의될 때와 동일한 제약을 받습니다.
기타 Python 구현
피드백이 남아 있지만 Python 구현에 미치는 영향은 최소화될 것으로 예상됩니다. 모든 준수 구현은 이 PEP에 설명된 대로 __definition_order__를 설정해야 합니다.
구현
구현은 tracker에서 확인할 수 있습니다.
대안
순서를 보존하는 cls.__dict__
정의 순서를 __definition_order__에 저장하는 대신, 이제 순서가 지정된 정의 네임스페이스를 새로운 OrderedDict로 복사할 수 있습니다. 그러면 이를 __dict__로 프록시되는 매핑으로 사용합니다. 이렇게 하면 대부분 동일한 의미를 제공합니다.
그러나 OrderedDict를 __dict__에 사용하면 정의 네임스페이스와의 관계가 모호해져 유용성이 떨어집니다.
또한 특히 OrderedDict의 경우 이렇게 하려면 구체적인 dict C-API의 의미론을 크게 변경해야 합니다.
삽입 순서를 (대부분) 보존하는 압축 딕셔너리 구현으로 전환하는 방안에 대한 논의가 있었습니다. 그러나 명시적인 __definition_order__가 없다는 문제점은 여전히 남습니다.
클래스 정의를 위한 “네임스페이스” 키워드 인자
PEP 422는 클래스 정의에 새로운 “네임스페이스” 키워드 인자를 도입했으며, 이는 사실상 __prepare__()의 필요성을 대체합니다. 그러나 더 단순한 PEP 487을 채택하기 위해 이 제안은 철회되었습니다.
OrderedDict로 __prepare__()를 구현하는 표준 라이브러리 메타클래스
이것은 자신만의 메타클래스를 작성하는 것과 동일한 모든 문제를 안고 있습니다. 유일한 장점은 실제로 이 메타클래스를 작성할 필요가 없다는 것입니다. 따라서 이 PEP의 맥락에서는 아무런 이점을 제공하지 않습니다.
컴파일 타임에 __definition_order__ 설정하기
각 클래스의 __qualname__은 컴파일 타임에 결정됩니다. 이와 동일한 개념을 __definition_order__에도 적용할 수 있습니다. 컴파일 타임에 __definition_order__을 구성한 결과는 런타임에 구성하는 것과 거의 동일할 것입니다.
구현 난이도의 비교를 차치하고, 핵심적인 차이는 컴파일 타임에는 클래스 본문에서 동적으로 설정되는 속성(예: locals()[name] = value)의 정의 순서를 보존하는 것이 실용적이지 않다는 점일 것입니다. 그러나 이러한 속성도 여전히 정의 순서에 반영되어야 합니다. 가능한 해결책 중 하나는 클래스 작성자가 클래스 속성을 동적으로 정의하는 경우 __definition_order__을 수동으로 설정하도록 요구하는 것입니다.
궁극적으로 런타임에 OrderedDict를 사용할지 컴파일 타임에 탐지할지는 거의 전적으로 구현 세부사항입니다.
참고 문헌
Copyright
This document has been placed in the public domain.