PEP 447 – 메타클래스에 __getdescriptor__ 메서드 추가
- Author:
- Ronald Oussoren <ronaldoussoren at mac.com>
- Status:
- Deferred
- Type:
- Standards Track
- Created:
- 12-Jun-2013
- Post-History:
- 02-Jul-2013, 15-Jul-2013, 29-Jul-2013, 22-Jul-2015
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
현재 object.__getattribute__와 super.__getattribute__는 속성을 찾을 때 클래스의 MRO에 있는 클래스들의 __dict__를 살펴봅니다. 이 PEP에서는 메타클래스에 선택적인 __getdescriptor__ 메서드를 추가하여 이 동작을 대체하고, 특히 super 객체를 사용할 때 속성 조회를 더 세밀하게 제어할 수 있도록 합니다.
즉, _PyType_Lookup과 super.__getattribute__에 있는 MRO 순회 루프가 다음에서 변경됩니다.:
def lookup(mro_list, name):
for cls in mro_list:
if name in cls.__dict__:
return cls.__dict__
return NotFound
다음으로:
def lookup(mro_list, name):
for cls in mro_list:
try:
return cls.__getdescriptor__(name)
except AttributeError:
pass
return NotFound
__getdescriptor__의 기본 구현은 클래스 딕셔너리를 살펴봅니다.:
class type:
def __getdescriptor__(cls, name):
try:
return cls.__dict__[name]
except KeyError:
raise AttributeError(name) from None
PEP 상태
이 PEP는 누군가 이 PEP를 갱신하고 추진할 시간이 생길 때까지 보류됩니다.
근거
현재 super class가 속성을 조회하는 방식을 제어할 수 없습니다(즉, super.__getattribute__는 무조건 클래스의 __dict__를 살펴봅니다). 이는 필요에 따라 새 메서드를 추가할 수 있는 동적 클래스, 예를 들어 동적 프록시 클래스에 문제가 될 수 있습니다.
__getdescriptor__ 메서드를 사용하면 super class를 통해 속성을 조회할 때에도 속성을 동적으로 추가할 수 있습니다.
새 메서드는 일관성을 유지하고 클래스의 동적 속성 해석을 구현할 단일 위치를 제공하기 위해 object.__getattribute__(및 PyObject_GenericGetAttr)에도 영향을 줍니다.
배경
현재 super.__getattribute__의 동작은 다른 (Python이 아닌) 클래스 또는 타입의 동적 프록시인 클래스에 문제를 일으키며, 그 예로 PyObjC를 들 수 있습니다. PyObjC는 Objective-C 런타임의 각 클래스에 대해 Python 클래스를 생성하고, 메서드가 사용될 때 Objective-C 런타임에서 해당 메서드를 조회합니다. 이는 일반적인 접근에서는 정상적으로 작동하지만, super 객체를 통한 접근에서는 작동하지 않습니다. 이 때문에 PyObjC에는 현재 해당 클래스와 함께 사용해야 하는 사용자 정의 super가 포함되어 있으며, 일반적인 속성 접근을 위해 PyObject_GenericGetAttr 도 완전히 다시 구현해야 합니다.
이 PEP의 API를 사용하면 사용자 정의 super를 제거할 수 있고, 사용자 정의 조회 동작을 중앙 위치에 추가할 수 있으므로 구현도 단순해집니다.
Note
PyObjC 는 Objective-C 클래스가 런타임에 새 메서드를 추가할 수 있기 때문에 클래스 __dict__의 내용을 미리 계산할 수 없습니다. 또한 Objective-C 클래스에는 많은 메서드가 포함되는 경향이 있는 반면 대부분의 Python 코드는 그중 일부만 사용하므로, 이를 미리 계산하면 불필요하게 비용이 많이 듭니다.
슈퍼클래스 속성 조회 훅
super.__getattribute__와 object.__getattribute__(또는 PyObject_GenericGetAttr 및 특히 _PyType_Lookup를 C 코드에서 사용)는 객체의 MRO를 순회하며 현재 속성을 조회하기 위해 클래스의 __dict__를 살펴봅니다.
이 제안에 따르면 두 조회 메서드는 더 이상 클래스의 __dict__를 살펴보지 않고, 메타클래스에 정의된 슬롯인 특수 메서드 __getdescriptor__를 호출합니다. 해당 메서드의 기본 구현은 클래스 __dict__에서 이름을 조회하므로, 메타타입이 실제로 새 특수 메서드를 정의하지 않는 한 속성 조회는 변경되지 않습니다.
참고: Python의 속성 해석 알고리즘
object.__getattribute__ (또는 CPython 구현의 PyObject_GenericGetAttr)가 구현하는 속성 해석 과정은 상당히 간단하지만, C 코드를 읽지 않으면 완전히 그렇지는 않습니다.
현재 CPython의 object.__getattribute__ 구현은 기본적으로 다음 (의사) Python 코드와 동등합니다(일부 정리 작업과 속도 최적화 기법은 제외합니다).:
def _PyType_Lookup(tp, name):
mro = tp.mro()
assert isinstance(mro, tuple)
for base in mro:
assert isinstance(base, type)
# PEP 447 will change these lines:
try:
return base.__dict__[name]
except KeyError:
pass
return None
class object:
def __getattribute__(self, name):
assert isinstance(name, str)
tp = type(self)
descr = _PyType_Lookup(tp, name)
f = None
if descr is not None:
f = descr.__get__
if f is not None and descr.__set__ is not None:
# Data descriptor
return f(descr, self, type(self))
dict = self.__dict__
if dict is not None:
try:
return self.__dict__[name]
except KeyError:
pass
if f is not None:
# Non-data descriptor
return f(descr, self, type(self))
if descr is not None:
# Regular class attribute
return descr
raise AttributeError(name)
class super:
def __getattribute__(self, name):
assert isinstance(name, unicode)
if name != '__class__':
starttype = self.__self_type__
mro = startype.mro()
try:
idx = mro.index(self.__thisclass__)
except ValueError:
pass
else:
for base in mro[idx+1:]:
# PEP 447 will change these lines:
try:
descr = base.__dict__[name]
except KeyError:
continue
f = descr.__get__
if f is not None:
return f(descr,
None if (self.__self__ is self.__self_type__) else self.__self__,
starttype)
else:
return descr
return object.__getattribute__(self, name)
이 PEP에서는 “# PEP 447”로 시작하는 줄의 딕셔너리 조회를 실제 조회를 수행하는 메서드 호출로 변경하여, 일반적인 속성 접근과 super proxy를 통한 접근 모두에서 해당 조회에 영향을 줄 수 있도록 해야 합니다.
특정 클래스는 자체 __getattribute__ 슬롯을 구현하여 이미 기본 동작을 완전히 재정의할 수 있다는 점에 유의하십시오(슈퍼클래스 구현을 호출하는지 여부와 관계없이 가능합니다).
Python 코드에서
메타타입은 __getdescriptor__ 메서드를 정의할 수 있으며, 이 메서드는 super.__getattribute__와 object.__getattribute가 속성을 해석하는 동안 호출됩니다.:
class MetaType(type):
def __getdescriptor__(cls, name):
try:
return cls.__dict__[name]
except KeyError:
raise AttributeError(name) from None
__getdescriptor__ 메서드는 메타 타입의 인스턴스인 클래스와 조회되는 속성의 이름을 인자로 가집니다. 디스크립터를 호출하지 않고 속성의 값을 반환해야 하며, 이름을 찾을 수 없을 때는 AttributeError를 발생시켜야 합니다.
type 클래스는 클래스 딕셔너리에서 이름을 조회하는 __getdescriptor__의 기본 구현을 제공합니다.
사용 예
아래 코드는 속성 조회를 이름의 대문자 버전으로 리디렉션하는 단순한 메타클래스를 구현합니다.:
class UpperCaseAccess (type):
def __getdescriptor__(cls, name):
try:
return cls.__dict__[name.upper()]
except KeyError:
raise AttributeError(name) from None
class SillyObject (metaclass=UpperCaseAccess):
def m(self):
return 42
def M(self):
return "fortytwo"
obj = SillyObject()
assert obj.m() == "fortytwo"
이 PEP에서 앞서 언급했듯이, 이 기능의 보다 현실적인 사용 사례는 속성 접근을 기반으로 클래스 __dict__를 동적으로 채우는 __getdescriptor__ 메서드입니다. 이는 주로 클래스 딕셔너리를 소스와 안정적으로 동기화할 수 없는 경우에 사용되며, 예를 들어 __dict__를 채우는 데 사용되는 소스 자체도 동적이고 해당 소스의 변경을 감지하는 데 사용할 트리거가 없는 경우가 이에 해당합니다.
그 예로 PyObjC의 클래스 브리지가 있습니다. 클래스 브리지는 Objective-C 클래스를 나타내는 Python 객체(클래스)이며, 개념적으로 Objective-C 클래스의 모든 Objective-C 메서드에 대응하는 Python 메서드를 가집니다. Python과 마찬가지로 Objective-C 클래스에 새 메서드를 추가하거나 기존 메서드를 교체할 수 있지만, 이를 감지하는 데 사용할 수 있는 콜백은 없습니다.
C 코드에서
새로운 슬롯이 존재하며 사용되어야 함을 나타내는 값 (1UL << 11)의 새로운 타입 플래그 Py_TPFLAGS_GETDESCRIPTOR가 추가됩니다.
PyTypeObject 구조체에 새로운 슬롯 tp_getdescriptor가 추가되며, 이 슬롯은 type의 __getdescriptor__ 메서드에 대응합니다.
슬롯은 다음 프로토타입을 가집니다.:
PyObject* (*getdescriptorfunc)(PyTypeObject* cls, PyObject* name);
이 메서드는 슈퍼클래스를 조회하지 않고 cls의 네임스페이스에서 name을 조회해야 하며, 디스크립터를 호출해서는 안 됩니다. 이름 name을 찾을 수 없을 때 이 메서드는 예외를 설정하지 않고 NULL을 반환하며, 그렇지 않으면 새 참조를 반환합니다(빌린 참조가 아님).
tp_getdescriptor 슬롯이 있는 클래스는 새 슬롯을 사용해야 함을 나타내기 위해 Py_TPFLAGS_GETDESCRIPTOR를 tp_flags에 추가해야 합니다.
인터프리터에서 이 훅 사용
새로운 메서드는 메타 타입에 필요하므로 type_에 정의됩니다. super.__getattribute__와 object.__getattribute__/PyObject_GenericGetAttr 는(_PyType_Lookup을 통해) MRO를 순회할 때 이 __getdescriptor__ 메서드를 사용합니다.
구현에 대한 기타 변경 사항
PyObject_GenericGetAttr 의 변경은 비공개 함수 _PyType_Lookup을 변경하여 수행됩니다. 이 함수는 현재 빌린 참조를 반환하지만, __getdescriptor__ 메서드가 있을 때는 새 참조를 반환해야 합니다. 따라서 _PyType_Lookup은 _PyType_LookupName으로 이름이 변경되며, 이로 인해 이 비공개 API를 사용하는 모든 트리 외부 사용자는 컴파일 시 오류를 받게 됩니다.
같은 이유로 _PyType_LookupId는 _PyType_LookupId2로 이름이 변경됩니다. 같은 문제가 있는 typeobject.c의 다른 여러 함수는 해당 파일에 비공개이므로 이름이 업데이트되지 않습니다.
Objects/typeobject.c의 속성 조회 캐시는 __getdescriptor__를 재정의하는 메타클래스를 가진 클래스에서 비활성화됩니다. 이러한 클래스에서는 캐시를 사용하는 것이 유효하지 않을 수 있기 때문입니다.
이 PEP가 인트로스펙션에 미치는 영향
이 PEP에서 도입된 메서드는 사용자 지정 __getdescriptor__ 메서드를 사용하는 메타클래스를 가진 클래스의 인트로스펙션에 영향을 줄 수 있습니다. 이 절에서는 이러한 변경 사항을 나열합니다.
아래에 나열된 항목은 사용자 지정 __getdescriptor__ 메서드의 영향을 받을 뿐입니다. object의 기본 구현은 여전히 클래스 __dict__만 사용하므로 문제가 발생하지 않으며, object.__getattribute__의 눈에 보이는 동작에도 가시적인 변경을 일으키지 않습니다.
dir은 모든 속성을 표시하지 않을 수 있습니다.사용자 지정
__getattribute__메서드와 마찬가지로 dir() 은__getdescriptor__()메서드를 사용하여 속성을 동적으로 확인할 때 모든 (인스턴스) 속성을 확인하지 못할 수 있습니다.이에 대한 해결책은 매우 간단합니다.
__getdescriptor__를 사용하는 클래스가 내장 dir() 함수를 완전히 지원하려면 __dir__() 도 구현해야 합니다.inspect.getattr_static은 모든 속성을 표시하지 않을 수 있습니다.inspect.getattr_static함수는 이 함수를 사용한 인트로스펙션 중 사용자 코드를 호출하지 않도록__getattribute__와 디스크립터를 의도적으로 호출하지 않습니다.__getdescriptor__메서드도 무시되며, 이 때문에inspect.getattr_static의 결과가builtin.getattr의 결과와 달라질 수 있습니다.inspect.getmembers및inspect.classify_class_attrs이 두 함수는 MRO를 따라 존재하는 클래스들의 클래스 __dict__에 직접 접근하므로, 사용자 정의
__getdescriptor__메서드의 영향을 받을 수 있습니다.사용자 정의
__getdescriptor__메서드를 사용하는 코드가 이러한 메서드와 올바르게 호환되려면, Python 코드가 직접 접근할 때__dict__가 올바르게 설정되어 있는지도 보장해야 합니다.inspect.getmembers는pydoc에서 사용되므로, 이로 인해 런타임 문서 인트로스펙션에 영향을 줄 수 있다는 점에 유의하십시오.- 클래스
__dict__의 직접 인트로스펙션인트로스펙션을 위해 클래스
__dict__에 직접 접근하는 코드는 사용자 정의__getdescriptor__메서드의 영향을 받을 수 있습니다. 이전 항목을 참조하십시오.
성능 영향
경고: 이 절의 벤치마크 결과는 오래된 것이며, 패치를 현재 trunk로 포팅하면 업데이트할 예정입니다. 이 절의 결과에는 큰 변화가 없을 것으로 예상합니다.
마이크로 벤치마크
Issue 18181에는 첨부 파일 중 하나로 마이크로 벤치마크(pep447-micro-bench.py)가 포함되어 있으며, 이 벤치마크는 직접 수행하는 경우와 super를 통한 경우 모두에서 속성 조회 속도를 구체적으로 테스트합니다.
사용자 정의 __getdescriptor__ 메서드를 사용할 때 깊은 클래스 계층 구조에서의 속성 조회가 크게 느려진다는 점에 유의하십시오. 이 메서드가 있으면 CPython의 속성 조회 캐시를 사용할 수 없기 때문입니다.
Pybench
아래의 pybench 출력은 changeset a5681f50bae2를 기반으로 하며, 유휴 상태인 머신과 Centos 6.4를 실행하는 Core i7 프로세서에서 실행한 이 PEP의 구현과 일반 소스 트리를 비교합니다.
머신이 유휴 상태였음에도 실행마다 뚜렷한 차이가 있었으며, “최소 시간”의 차이는 -0.1%에서 +1.5%까지 변동했고, “평균 시간” 차이에서도 비슷하지만 약간 더 작은 차이가 나타났습니다.
-------------------------------------------------------------------------------
PYBENCH 2.1
-------------------------------------------------------------------------------
* using CPython 3.4.0a0 (default, Jul 29 2013, 13:01:34) [GCC 4.4.7 20120313 (Red Hat 4.4.7-3)]
* disabled garbage collection
* system check interval set to maximum: 2147483647
* using timer: time.perf_counter
* timer: resolution=1e-09, implementation=clock_gettime(CLOCK_MONOTONIC)
-------------------------------------------------------------------------------
Benchmark: pep447.pybench
-------------------------------------------------------------------------------
Rounds: 10
Warp: 10
Timer: time.perf_counter
Machine Details:
Platform ID: Linux-2.6.32-358.114.1.openstack.el6.x86_64-x86_64-with-centos-6.4-Final
Processor: x86_64
Python:
Implementation: CPython
Executable: /tmp/default-pep447/bin/python3
Version: 3.4.0a0
Compiler: GCC 4.4.7 20120313 (Red Hat 4.4.7-3)
Bits: 64bit
Build: Jul 29 2013 14:09:12 (#default)
Unicode: UCS4
-------------------------------------------------------------------------------
Comparing with: default.pybench
-------------------------------------------------------------------------------
Rounds: 10
Warp: 10
Timer: time.perf_counter
Machine Details:
Platform ID: Linux-2.6.32-358.114.1.openstack.el6.x86_64-x86_64-with-centos-6.4-Final
Processor: x86_64
Python:
Implementation: CPython
Executable: /tmp/default/bin/python3
Version: 3.4.0a0
Compiler: GCC 4.4.7 20120313 (Red Hat 4.4.7-3)
Bits: 64bit
Build: Jul 29 2013 13:01:34 (#default)
Unicode: UCS4
Test minimum run-time average run-time
this other diff this other diff
-------------------------------------------------------------------------------
BuiltinFunctionCalls: 45ms 44ms +1.3% 45ms 44ms +1.3%
BuiltinMethodLookup: 26ms 27ms -2.4% 27ms 27ms -2.2%
CompareFloats: 33ms 34ms -0.7% 33ms 34ms -1.1%
CompareFloatsIntegers: 66ms 67ms -0.9% 66ms 67ms -0.8%
CompareIntegers: 51ms 50ms +0.9% 51ms 50ms +0.8%
CompareInternedStrings: 34ms 33ms +0.4% 34ms 34ms -0.4%
CompareLongs: 29ms 29ms -0.1% 29ms 29ms -0.0%
CompareStrings: 43ms 44ms -1.8% 44ms 44ms -1.8%
ComplexPythonFunctionCalls: 44ms 42ms +3.9% 44ms 42ms +4.1%
ConcatStrings: 33ms 33ms -0.4% 33ms 33ms -1.0%
CreateInstances: 47ms 48ms -2.9% 47ms 49ms -3.4%
CreateNewInstances: 35ms 36ms -2.5% 36ms 36ms -2.5%
CreateStringsWithConcat: 69ms 70ms -0.7% 69ms 70ms -0.9%
DictCreation: 52ms 50ms +3.1% 52ms 50ms +3.0%
DictWithFloatKeys: 40ms 44ms -10.1% 43ms 45ms -5.8%
DictWithIntegerKeys: 32ms 36ms -11.2% 35ms 37ms -4.6%
DictWithStringKeys: 29ms 34ms -15.7% 35ms 40ms -11.0%
ForLoops: 30ms 29ms +2.2% 30ms 29ms +2.2%
IfThenElse: 38ms 41ms -6.7% 38ms 41ms -6.9%
ListSlicing: 36ms 36ms -0.7% 36ms 37ms -1.3%
NestedForLoops: 43ms 45ms -3.1% 43ms 45ms -3.2%
NestedListComprehensions: 39ms 40ms -1.7% 39ms 40ms -2.1%
NormalClassAttribute: 86ms 82ms +5.1% 86ms 82ms +5.0%
NormalInstanceAttribute: 42ms 42ms +0.3% 42ms 42ms +0.0%
PythonFunctionCalls: 39ms 38ms +3.5% 39ms 38ms +2.8%
PythonMethodCalls: 51ms 49ms +3.0% 51ms 50ms +2.8%
Recursion: 67ms 68ms -1.4% 67ms 68ms -1.4%
SecondImport: 41ms 36ms +12.5% 41ms 36ms +12.6%
SecondPackageImport: 45ms 40ms +13.1% 45ms 40ms +13.2%
SecondSubmoduleImport: 92ms 95ms -2.4% 95ms 98ms -3.6%
SimpleComplexArithmetic: 28ms 28ms -0.1% 28ms 28ms -0.2%
SimpleDictManipulation: 57ms 57ms -1.0% 57ms 58ms -1.0%
SimpleFloatArithmetic: 29ms 28ms +4.7% 29ms 28ms +4.9%
SimpleIntFloatArithmetic: 37ms 41ms -8.5% 37ms 41ms -8.7%
SimpleIntegerArithmetic: 37ms 41ms -9.4% 37ms 42ms -10.2%
SimpleListComprehensions: 33ms 33ms -1.9% 33ms 34ms -2.9%
SimpleListManipulation: 28ms 30ms -4.3% 29ms 30ms -4.1%
SimpleLongArithmetic: 26ms 26ms +0.5% 26ms 26ms +0.5%
SmallLists: 40ms 40ms +0.1% 40ms 40ms +0.1%
SmallTuples: 46ms 47ms -2.4% 46ms 48ms -3.0%
SpecialClassAttribute: 126ms 120ms +4.7% 126ms 121ms +4.4%
SpecialInstanceAttribute: 42ms 42ms +0.6% 42ms 42ms +0.8%
StringMappings: 94ms 91ms +3.9% 94ms 91ms +3.8%
StringPredicates: 48ms 49ms -1.7% 48ms 49ms -2.1%
StringSlicing: 45ms 45ms +1.4% 46ms 45ms +1.5%
TryExcept: 23ms 22ms +4.9% 23ms 22ms +4.8%
TryFinally: 32ms 32ms -0.1% 32ms 32ms +0.1%
TryRaiseExcept: 17ms 17ms +0.9% 17ms 17ms +0.5%
TupleSlicing: 49ms 48ms +1.1% 49ms 49ms +1.0%
WithFinally: 48ms 47ms +2.3% 48ms 47ms +2.4%
WithRaiseExcept: 45ms 44ms +0.8% 45ms 45ms +0.5%
-------------------------------------------------------------------------------
Totals: 2284ms 2287ms -0.1% 2306ms 2308ms -0.1%
(this=pep447.pybench, other=default.pybench)
벤치마크 모음을 옵션 “-b 2n3”과 함께 실행한 결과도 성능 영향이 미미함을 나타내는 것으로 보입니다.:
Report on Linux fangorn.local 2.6.32-358.114.1.openstack.el6.x86_64 #1 SMP Wed Jul 3 02:11:25 EDT 2013 x86_64 x86_64
Total CPU cores: 8
### call_method_slots ###
Min: 0.304120 -> 0.282791: 1.08x faster
Avg: 0.304394 -> 0.282906: 1.08x faster
Significant (t=2329.92)
Stddev: 0.00016 -> 0.00004: 4.1814x smaller
### call_simple ###
Min: 0.249268 -> 0.221175: 1.13x faster
Avg: 0.249789 -> 0.221387: 1.13x faster
Significant (t=2770.11)
Stddev: 0.00012 -> 0.00013: 1.1101x larger
### django_v2 ###
Min: 0.632590 -> 0.601519: 1.05x faster
Avg: 0.635085 -> 0.602653: 1.05x faster
Significant (t=321.32)
Stddev: 0.00087 -> 0.00051: 1.6933x smaller
### fannkuch ###
Min: 1.033181 -> 0.999779: 1.03x faster
Avg: 1.036457 -> 1.001840: 1.03x faster
Significant (t=260.31)
Stddev: 0.00113 -> 0.00070: 1.6112x smaller
### go ###
Min: 0.526714 -> 0.544428: 1.03x slower
Avg: 0.529649 -> 0.547626: 1.03x slower
Significant (t=-93.32)
Stddev: 0.00136 -> 0.00136: 1.0028x smaller
### iterative_count ###
Min: 0.109748 -> 0.116513: 1.06x slower
Avg: 0.109816 -> 0.117202: 1.07x slower
Significant (t=-357.08)
Stddev: 0.00008 -> 0.00019: 2.3664x larger
### json_dump_v2 ###
Min: 2.554462 -> 2.609141: 1.02x slower
Avg: 2.564472 -> 2.620013: 1.02x slower
Significant (t=-76.93)
Stddev: 0.00538 -> 0.00481: 1.1194x smaller
### meteor_contest ###
Min: 0.196336 -> 0.191925: 1.02x faster
Avg: 0.196878 -> 0.192698: 1.02x faster
Significant (t=61.86)
Stddev: 0.00053 -> 0.00041: 1.2925x smaller
### nbody ###
Min: 0.228039 -> 0.235551: 1.03x slower
Avg: 0.228857 -> 0.236052: 1.03x slower
Significant (t=-54.15)
Stddev: 0.00130 -> 0.00029: 4.4810x smaller
### pathlib ###
Min: 0.108501 -> 0.105339: 1.03x faster
Avg: 0.109084 -> 0.105619: 1.03x faster
Significant (t=311.08)
Stddev: 0.00022 -> 0.00011: 1.9314x smaller
### regex_effbot ###
Min: 0.057905 -> 0.056447: 1.03x faster
Avg: 0.058055 -> 0.056760: 1.02x faster
Significant (t=79.22)
Stddev: 0.00006 -> 0.00015: 2.7741x larger
### silent_logging ###
Min: 0.070810 -> 0.072436: 1.02x slower
Avg: 0.070899 -> 0.072609: 1.02x slower
Significant (t=-191.59)
Stddev: 0.00004 -> 0.00008: 2.2640x larger
### spectral_norm ###
Min: 0.290255 -> 0.299286: 1.03x slower
Avg: 0.290335 -> 0.299541: 1.03x slower
Significant (t=-572.10)
Stddev: 0.00005 -> 0.00015: 2.8547x larger
### threaded_count ###
Min: 0.107215 -> 0.115206: 1.07x slower
Avg: 0.107488 -> 0.115996: 1.08x slower
Significant (t=-109.39)
Stddev: 0.00016 -> 0.00076: 4.8665x larger
The following not significant results are hidden, use -v to show them:
call_method, call_method_unknown, chaos, fastpickle, fastunpickle, float, formatted_logging, hexiom2, json_load, normal_startup, nqueens, pidigits, raytrace, regex_compile, regex_v8, richards, simple_logging, startup_nosite, telco, unpack_sequence.
대안 제안
__getattribute_super__
이 PEP의 이전 버전에서는 클래스에 다음 정적 메서드를 사용했습니다.:
def __getattribute_super__(cls, name, object, owner): pass
이 메서드는 이름 조회와 디스크립터 호출을 모두 수행했으며, super.__getattribute__와 함께 작동하는 경우로만 제한될 수밖에 없었습니다.
tp_getattro 재사용
새로운 슬롯을 추가하지 않으면 API를 더 단순하고 이해하기 쉽게 유지할 수 있으므로 좋을 것입니다. Issue 18181에 달린 댓글에서는 tp_getattro 슬롯을 재사용하는 방안, 즉 super가 MRO를 따라 모든 메서드의 tp_getattro 슬롯을 호출할 수 있는 방안에 관해 질문했습니다.
이는 작동하지 않습니다. tp_getattro는 MRO의 클래스를 사용해 속성을 확인하기 전에 인스턴스 __dict__를 확인하기 때문입니다. 이는 클래스 딕셔너리를 엿보는 대신 tp_getattro를 사용하면 super class의 의미 체계가 변경된다는 것을 의미합니다.
새 메서드의 다른 배치
이 PEP는 __getdescriptor__를 메타클래스의 메서드로 추가할 것을 제안합니다. 대안으로는 클래스 자체의 클래스 메서드로 추가하는 방법이 있습니다 (__new__가 메타클래스의 메서드가 아니라 클래스의 staticmethod인 것과 유사합니다).
메타클래스의 메서드를 사용하는 것의 이점은 MRO 상의 두 클래스가 서로 다른 메타클래스를 가지고 있어 __getdescriptor__에 대해 다른 동작을 할 수 있는 경우 오류를 발생시킨다는 것입니다. 일반적인 클래스 메서드를 사용하면 이 문제가 감지되지 않고 지나가면서도, 코드 실행 시 미묘한 오류를 일으킬 수 있습니다.
역사
- 2015년 7월 23일: Guido와 논의한 후 타입 플래그
Py_TPFLAGS_GETDESCRIPTOR를 추가했습니다.이 새 플래그는 주로 이전 버전의 CPython용 확장을 로드할 때 충돌을 피하는 데 유용하며, 속도 면에서도 긍정적인 영향을 미칠 수 있습니다.
- 2014년 7월: 슬롯 이름을
__getdescriptor__로 변경했습니다. 이전 이름은 다른 슬롯들의 명명 스타일과 맞지 않았고 설명력도 떨어졌습니다.
토론 스레드
- 이 PEP의 초기 버전은 Message-ID mailto:75030FAC-6918-4E94-95DA-67A88D53E6F5@mac.com로 전송되었습니다.
- 추가 논의는 Message-ID mailto:5BB87CC4-F31B-4213-AAAC-0C0CE738460C@mac.com인 메시지에서 시작되었습니다.
- 그리고 더 많은 논의가 Message-ID mailto:00AA7433-C853-4101-9718-060468EBAC54@mac.com인 메시지에서 시작되었습니다.
참고 문헌
- Issue 18181에는 오래된 프로토타입 구현이 포함되어 있습니다.
Copyright
This document has been placed in the public domain.