PEP 367 – 새로운 Super
- Author:
- Calvin Spealman <ironfroggy at gmail.com>, Tim Delaney <timothy.c.delaney at gmail.com>
- Status:
- Superseded
- Type:
- Standards Track
- Created:
- 28-Apr-2007
- Python-Version:
- 2.6
- Post-History:
- 28-Apr-2007, 29-Apr-2007, 29-Apr-2007, 14-May-2007
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
번호에 관한 참고
이 PEP의 번호는 PEP 3135로 변경되었습니다. 아래 내용은 이전 번호로 제출된 마지막 버전입니다.
초록
이 PEP는 super 타입의 사용을 위한 문법적 설탕(syntactic sugar)을 제안합니다. 이를 통해 메서드가 정의된 클래스와 메서드가 현재 작용하고 있는 인스턴스(클래스 메서드의 경우 클래스 객체)에 바인딩되는 super 타입의 인스턴스를 자동으로 생성합니다.
제안하는 새로운 super 사용법의 전제는 다음과 같습니다.:
super.foo(1, 2)
이는 기존의 다음 방식을 대체합니다.:
super(Foo, self).foo(1, 2)
그리고 현재의 __builtin__.super에 __builtin__.__super__라는 별칭을 부여합니다(__builtin__.super는 Python 3.0에서 제거됩니다).
또한 None의 동작과 마찬가지로 super에 대한 대입이 SyntaxError가 되도록 할 것을 제안합니다.
근거
현재 super를 사용하려면 super가 동작할 기준이 되는 클래스와 인스턴스를 모두 명시적으로 전달해야 하므로, DRY(Don’t Repeat Yourself) 원칙을 어기게 됩니다. 이는 클래스 이름을 변경하기 어렵게 만들며, 많은 사람이 이를 흠으로 여기곤 합니다.
명세
명세 절에서는 유사하고 밀접하게 관련된 개념을 구별하기 위해 몇 가지 특별한 용어를 사용합니다. “super 타입”은 “super”라는 이름의 실제 내장 타입을 가리킵니다. “super 인스턴스”는 단순히 super 타입의 인스턴스로, 하나의 클래스와 연관되며 경우에 따라 그 클래스의 인스턴스와도 연관됩니다.
새 super 의미는 Python 2.5와 하위 호환되지 않으므로, 새 의미를 사용하려면 __future__ 임포트가 필요합니다:
from __future__ import new_super
현재의 __builtin__.super에는 __builtin__.__super__라는 별칭이 부여됩니다. 이는 새 super 의미가 활성화되었는지 여부와 관계없이 일어납니다. __builtin__.super의 이름을 단순히 바꿀 수는 없습니다. 그렇게 하면 새 super 의미를 사용하지 않는 모듈에도 영향을 주기 때문입니다. Python 3.0에서는 __builtin__.super라는 이름을 제거할 것을 제안합니다.
기존의 super 사용법을 대체하여, super 인스턴스를 명시적으로 생성하지 않고도 MRO(메서드 결정 순서)상의 다음 클래스를 호출할 수 있습니다(명시적으로 생성하는 방식도 __super__를 통해 계속 지원됩니다). 모든 함수는 super라는 이름의 암묵적인 지역 변수를 가집니다. 이 이름은 셀을 통해 내부 함수에서 사용되는 경우를 포함하여 일반 지역 변수와 똑같이 동작하지만, 다음과 같은 예외가 있습니다:
super라는 이름에 대입하면 컴파일 시점에SyntaxError가 발생합니다;super이름에 접근하는 정적 메서드나 일반 함수를 호출하면 런타임에TypeError가 발생합니다.
super라는 이름을 사용하거나, super라는 이름을 사용하는 내부 함수를 가진 모든 함수에는 다음과 동등한 작업을 수행하는 프리앰블이 포함됩니다.:
super = __builtin__.__super__(<class>, <instance>)
여기서 <class>는 메서드가 정의된 클래스이고, <instance>는 메서드의 첫 번째 매개변수입니다(인스턴스 메서드에서는 보통 self, 클래스 메서드에서는 보통 cls입니다). 정적 메서드와 일반 함수의 경우 <class>는 None이 되므로, 프리앰블 실행 중에 TypeError가 발생합니다.
참고: super와 __super__의 관계는 import와 __import__의 관계와 비슷합니다.
이 내용의 상당 부분은 python-dev 메일링 리스트의 “Fixing super anyone?” [1] 스레드에서 논의되었습니다.
미해결 문제
사용할 클래스 객체 결정하기
메서드를 그 메서드를 정의한 클래스와 연결하는 정확한 메커니즘은 이 PEP에서 명시하지 않으며, 성능이 가장 좋은 방식으로 선택해야 합니다. CPython의 경우, 클래스 인스턴스를 함수 객체의 C 수준 변수에 보관하고, 이 변수를 NULL (클래스의 일부가 아님), Py_None (정적 메서드), 클래스 객체(인스턴스 메서드 또는 클래스 메서드) 중 하나에 바인딩하는 방식을 제안합니다.
super가 실제로 키워드가 되어야 합니까?
이 제안에 따르면 super는 None이 키워드인 것과 같은 정도로 키워드가 됩니다. super 이름을 더 제한하면 구현이 단순해질 수도 있지만, super를 실제 키워드로 만드는 데 반대하는 사람들도 있습니다. 가장 단순한 해결책이 올바른 해결책인 경우가 많으며, 필요하지 않은 키워드를 언어에 추가하지 않는 것이 가장 단순한 해결책일 수도 있습니다. 그렇더라도 키워드로 만들면 아직 해결되지 않은 다른 문제들이 해결될 수도 있습니다.
해결된 문제
__call__ 속성과 함께 사용되는 super
super 인스턴스를 고전적인 방식으로 인스턴스화하면 문제가 될 수 있다는 의견이 있었습니다. 인스턴스를 호출하면 __call__ 속성을 조회하게 되고, 그에 따라 MRO의 다음 클래스로 자동 super 조회를 시도하게 된다는 이유였습니다. 그러나 이는 사실이 아닌 것으로 밝혀졌습니다. 객체를 호출할 때는 객체의 타입에서 직접 __call__ 메서드만 조회하기 때문입니다. 다음 예제에서 실제로 어떻게 동작하는지 볼 수 있습니다.
class A(object):
def __call__(self):
return '__call__'
def __getattribute__(self, attr):
if attr == '__call__':
return lambda: '__getattribute__'
a = A()
assert a() == '__call__'
assert a.__call__() == '__getattribute__'
어쨌든 __builtin__.super의 이름을 __builtin__.__super__로 바꾸면 이 문제는 완전히 사라집니다.
참조 구현
위 명세를 순수 Python만으로 모두 구현하는 것은 불가능합니다. 이 참조 구현은 명세와 다음과 같은 차이가 있습니다:
- 새
super의미는 바이트코드 해킹으로 구현됩니다. super에 대입해도SyntaxError가 발생하지 않습니다. 4번 항목도 참고하십시오.- 새
super의미를 사용하려면 클래스는 메타클래스autosuper_meta를 사용하거나 베이스 클래스autosuper를 상속해야 합니다. super는 암묵적 지역 변수가 아닙니다. 특히 내부 함수가 super 인스턴스를 사용할 수 있으려면 메서드 안에super = super형태의 대입문이 있어야 합니다.
참조 구현은 Python 2.5 이상에서 실행된다고 가정합니다.
#!/usr/bin/env python
#
# autosuper.py
from array import array
import dis
import new
import types
import __builtin__
__builtin__.__super__ = __builtin__.super
del __builtin__.super
# We need these for modifying bytecode
from opcode import opmap, HAVE_ARGUMENT, EXTENDED_ARG
LOAD_GLOBAL = opmap['LOAD_GLOBAL']
LOAD_NAME = opmap['LOAD_NAME']
LOAD_CONST = opmap['LOAD_CONST']
LOAD_FAST = opmap['LOAD_FAST']
LOAD_ATTR = opmap['LOAD_ATTR']
STORE_FAST = opmap['STORE_FAST']
LOAD_DEREF = opmap['LOAD_DEREF']
STORE_DEREF = opmap['STORE_DEREF']
CALL_FUNCTION = opmap['CALL_FUNCTION']
STORE_GLOBAL = opmap['STORE_GLOBAL']
DUP_TOP = opmap['DUP_TOP']
POP_TOP = opmap['POP_TOP']
NOP = opmap['NOP']
JUMP_FORWARD = opmap['JUMP_FORWARD']
ABSOLUTE_TARGET = dis.hasjabs
def _oparg(code, opcode_pos):
return code[opcode_pos+1] + (code[opcode_pos+2] << 8)
def _bind_autosuper(func, cls):
co = func.func_code
name = func.func_name
newcode = array('B', co.co_code)
codelen = len(newcode)
newconsts = list(co.co_consts)
newvarnames = list(co.co_varnames)
# Check if the global 'super' keyword is already present
try:
sn_pos = list(co.co_names).index('super')
except ValueError:
sn_pos = None
# Check if the varname 'super' keyword is already present
try:
sv_pos = newvarnames.index('super')
except ValueError:
sv_pos = None
# Check if the cellvar 'super' keyword is already present
try:
sc_pos = list(co.co_cellvars).index('super')
except ValueError:
sc_pos = None
# If 'super' isn't used anywhere in the function, we don't have anything to do
if sn_pos is None and sv_pos is None and sc_pos is None:
return func
c_pos = None
s_pos = None
n_pos = None
# Check if the 'cls_name' and 'super' objects are already in the constants
for pos, o in enumerate(newconsts):
if o is cls:
c_pos = pos
if o is __super__:
s_pos = pos
if o == name:
n_pos = pos
# Add in any missing objects to constants and varnames
if c_pos is None:
c_pos = len(newconsts)
newconsts.append(cls)
if n_pos is None:
n_pos = len(newconsts)
newconsts.append(name)
if s_pos is None:
s_pos = len(newconsts)
newconsts.append(__super__)
if sv_pos is None:
sv_pos = len(newvarnames)
newvarnames.append('super')
# This goes at the start of the function. It is:
#
# super = __super__(cls, self)
#
# If 'super' is a cell variable, we store to both the
# local and cell variables (i.e. STORE_FAST and STORE_DEREF).
#
preamble = [
LOAD_CONST, s_pos & 0xFF, s_pos >> 8,
LOAD_CONST, c_pos & 0xFF, c_pos >> 8,
LOAD_FAST, 0, 0,
CALL_FUNCTION, 2, 0,
]
if sc_pos is None:
# 'super' is not a cell variable - we can just use the local variable
preamble += [
STORE_FAST, sv_pos & 0xFF, sv_pos >> 8,
]
else:
# If 'super' is a cell variable, we need to handle LOAD_DEREF.
preamble += [
DUP_TOP,
STORE_FAST, sv_pos & 0xFF, sv_pos >> 8,
STORE_DEREF, sc_pos & 0xFF, sc_pos >> 8,
]
preamble = array('B', preamble)
# Bytecode for loading the local 'super' variable.
load_super = array('B', [
LOAD_FAST, sv_pos & 0xFF, sv_pos >> 8,
])
preamble_len = len(preamble)
need_preamble = False
i = 0
while i < codelen:
opcode = newcode[i]
need_load = False
remove_store = False
if opcode == EXTENDED_ARG:
raise TypeError("Cannot use 'super' in function with EXTENDED_ARG opcode")
# If the opcode is an absolute target it needs to be adjusted
# to take into account the preamble.
elif opcode in ABSOLUTE_TARGET:
oparg = _oparg(newcode, i) + preamble_len
newcode[i+1] = oparg & 0xFF
newcode[i+2] = oparg >> 8
# If LOAD_GLOBAL(super) or LOAD_NAME(super) then we want to change it into
# LOAD_FAST(super)
elif (opcode == LOAD_GLOBAL or opcode == LOAD_NAME) and _oparg(newcode, i) == sn_pos:
need_preamble = need_load = True
# If LOAD_FAST(super) then we just need to add the preamble
elif opcode == LOAD_FAST and _oparg(newcode, i) == sv_pos:
need_preamble = need_load = True
# If LOAD_DEREF(super) then we change it into LOAD_FAST(super) because
# it's slightly faster.
elif opcode == LOAD_DEREF and _oparg(newcode, i) == sc_pos:
need_preamble = need_load = True
if need_load:
newcode[i:i+3] = load_super
i += 1
if opcode >= HAVE_ARGUMENT:
i += 2
# No changes needed - get out.
if not need_preamble:
return func
# Our preamble will have 3 things on the stack
co_stacksize = max(3, co.co_stacksize)
# Conceptually, our preamble is on the `def` line.
co_lnotab = array('B', co.co_lnotab)
if co_lnotab:
co_lnotab[0] += preamble_len
co_lnotab = co_lnotab.tostring()
# Our code consists of the preamble and the modified code.
codestr = (preamble + newcode).tostring()
codeobj = new.code(co.co_argcount, len(newvarnames), co_stacksize,
co.co_flags, codestr, tuple(newconsts), co.co_names,
tuple(newvarnames), co.co_filename, co.co_name,
co.co_firstlineno, co_lnotab, co.co_freevars,
co.co_cellvars)
func.func_code = codeobj
func.func_class = cls
return func
class autosuper_meta(type):
def __init__(cls, name, bases, clsdict):
UnboundMethodType = types.UnboundMethodType
for v in vars(cls):
o = getattr(cls, v)
if isinstance(o, UnboundMethodType):
_bind_autosuper(o.im_func, cls)
class autosuper(object):
__metaclass__ = autosuper_meta
if __name__ == '__main__':
class A(autosuper):
def f(self):
return 'A'
class B(A):
def f(self):
return 'B' + super.f()
class C(A):
def f(self):
def inner():
return 'C' + super.f()
# Needed to put 'super' into a cell
super = super
return inner()
class D(B, C):
def f(self, arg=None):
var = None
return 'D' + super.f()
assert D().f() == 'DBCA'
B.f와 C.f를 역어셈블해 보면 super가 단순한 지역 변수일 때와 내부 함수에서 사용될 때 서로 다른 프리앰블이 사용된다는 것을 알 수 있습니다.
>>> dis.dis(B.f)
214 0 LOAD_CONST 4 (<type 'super'>)
3 LOAD_CONST 2 (<class '__main__.B'>)
6 LOAD_FAST 0 (self)
9 CALL_FUNCTION 2
12 STORE_FAST 1 (super)
215 15 LOAD_CONST 1 ('B')
18 LOAD_FAST 1 (super)
21 LOAD_ATTR 1 (f)
24 CALL_FUNCTION 0
27 BINARY_ADD
28 RETURN_VALUE
>>> dis.dis(C.f)
218 0 LOAD_CONST 4 (<type 'super'>)
3 LOAD_CONST 2 (<class '__main__.C'>)
6 LOAD_FAST 0 (self)
9 CALL_FUNCTION 2
12 DUP_TOP
13 STORE_FAST 1 (super)
16 STORE_DEREF 0 (super)
219 19 LOAD_CLOSURE 0 (super)
22 LOAD_CONST 1 (<code object inner at 00C160A0, file "autosuper.py", line 219>)
25 MAKE_CLOSURE 0
28 STORE_FAST 2 (inner)
223 31 LOAD_FAST 1 (super)
34 STORE_DEREF 0 (super)
224 37 LOAD_FAST 2 (inner)
40 CALL_FUNCTION 0
43 RETURN_VALUE
최종 구현에서는 프리앰블이 메서드 바이트코드에 포함되지 않고, 매개변수 언패킹 직후에 실행된다는 점에 유의하십시오.
대안 제안
변경하지 않음
모든 것을 그대로 두는 방안은 언제나 매력적이지만, 사람들은 한동안 super 호출 방식의 변경을 요구해 왔으며, 앞서 모두 언급한 것처럼 그럴 만한 이유가 있습니다.
- 클래스 이름으로부터의 분리(그 이름이 더 이상 올바른 클래스에 바인딩되어 있지 않을 수도 있습니다!)
- 더 단순해 보이고 깔끔한 super 호출이 더 낫습니다
super 타입의 동적 속성
이 제안은 super 타입에 동적 속성 조회를 추가합니다. 이 조회는 올바른 클래스 매개변수와 인스턴스 매개변수를 자동으로 결정합니다. 각 super 속성 조회는 이 매개변수들을 식별하고 인스턴스에 대해 super 조회를 수행합니다. 이는 현재 super 구현에서 클래스와 인스턴스를 대상으로 super 인스턴스를 명시적으로 호출하는 방식과 같습니다.
이 제안은 sys._getframe()에 의존하는데, 이 함수는 프로토타입 구현 외에는 적합하지 않습니다.
super(__this_class__, self)
이것은 거의 반(反)제안에 가깝습니다. 기본적으로 __this_class__ PEP가 채택되어야 성립하기 때문입니다. 이 PEP는 그 이름이 사용되는 클래스에 항상 바인딩되는 특수한 이름을 제안합니다. 그 PEP가 채택되면 클래스 이름을 명시적으로 쓰는 대신 간단히 __this_class__를 사용할 수 있으므로 이름 바인딩 문제가 해결됩니다 [2].
self.__super__.foo(*args)
__super__ 속성은 이 PEP의 여러 곳에서 언급됩니다. super를 직접 사용하는 대신 이 속성을 실제로 명시적으로 사용한다면 완전한 해결책의 후보가 될 수 있습니다. 그러나 이중 밑줄 이름은 보통 내부 세부 사항으로 여겨지며, 일상적인 코드에는 되도록 쓰지 않습니다.
super(self, *args) 또는 __super__(self, *args)
이 해결책은 타입을 지정하는 문제만 해결합니다. 이름이 다른 super 메서드는 처리하지 못하며, 인스턴스의 이름도 명시적으로 드러냅니다. 다른 메서드 이름에 적용해야 하는 경우에도 그렇게 할 수 없으므로 유연성이 떨어집니다. 이 방식이 실패하는 사용 사례가 하나 있습니다. 베이스 클래스에 팩토리 클래스 메서드가 하나 있고 서브클래스에 팩토리 클래스 메서드가 두 개 있는데, 두 메서드 모두 베이스 클래스의 그 메서드를 super로 올바르게 호출해야 하는 경우입니다.
super.foo(self, *args)
이 변형을 쓰면 올바른 인스턴스를 찾는 문제가 실제로 사라집니다. 대안 중 하나가 주목받게 된다면 저는 이 방식이기를 바랍니다.
super 또는 super()
이 제안에서는 다른 이름이나 시그니처를 쓸 수 없고, 다른 클래스나 인스턴스에 적용할 여지도 전혀 없습니다. 일반적인 제안과 함께 이와 비슷한 사용을 허용하는 방법이 있다면 바람직할 것입니다. 그러면 다중 상속 트리와 호환 가능한 메서드를 잘 설계하도록 장려할 수 있습니다.
super(*p, **kw)
super(*p, **kw)를 직접 호출하면, 현재 실행 중인 메서드와 이름이 같은 메서드를 super 객체에서 호출하는 것과 동등하게 하자는 제안이 있었습니다. 즉, 다음 두 메서드는 동등합니다:
def f(self, *p, **kw):
super.f(*p, **kw)
def f(self, *p, **kw):
super(*p, **kw)
이에 대해서는 찬성과 반대 의견이 모두 강하지만, 구현과 스타일 면의 우려는 분명합니다. Guido는 KISS(Keep It Simple Stupid) 원칙에 따라 이것을 이 PEP에서 제외해야 한다고 제안했습니다.
역사
- 2007년 4월 29일 - 제목을 “키워드로서의 Super”에서 “새로운 Super”로 변경했습니다.
- 문구의 상당 부분을 수정했으며, 헷갈리기 쉬운 부분을 명확히 하기 위해 용어 섹션을 추가했습니다.
- 참조 구현 섹션과 이력 섹션을 추가했습니다.
- 2007년 5월 6일 - Tim Delaney가 python-3000
- 및 python-dev 메일링 리스트의 논의를 반영하여 갱신했습니다.
참고 문헌
Copyright
This document has been placed in the public domain.