PEP 267 – 모듈 네임스페이스에 대한 최적화된 접근
- Author:
- Jeremy Hylton <jeremy at alum.mit.edu>
- Status:
- Deferred
- Type:
- Standards Track
- Created:
- 23-May-2001
- Python-Version:
- 2.2
- Post-History:
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
보류
이 PEP는 좋은 아이디어이지만, 이 PEP와 PEP 266, PEP 280 사이의 차이점을 정리하는 작업을 맡겠다고 나선 사람이 아직 없습니다. 따라서 이 PEP는 보류되었습니다.
초록
이 PEP는 이름 해석 속도를 높이는, 전역 모듈 네임스페이스와 내장 네임스페이스의 새로운 구현을 제안합니다. 이 구현은 이러한 네임스페이스에서 이루어지는 대부분의 연산에 객체 포인터 배열을 사용합니다. 컴파일러는 컴파일 시점에 전역 변수와 모듈 속성에 인덱스를 할당합니다.
현재 구현은 이러한 네임스페이스를 딕셔너리로 표현합니다. 전역 이름은 사용될 때마다 딕셔너리 조회를 한 번 거칩니다. 내장 이름은 딕셔너리 조회를 두 번 거치는데, 전역 네임스페이스에서 실패하는 조회가 한 번, 내장 네임스페이스에서 하는 조회가 한 번입니다.
이 구현은 모듈 수준 함수와 변수를 사용하는 Python 코드의 속도를 높일 것입니다. 또한 이러한 이름에 더 빠르게 접근하려고 생겨난 어색한 코딩 스타일도 없앨 수 있을 것입니다.
전역 네임스페이스와 내장 네임스페이스는 컴파일러가 감지할 수 없는 방식으로 동적으로 수정될 수 있기 때문에 구현이 복잡합니다. (예: 모듈을 임포트한 후에 스크립트가 그 모듈의 네임스페이스를 수정하는 경우) 그 결과, 구현은 이러한 동적 기능을 보존하기 위해 여러 보조 자료 구조를 유지해야 합니다.
소개
이 PEP는 컴파일 시점에 알려진 모듈 변수에 대한 접근을 최적화하는, 모듈 객체의 속성 접근에 대한 새로운 구현을 제안합니다. 모듈은 이러한 변수를 배열에 저장하고, 배열 오프셋을 사용해 속성을 조회하는 인터페이스를 제공합니다. 전역 변수, 내장 이름, 임포트된 모듈의 속성에 대해 컴파일러는 빠른 접근을 위해 배열 오프셋을 사용하는 코드를 생성합니다.
[설계의 핵심 부분을 설명하십시오: dlict, 컴파일러 지원, 어설픈 이름 트릭에 대한 우회책, 다른 모듈의 전역 변수 최적화]
이 구현은 내장 이름의 가시성에 영향을 주는 방식으로 런타임에 모듈 네임스페이스를 수정하는 기능을 포함하여, 모듈 네임스페이스의 기존 의미를 유지합니다.
DLict 설계
네임스페이스는 때때로 dlict라는 이름으로 불려 온 자료 구조를 사용해 구현됩니다. 이는 일부 딕셔너리 항목에 번호가 매겨진 슬롯이 있는 딕셔너리입니다. 받아들일 만한 성능을 내려면 이 타입을 C로 구현해야 합니다. 새로운 타입-클래스 통합 작업 덕분에 이 작업은 꽤 쉬워질 것입니다. DLict는 아마도 일부 키에 대해 별도의 저장 모듈을 갖는 딕셔너리의 서브클래스가 될 것입니다.
기본 설계를 보여 주기 위해 Python 구현을 여기에 포함합니다:
"""A dictionary-list hybrid"""
import types
class DLict:
def __init__(self, names):
assert isinstance(names, types.DictType)
self.names = {}
self.list = [None] * size
self.empty = [1] * size
self.dict = {}
self.size = 0
def __getitem__(self, name):
i = self.names.get(name)
if i is None:
return self.dict[name]
if self.empty[i] is not None:
raise KeyError, name
return self.list[i]
def __setitem__(self, name, val):
i = self.names.get(name)
if i is None:
self.dict[name] = val
else:
self.empty[i] = None
self.list[i] = val
self.size += 1
def __delitem__(self, name):
i = self.names.get(name)
if i is None:
del self.dict[name]
else:
if self.empty[i] is not None:
raise KeyError, name
self.empty[i] = 1
self.list[i] = None
self.size -= 1
def keys(self):
if self.dict:
return self.names.keys() + self.dict.keys()
else:
return self.names.keys()
def values(self):
if self.dict:
return self.names.values() + self.dict.values()
else:
return self.names.values()
def items(self):
if self.dict:
return self.names.items()
else:
return self.names.items() + self.dict.items()
def __len__(self):
return self.size + len(self.dict)
def __cmp__(self, dlict):
c = cmp(self.names, dlict.names)
if c != 0:
return c
c = cmp(self.size, dlict.size)
if c != 0:
return c
for i in range(len(self.names)):
c = cmp(self.empty[i], dlict.empty[i])
if c != 0:
return c
if self.empty[i] is None:
c = cmp(self.list[i], dlict.empty[i])
if c != 0:
return c
return cmp(self.dict, dlict.dict)
def clear(self):
self.dict.clear()
for i in range(len(self.names)):
if self.empty[i] is None:
self.empty[i] = 1
self.list[i] = None
def update(self):
pass
def load(self, index):
"""dlict-special method to support indexed access"""
if self.empty[index] is None:
return self.list[index]
else:
raise KeyError, index # XXX might want reverse mapping
def store(self, index, val):
"""dlict-special method to support indexed access"""
self.empty[index] = None
self.list[index] = val
def delete(self, index):
"""dlict-special method to support indexed access"""
self.empty[index] = 1
self.list[index] = None
컴파일러 문제
현재 컴파일러는 모듈에 있는 모든 전역 변수의 이름을 수집합니다. 이러한 이름은 모듈 수준에서 바인딩된 이름이거나, 해당 이름을 전역으로 선언한 클래스 또는 함수 본문에서 바인딩된 이름입니다.
컴파일러는 각 전역 이름에 인덱스를 할당하고, 전역 변수의 이름과 인덱스를 모듈의 코드 객체에 추가합니다. 그러면 각 코드 객체는 자신이 정의된 모듈에 되돌릴 수 없게 바인딩됩니다. (이 방식에 미묘한 문제가 있는지는 확실하지 않습니다.)
임포트된 모듈의 속성에 대해서는 모듈이 간접 참조 레코드를 저장합니다. 내부적으로 모듈은 해당 속성을 정의하는 모듈에 대한 포인터와, 정의하는 모듈의 전역 변수 배열에서 그 속성이 위치한 오프셋을 저장합니다. 오프셋은 해당 이름을 처음 조회할 때 초기화됩니다.
런타임 모델
PythonVM은 모듈 수준 배열을 통해 전역 변수와 모듈 속성에 접근하는 새로운 옵코드로 확장됩니다.
함수 객체는 모듈 수준 전역 배열에 접근할 수 있도록 자신을 정의한 모듈을 가리켜야 합니다.
dlict에 저장된 모듈 속성(이를 정적 속성이라고 부릅시다)의 경우, get/delattr 구현은 기존의 이름 기반 인터페이스를 사용하는 이러한 속성에 대한 접근을 추적해야 합니다. 정적 속성이 동적으로 갱신되는 경우, 예를 들어:
mod.__dict__["foo"] = 2
구현은 백업 딕셔너리 대신 배열 슬롯을 갱신해야 합니다.
하위 호환성
dlict는 슬롯이 현재 사용 중인지 여부에 대한 메타 정보를 유지해야 합니다. 또한 내장 네임스페이스에 대한 포인터도 유지해야 합니다. 어떤 이름이 현재 전역 네임스페이스에서 사용되지 않으면, 조회는 내장 네임스페이스로 넘어가야 합니다.
반대의 경우, 각 모듈에는 내장 이름을 가리는 전역 이름이 동적으로 추가되었는지 확인하는, 내장 네임스페이스용 특수 접근자 함수가 필요할 수 있습니다. 이 검사는 모듈의 dlict에 동적 변경이 있었을 때, 즉 컴파일 시점에 발견되지 않은 이름이 바인딩되었을 때만 수행됩니다.
이러한 메커니즘은 모듈의 전역 네임스페이스가 런타임에 이상한 방식으로 수정되지 않는 일반적인 경우에는 비용이 거의 또는 전혀 들지 않습니다. 전역 이름으로 특이한 일을 하는 모듈에는 오버헤드가 추가되겠지만, 이는 흔하지 않은 관행이며 아마도 지양하도록 권장할 만한 관행입니다.
향후 어떤 버전의 Python에서는 전역 네임스페이스에 대한 동적 추가를 금지하는 것이 바람직할 수도 있습니다. 그렇게 된다면 새 구현이 경고를 제공할 수 있습니다.
관련 PEP
PEP 266, 전역 변수/속성 접근 최적화(Optimizing Global Variable/Attribute Access)는 전역 변수뿐만 아니라 객체의 속성에 대한 접근을 최적화하는 다른 메커니즘을 제안합니다. 이 메커니즘은 두 개의 새로운 옵코드 TRACK_OBJECT와 UNTRACK_OBJECT를 사용하여, 전역 변수나 객체 속성의 별칭이 되는 슬롯을 지역 변수 배열에 생성합니다. 별칭 대상 객체가 다시 바인딩되면, 재바인딩 연산이 별칭을 갱신할 책임을 집니다.
객체 추적 방식은 모듈에만 국한되지 않고 더 넓은 범위의 객체에 적용됩니다. 또한 런타임 비용이 더 높을 수 있습니다. 전역 변수나 객체 속성을 사용하는 각 함수는 객체에 대한 관심을 등록하고 종료할 때 등록을 해제하기 위해 추가 옵코드를 실행해야 하기 때문입니다. 등록 비용은 분명하지 않지만, 아마도 콜백 목록을 담기 위해 동적으로 크기를 조절할 수 있는 자료 구조가 필요할 것입니다.
여기에서 제안하는 구현은 별칭을 생성하지 않으므로 등록이 필요하지 않습니다. 대신 전역 변수나 모듈 속성을 참조하는 함수가 원래 바인딩이 저장된 위치에 대한 포인터를 유지할 수 있게 합니다. 두 번째 장점은 초기 조회가 함수 호출마다 한 번이 아니라 모듈마다 한 번 수행된다는 것입니다.
Copyright
This document has been placed in the public domain.