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타입을 사용하여 메서드가 정의된 클래스와 메서드가 현재 동작 중인 인스턴스(또는 클래스 메서드의 경우 클래스 객체)에 바인딩된 super 타입의 인스턴스를 자동으로 생성하는 문법적 편의를 제안합니다.
제안된 새로운 super 사용법의 전제는 다음과 같습니다.:
super.foo(1, 2)
기존 사용법을 대체하여:
super(Foo, self).foo(1, 2)
현재의 __builtin__.super를 __builtin__.__super__에 별칭으로 지정합니다(__builtin__.super는 Python 3.0에서 제거됩니다).
또한 super에 대한 할당이 None의 동작과 유사하게 SyntaxError가 되도록 제안합니다.
근거
현재 super 사용법에서는 super가 동작해야 하는 클래스와 인스턴스를 모두 명시적으로 전달해야 하므로, DRY(스스로 반복하지 않기) 규칙을 위반해야 합니다. 이로 인해 클래스 이름을 변경하기 어려워지며, 많은 사람이 이를 흔히 결함으로 간주합니다.
사양
사양 절에서는 유사하거나 밀접하게 관련된 개념을 구별하기 위해 몇 가지 특수 용어를 사용합니다. “super type”은 “super”라는 이름의 실제 내장 타입을 의미합니다. “super instance”는 단순히 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의 경우, 클래스 인스턴스를 NULL(클래스의 일부가 아님), Py_None(정적 메서드) 또는 클래스 객체(인스턴스 메서드 또는 클래스 메서드) 중 하나에 바인딩된 함수 객체의 C 수준 변수에 보관하도록 제안합니다.
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는 사용되는 클래스에 항상 바인딩되는 특수한 이름을 제안합니다. 이것이 받아들여지면 __this_class__는 클래스 이름을 명시적으로 사용하는 대신 간단히 사용할 수 있으며, 이름 바인딩 문제 [2]를 해결합니다.
self.__super__.foo(*args)
__super__ 속성은 이 PEP의 여러 곳에서 언급되며, 실제로 어떤 super 사용도 직접 수행하는 대신 이를 명시적으로 사용하는 완전한 해결책의 후보가 될 수 있습니다. 그러나 이중 밑줄 이름은 일반적으로 내부 세부 사항이며, 일상적인 코드에서는 사용하지 않도록 하는 것이 바람직합니다.
super(self, *args) 또는 __super__(self, *args)
이 해결책은 타입 표시 문제만 해결하며, 이름이 서로 다른 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 As A Keyword”에서 “New Super”로 변경했습니다.
- 혼동하기 쉬운 부분을 명확히 하기 위해 언어의 많은 부분을 수정하고 용어 섹션을 추가했습니다.
- 참조 구현 및 역사 섹션을 추가했습니다.
- 2007년 5월 6일 - python-3000
- 및 python-dev 메일링 리스트에서의 논의를 반영하도록 Tim Delaney가 업데이트했습니다.
참고 자료
Copyright
This document has been placed in the public domain.