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는 이름 해석 속도를 높이는 전역 모듈 네임스페이스와 내장 네임스페이스의 새로운 구현을 제안합니다. 이 구현은 이러한 네임스페이스에서 대부분의 연산에 객체 포인터 배열을 사용할 것입니다. 컴파일러는 컴파일 시점에 전역 변수와 모듈 속성에 대한 인덱스를 할당할 것입니다.
현재 구현은 이러한 네임스페이스를 딕셔너리로 나타냅니다. 전역 이름은 사용될 때마다 딕셔너리 조회가 발생하고, 내장 이름은 두 번의 딕셔너리 조회, 즉 전역 네임스페이스에서의 실패한 조회와 내장 네임스페이스에서의 두 번째 조회가 발생합니다.
이 구현은 모듈 수준의 함수와 변수를 사용하는 파이썬 코드의 속도를 높일 것입니다. 또한 이 구현은 이러한 이름에 대한 접근 속도를 높이기 위해 발전해 온 어색한 코딩 스타일을 없애줄 것입니다.
전역 네임스페이스와 내장 네임스페이스는 컴파일러가 감지할 수 없는 방식으로 동적으로 수정될 수 있기 때문에 구현이 복잡합니다. (예: 모듈이 임포트된 후 스크립트에 의해 그 모듈의 네임스페이스가 수정되는 경우.) 그 결과, 이러한 동적 특성을 유지하기 위해 구현은 여러 보조 데이터 구조를 관리해야 합니다.
소개
이 PEP는 컴파일 타임에 알려진 모듈 변수에 대한 접근을 최적화하는, 모듈 객체를 위한 새로운 속성 접근 구현을 제안합니다. 모듈은 이러한 변수들을 배열에 저장하고, 배열 오프셋을 사용하여 속성을 조회하는 인터페이스를 제공합니다. 전역 변수, 내장 이름, 그리고 임포트된 모듈의 속성의 경우, 컴파일러는 빠른 접근을 위해 배열 오프셋을 사용하는 코드를 생성합니다.
[설계의 핵심 부분들, 즉 dlict, 컴파일러 지원, 어리석은 이름 트릭 우회 방법, 다른 모듈의 전역 변수 최적화를 설명하십시오]
이 구현은 모듈 네임스페이스에 대한 기존의 의미론을 보존하며, 여기에는 내장 이름의 가시성에 영향을 미치는 방식으로 런타임에 모듈 네임스페이스를 수정할 수 있는 능력도 포함됩니다.
DLict 설계
이 네임스페이스는 때때로 dlict라는 이름으로 불려온 자료 구조를 사용하여 구현됩니다. 이는 일부 딕셔너리 항목에 번호가 매겨진 슬롯을 가진 딕셔너리입니다. 이 타입은 허용 가능한 성능을 얻기 위해 C로 구현되어야 합니다. 새로운 타입-클래스 통합 작업 덕분에 이는 상당히 쉬워질 것입니다. DLict는 아마도 일부 키에 대한 대체 저장 모듈을 가진 딕셔너리의 서브클래스가 될 것입니다.
여기에는 기본 설계를 보여주기 위한 파이썬 구현이 포함되어 있습니다.:
"""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)는 전역 변수뿐만 아니라 객체의 속성에 대한 접근을 최적화하는 다른 메커니즘을 제안합니다. 이 메커니즘은 전역 변수 또는 객체 속성을 별칭하는 슬롯을 지역 변수 배열에 생성하는 두 개의 새 옵코드 TRACK_OBJECT와 UNTRACK_OBJECT를 사용합니다. 별칭되고 있는 객체가 재바인딩되면, 재바인딩 연산이 별칭들을 갱신할 책임을 집니다.
객체 추적 방식은 모듈뿐만 아니라 더 넓은 범위의 객체에 적용됩니다. 또한 이 방식은 실행 시점 비용이 더 높을 수 있는데, 전역 변수나 객체 속성을 사용하는 각 함수는 객체에 대한 관심을 등록하기 위해 추가 옵코드를 실행해야 하고 종료 시 등록을 해제해야 하기 때문입니다. 등록 비용은 명확하지 않지만, 아마도 콜백 목록을 보관하기 위한 동적으로 크기 조정 가능한 자료 구조가 필요할 것입니다.
여기서 제안하는 구현은 별칭을 생성하지 않기 때문에 등록이 필요하지 않습니다. 대신 전역 변수나 모듈 속성을 참조하는 함수가 원래 바인딩이 저장된 위치에 대한 포인터를 유지할 수 있도록 합니다. 두 번째 이점은 초기 조회가 함수 호출마다가 아니라 모듈마다 한 번씩만 수행된다는 것입니다.
Copyright
This document has been placed in the public domain.