PEP 363 – 동적 속성 접근을 위한 구문
- Author:
- Ben North <ben at redfrontdoor.org>
- Status:
- Rejected
- Type:
- Standards Track
- Created:
- 29-Jan-2007
- Post-History:
- 12-Feb-2007
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
현재 “getattr” 및 “setattr” 내장 함수를 사용하여 동적으로 속성에 접근할 수 있습니다. 이 PEP에서는 이러한 접근을 더 쉽게 만드는 새로운 구문을 제안하며, 예를 들어 코더가 다음과 같이 작성할 수 있도록 합니다.:
x.('foo_%d' % n) += 1
z = y.('foo_%d' % n).('bar_%s' % s)
다음 대신에:
attr_name = 'foo_%d' % n
setattr(x, attr_name, getattr(x, attr_name) + 1)
z = getattr(getattr(y, 'foo_%d' % n), 'bar_%s' % s)
근거
딕셔너리 접근과 인덱싱에는 모두 편리한 호출 구문이 있습니다. 즉, x.__getitem__(12) 대신 코더는 x[12]라고 작성할 수 있습니다. 또한 이를 통해 “x[12] += 1”과 같이 확장 할당에서 첨자 표기 요소를 사용할 수 있습니다. 현재 제안은 이러한 편리함을 동적 속성 접근에도 제공합니다.
현재 속성 접근은 다음 두 가지 방법으로 가능합니다.
- 코드를 작성할 때 속성 이름을 알고 있는 경우에는 “.NAME” 트레일러를 사용할 수 있습니다. 예를 들면 다음과 같습니다.:
x.foo = 42 y.bar += 100
- 속성 이름이 실행 시간에 동적으로 계산되는 경우에는 “getattr” 및 “setattr” 내장 함수를 사용해야 합니다.:
x = getattr(y, 'foo_%d' % n) setattr(z, 'bar_%s' % s, 99)
또한 “getattr” 내장 함수를 사용하면 객체에 지정된 이름의 속성이 없는 경우 반환할 기본값을 코더가 지정할 수 있습니다.:
x = getattr(y, 'foo_%d' % n, 0)
이 PEP에서는 위의 초록에 예시로 제시한 “x.(expr)”이라는 동적 속성 접근을 위한 새로운 구문을 설명합니다.
(새로운 구문을 사용하면 “get”의 경우에도 다음과 같이 기본값을 제공할 수 있습니다.:
x = y.('foo_%d' % n, None)
이 동적 속성 접근의 2인자 형식은 (확장 또는 일반) 할당의 대상으로 사용할 수 없습니다. 아래의 “Discussion” 절에는 이 2인자 확장에 관한 의견이 구체적으로 포함되어 있습니다.)
마지막으로 새로운 구문은 다음과 같이 “del” 문과 함께 사용할 수 있습니다.:
del x.(attr_name)
기존 코드에 미치는 영향
제안된 새로운 구문은 현재 유효하지 않으므로, 이 제안으로 인해 기존의 올바르게 형성된 프로그램의 의미가 변경되지는 않습니다.
2.5 배포판의 모든 “*.py” 파일에서 “getattr”, “setattr” 또는 “delattr”의 사용 사례는 약 600개입니다. 이들은 다음과 같이 분류됩니다. (부분적으로 수작업으로 검사하여 산출한 수치이므로 어느 정도 오차가 있을 수 있습니다.):
c.300 uses of plain "getattr(x, attr_name)", which could be
replaced with the new syntax;
c.150 uses of the 3-argument form, i.e., with the default
value; these could be replaced with the 2-argument form
of the new syntax (the cases break down into c.125 cases
where the attribute name is a literal string, and c.25
where it's only known at run-time);
c.5 uses of the 2-argument form with a literal string
attribute name, which I think could be replaced with the
standard "x.attribute" syntax;
c.120 uses of setattr, of which 15 use getattr to find the
new value; all could be replaced with the new syntax,
the 15 where getattr is also involved would show a
particular increase in clarity;
c.5 uses which would have to stay as "getattr" because they
are calls of a variable named "getattr" whose default
value is the builtin "getattr";
c.5 uses of the 2-argument form, inside a try/except block
which catches AttributeError and uses a default value
instead; these could use 2-argument form of the new
syntax;
c.10 uses of "delattr", which could use the new syntax.
예를 들어 다음 행은:
setattr(self, attr, change_root(self.root, getattr(self, attr)))
Lib/distutils/command/install.py의 다음과 같은 코드로 다시 작성할 수 있습니다.:
self.(attr) = change_root(self.root, self.(attr))
그리고 다음 행은:
setattr(self, method_name, getattr(self.metadata, method_name))
Lib/distutils/dist.py의 다음과 같은 코드로 다시 작성할 수 있습니다.:
self.(method_name) = self.metadata.(method_name)
성능에 미치는 영향
초기 pystone 측정 결과는 결론을 내리기 어렵지만, 패치된 버전에서는 pystones 점수가 약 1% 정도 하락할 수 있음을 시사합니다. 한 가지 추측은 ceval.c의 더 긴 메인 루프가 캐시 동작에 악영향을 주기 때문이라는 것이지만, 이는 아직 확인되지 않았습니다.
반면 측정 결과는 동적 속성 접근에서 약 40–45%의 속도 향상이 있음을 시사합니다.
오류 사례
속성 이름으로는 문자열만 허용되므로, 예를 들어 다음과 같은 오류가 발생합니다:
>>> x.(99) = 8
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: attribute name must be string, not 'int'
이는 기존의 PyObject_GetAttr 함수로 처리됩니다.
초안 구현
초안 구현은 Grammar/Grammar의 “trailer” 절에 새로운 대안을 추가하고, Python.asdl에 “DynamicAttribute”라는 새로운 AST 타입을 추가하며, symtable.c, ast.c, compile.c에 그에 따른 변경을 가하고, 세 개의 새로운 opcode(load/store/del)와 함께 opcode.h 및 ceval.c에 그에 따른 변경을 가합니다. 이 패치는 핵심 코드에 약 180줄이 추가되고, 테스트에 약 100줄이 추가되는 것으로 구성됩니다. sourceforge 패치 #1657573 [1]로 제공됩니다.
메일링 리스트 논의
이 PEP의 초안 형태 초기 게시물은 20070209에 python-ideas에 올라왔으며 [2], 반응은 대체로 긍정적이었습니다. 이후 이 PEP는 20070212에 python-dev에 게시되었고 [3], 흥미로운 논의가 이어졌습니다. 간단히 요약하면 다음과 같습니다.
초기에는 이 아이디어에 대해 합리적인(하지만 만장일치는 아닌) 지지가 있었으나, 정확한 문법 선택에 대해서는 더 엇갈린 반응이 있었습니다. 여러 사람들이 “.”이 너무 쉽게 간과될 수 있어, 그 결과 이 문법이 메소드/함수 호출과 혼동될 수 있다고 생각했습니다. 몇 가지 대안 문법이 제안되었으며:
obj.(foo)
obj.[foo]
obj.{foo}
obj{foo}
obj.*foo
obj->foo
obj<-foo
obj@[foo]
obj.[[foo]]
“obj.[foo]”가 선호되는 것으로 부상했습니다. 이 초기 논의에서 두 인자 형태는 만장일치로 반감을 샀으므로, PEP에서 제외되기로 했습니다.
이후 논의는 이 특정 기능이 새로운 구문을 정당화할 만큼 충분한 이점을 제공하는지에 대한 원점으로 돌아갔습니다. 코더들이 새 구문에 익숙해져야 한다는 점 외에도, 하위 호환성 문제 또한 있었습니다 — 새 구문을 사용하는 코드는 이전 버전의 파이썬에서 실행되지 않을 것이었습니다.
새 구문 대신, Martin von Löwis가 다음과 같은 명세 / 개념적 구현을 제안한 새로운 “래퍼 클래스”가 제안되었습니다:
class attrs:
def __init__(self, obj):
self.obj = obj
def __getitem__(self, name):
return getattr(self.obj, name)
def __setitem__(self, name, value):
return setattr(self.obj, name, value)
def __delitem__(self, name):
return delattr(self, name)
def __contains__(self, name):
return hasattr(self, name)
이는 원래 문제에 대한 더 깔끔하고 우아한 해결책으로 여겨졌습니다. (또 다른 제안은 객체의 속성에 딕셔너리 방식 접근을 제공하는 믹스인 클래스였습니다.)
논의 초반부터 일부에서 제기해 온 견해대로, 현재 PEP가 새 구문 도입의 입증 책임을 충족하지 못한다는 결정이 내려졌습니다. 래퍼 클래스 아이디어는 향후 PEP를 위한 가능성으로 열어두었습니다.
참고 자료
Copyright
This document has been placed in the public domain.