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인자 형식의 동적 속성 접근은 (복합 또는 일반) 할당의 대상으로는 허용되지 않습니다. 아래의 “토론” 절에는 특히 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.
예를 들어, Lib/distutils/command/install.py에 있는 다음 줄은:
setattr(self, attr, change_root(self.root, getattr(self, attr)))
다음과 같이 다시 작성할 수 있습니다.:
self.(attr) = change_root(self.root, self.(attr))
그리고 Lib/distutils/dist.py에 있는 다음 줄은:
setattr(self, method_name, getattr(self.metadata, method_name))
다음과 같이 다시 작성할 수 있습니다.:
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에 새로운 AST 타입인 “DynamicAttribute”를 추가하며 이에 따라 symtable.c, ast.c, compile.c를 변경하고, 세 가지 새로운 옵코드(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에서 제외하기로 했습니다.
이어서 논의는 한발 물러나, 이 특정 기능이 새로운 문법을 정당화할 만큼 충분한 이점을 제공하는지에 대한 문제로 돌아갔습니다. 코드 작성자가 새 문법에 익숙해져야 한다는 점 외에도 하위 호환성 문제도 있습니다. 새 문법을 사용하는 코드는 이전 버전의 Python에서 실행되지 않습니다.
새로운 문법 대신 새로운 “래퍼 클래스”가 제안되었으며, 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.