Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 359 – “make” 문

Author:
Steven Bethard <steven.bethard at gmail.com>
Status:
Withdrawn
Type:
Standards Track
Created:
05-Apr-2006
Python-Version:
2.6
Post-History:
05-Apr-2006, 06-Apr-2006, 13-Apr-2006

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP는 클래스 선언 구문의 일반화인 make 문을 제안합니다. 제안된 구문과 의미는 클래스 정의 구문과 평행을 이루므로:

make <callable> <name> <tuple>:
    <block>

다음과 같은 할당문으로 변환됩니다:

<name> = <callable>("<name>", <tuple>, <namespace>)

여기서 <namespace><block>을 실행하여 생성된 딕셔너리입니다. 이는 대체로 다음과 같은 구문 설탕이며:

class <name> <tuple>:
    __metaclass__ = <callable>
    <block>

클래스 이외의 것이 생성될 때 해당 문의 의도를 더 명확하게 표현하는 데 도움이 되도록 고안되었습니다. 물론 이러한 문에는 다른 구문도 가능하지만, 클래스 문과 강한 평행 관계를 유지하면 클래스와 메타클래스의 작동 방식에 대한 이해가 make 문의 작동 방식에 대한 이해로도 이어지기를 기대합니다.

이 PEP는 python-dev 목록에서 Michele Simionato가 제안한 내용 [1]을 바탕으로 합니다.

철회 공지

이 PEP는 Guido의 요청 [2]에 따라 철회되었습니다. Guido는 이를 마음에 들어 하지 않았으며, 특히 속성 사용 사례에서 속성의 인스턴스 메서드가 다른 인스턴스 메서드와 다른 수준에 배치되고 속성 함수에 고정된 이름이 필요하다는 점을 마음에 들어 하지 않았습니다.

동기

클래스 문은 Python에 다음과 같은 두 가지 유용한 기능을 제공합니다:

  1. 문 블록을 실행하고 그 결과로 생성된 바인딩을 딕셔너리로 메타클래스에 제공합니다.
  2. 생성 중인 클래스가 자신이 할당될 이름을 알 수 있게 함으로써 DRY(반복하지 않기)를 장려합니다.

따라서 다음과 같은 간단한 클래스 문에서는:

class C(object):
    x = 1
    def foo(self):
        return 'bar'

메타클래스(type)가 다음과 같은 방식으로 호출됩니다:

C = type('C', (object,), {'x':1, 'foo':<function foo at ...>})

클래스 문은 위 할당문의 구문 설탕에 불과하지만, 분명 매우 유용한 종류의 구문 설탕입니다. 이는 C를 반복하는 것뿐만 아니라, 일련의 문으로 표현할 수 있게 하여 딕셔너리 생성을 단순화합니다.

역사적으로 타입 인스턴스(즉, 클래스 객체)는 이러한 종류의 구문 지원을 제공받은 유일한 객체였습니다. make 문은 이러한 구문이 유용할 수 있는 다른 종류의 객체로 이 지원을 확장하는 것을 목표로 합니다.

예: 단순 네임스페이스

모듈에 다음과 같이 접근하는 일부 속성이 있다고 하겠습니다:

mod.thematic_roletype
mod.opinion_roletype

mod.text_format
mod.html_format

그리고 “네임스페이스는 정말 대단한 아이디어”이므로, 대신 다음과 같이 이러한 속성에 접근할 수 있기를 바랍니다:

mod.roletypes.thematic
mod.roletypes.opinion

mod.format.text
mod.format.html

현재 두 가지 주요 선택지가 있습니다:

  1. 모듈을 패키지로 바꾸고, roletypesformat을 하위 모듈로 만든 다음, 속성을 하위 모듈로 옮깁니다.
  2. roletypesformat클래스를 만들고, 속성을 해당 클래스들로 옮깁니다.

전자는 상당한 리팩터링 작업이 필요하며, 내용이 많지 않은 작은 모듈 두 개를 만들어 냅니다. 후자는 속성을 모듈에 그대로 두지만, 해당 클래스의 인스턴스를 만들 의도가 전혀 없는데도 클래스를 생성합니다.

이와 같은 상황에서는 몇 가지 속성을 담을 수 있는 “네임스페이스”를 간단히 선언할 수 있으면 좋을 것입니다. 새로운 make 문을 사용하면 다음과 같이 새로운 네임스페이스를 도입할 수 있습니다.:

make namespace roletypes:
    thematic = ...
    opinion = ...

make namespace format:
    text = ...
    html = ...

그리고 결코 인스턴스화할 의도가 없는 클래스를 만들지 않고 속성을 모듈에 로컬로 유지할 수 있습니다. 이를 가능하게 하는 네임스페이스의 정의 중 하나는 다음과 같습니다.:

class namespace(object):
    def __init__(self, name, args, kwargs):
        self.__dict__.update(kwargs)

이 정의에 따르면, 위 make 문이 끝났을 때 roletypesformat은 네임스페이스 인스턴스가 됩니다.

예제: GUI 객체

GUI 툴킷에서는 프레임과 패널 같은 객체가 속성 및 함수와 연결되는 경우가 많습니다. make 문을 사용하면 다음과 비슷한 코드를:

root = Tkinter.Tk()
frame = Tkinter.Frame(root)
frame.pack()
def say_hi():
    print "hi there, everyone!"
hi_there = Tkinter.Button(frame, text="Hello", command=say_hi)
hi_there.pack(side=Tkinter.LEFT)
root.mainloop()

Button의 함수를 해당 선언과 함께 묶도록 다시 작성할 수 있습니다.:

root = Tkinter.Tk()
frame = Tkinter.Frame(root)
frame.pack()
make Tkinter.Button hi_there(frame):
    text = "Hello"
    def command():
        print "hi there, everyone!"
hi_there.pack(side=Tkinter.LEFT)
root.mainloop()

예제: 사용자 정의 디스크립터

디스크립터는 속성에 대한 접근을 사용자 정의하는 데 사용되므로 해당 속성의 이름을 아는 것이 유용한 경우가 많습니다. 현재 Python에서는 이 이름을 쉽게 찾을 방법을 제공하지 않으므로, Ian Bicking의 setonce 디스크립터 [3]와 같은 많은 사용자 정의 디스크립터는 이를 우회하는 편법을 사용해야 합니다. make 문을 사용하면 다음과 같이 setonce속성을 만들 수 있습니다.:

class A(object):
    ...
    make setonce x:
        "A's x attribute"
    ...

여기서 setonce 디스크립터는 다음과 같이 정의됩니다.:

class setonce(object):

    def __init__(self, name, args, kwargs):
        self._name = '_setonce_attr_%s' % name
        self.__doc__ = kwargs.pop('__doc__', None)

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        return getattr(obj, self._name)

    def __set__(self, obj, value):
        try:
            getattr(obj, self._name)
        except AttributeError:
            setattr(obj, self._name, value)
        else:
            raise AttributeError("Attribute already set")

    def set(self, obj, value):
        setattr(obj, self._name, value)

    def __delete__(self, obj):
        delattr(obj, self._name)

원래 구현과 달리 비공개 속성 이름은 디스크립터의 이름을 사용하므로 안정적이며, 따라서 A 클래스의 인스턴스를 피클할 수 있다는 점에 유의하십시오.

예제: 프로퍼티 네임스페이스

Python의 프로퍼티 타입은 함수 인자 세 개와 독스트링 인자를 받는데, 이 인자들은 프로퍼티에만 관련되지만 프로퍼티보다 먼저 선언한 다음 프로퍼티 호출에 인자로 전달해야 합니다. 예를 들면 다음과 같습니다.:

class C(object):
    ...
    def get_x(self):
        ...
    def set_x(self):
        ...
    x = property(get_x, set_x, "the x of the frobulation")

이 문제는 이전에도 제기된 적이 있으며, Guido [4]와 다른 사람들은 프로퍼티를 더 쉽게 선언할 수 있도록 대체 프로퍼티 구문을 잠시 검토한 바 있습니다 [5]. make 문을 사용하면 다음 구문을 지원할 수 있습니다.:

class C(object):
    ...
    make block_property x:
        '''The x of the frobulation'''
        def fget(self):
            ...
        def fset(self):
            ...

다음과 같이 block_property를 정의합니다.:

def block_property(name, args, block_dict):
    fget = block_dict.pop('fget', None)
    fset = block_dict.pop('fset', None)
    fdel = block_dict.pop('fdel', None)
    doc = block_dict.pop('__doc__', None)
    assert not block_dict
    return property(fget, fset, fdel, doc)

예제: 인터페이스

Guido [6]와 다른 사람들은 때때로 Python에 인터페이스를 도입하자고 제안했습니다. 대부분의 제안은 다음과 같은 형태의 구문을 제시했습니다.:

interface IFoo:
    """Foo blah blah"""

    def fumble(name, count):
        """docstring"""

그러나 현재 Python에는 이러한 방식으로 인터페이스를 선언할 방법이 없으므로, Python 인터페이스의 대부분 구현은 대신 클래스 객체를 사용합니다. 예를 들어 Zope의 경우는 다음과 같습니다.:

class IFoo(Interface):
    """Foo blah blah"""

    def fumble(name, count):
        """docstring"""

새로운 make 문을 사용하면 이러한 인터페이스를 대신 다음과 같이 선언할 수 있습니다.:

make Interface IFoo:
    """Foo blah blah"""

    def fumble(name, count):
        """docstring"""

이렇게 하면 이것이 클래스가 아니라 인터페이스라는 의도가 훨씬 더 명확해집니다.

명세

Python은 make 문을:

make <callable> <name> <tuple>:
    <block>

다음 대입문으로 변환합니다.:

<name> = <callable>("<name>", <tuple>, <namespace>)

여기서 <namespace><block>을 실행하여 생성된 딕셔너리입니다. <tuple> 표현식은 선택 사항이며, 지정하지 않으면 빈 튜플로 간주합니다.

이러한 의미 체계를 구현하는 패치를 사용할 수 있습니다 [7].

make 문은 새로운 키워드인 make를 도입합니다. 따라서 Python 2.6에서는 from __future__ import make_statement를 사용하여 make 문을 활성화해야 합니다.

미해결 문제

키워드

make 키워드는 너무 많은 코드를 손상합니까? 원래 make 문은 create 키워드를 사용했습니다(Alyssa Coghlan의 제안입니다). 그러나 표준 라이브러리 [8]와 Zope+Plone 코드 [9]를 조사한 결과 create가 훨씬 더 많은 코드를 손상시키는 것으로 밝혀졌으므로, 대신 make를 키워드로 채택했습니다. 그러나 make가 코드를 손상시키는 경우가 아직 몇 가지 있습니다. 이 문에 더 나은 키워드가 있습니까?

표준 라이브러리(및 설치된 일부 패키지)에서 가능한 키워드와 그 개수는 다음과 같습니다.

  • make - 2(둘 다 테스트에서 사용됨)
  • create - 19(imaplib에 이미 존재하는 함수 포함)
  • build - 83(distutils.command.build에 이미 존재하는 클래스 포함)
  • construct - 0
  • produce - 0

대체 생성자로서의 make 문

현재 (name, args, kwargs) 서명을 가진 함수는 많지 않습니다. 따라서 다음과 같은 것은:

make dict params:
    x = 1
    y = 2

딕셔너리 생성자의 서명이 다르기 때문에 현재 불가능합니다. 이런 종류의 것을 지원해야 합니까? Carl Banks가 제안한 한 가지 방법은, 발견될 경우 __call__대신 호출되는 __make__ 매직 메서드를 추가하는 것입니다. 타입에서는 __make__ 메서드가 __call__과 동일하므로 불필요하지만, 딕셔너리는 딕셔너리 타입에 __make__ 메서드를 정의하여 make 문을 지원할 수 있으며, 그 메서드는 다음과 같은 형태일 수 있습니다.:

def __make__(cls, name, args, kwargs):
    return cls(**kwargs)

물론 또 다른 매직 메서드를 추가하는 대신, 딕셔너리 타입에 dict.fromblock과 같은 클래스 메서드를 추가하여 다음과 같이 사용할 수도 있습니다.:

make dict.fromblock params:
    x = 1
    y = 2

그렇다면 많은 타입이 make 문을 대체 생성자로 사용하려고 할까요? 그렇다면 그 대체 생성자는 원래 생성자와 같은 이름을 가져야 합니까?

블록이 실행되는 딕셔너리 사용자 지정

make 문 사용자는 코드가 어느 딕셔너리 객체에서 실행되는지 결정할 수 있어야 합니까? 이를 통해 일반 딕셔너리 객체로는 충분하지 않은 상황에서 make 문을 사용할 수 있습니다. 예를 들어 순서와 반복되는 이름을 허용해야 하는 경우입니다. 이러한 사용자 지정을 허용하면 요소 이름을 반복하지 않고 XML을 작성할 수 있으며, make 문의 중첩이 XML 요소의 중첩에 대응하도록 할 수 있습니다.:

make Element html:
    make Element body:
        text('before first h1')
        make Element h1:
            attrib(style='first')
            text('first h1')
            tail('after first h1')
        make Element h1:
            attrib(style='second')
            text('second h1')
            tail('after second h1')

make 문이 호출 가능 객체의 __make_dict__ 메서드를 호출하여 블록을 실행할 딕셔너리를 가져오려고 한다면, 다음 코드를 통해 위와 같이 make 문을 사용할 수 있습니다.:

class Element(object):

    class __make_dict__(dict):

        def __init__(self, *args, **kwargs):
            self._super = super(Element.__make_dict__, self)
            self._super.__init__(*args, **kwargs)
            self.elements = []
            self.text = None
            self.tail = None
            self.attrib = {}

        def __getitem__(self, name):
            try:
                return self._super.__getitem__(name)
            except KeyError:
                if name in ['attrib', 'text', 'tail']:
                    return getattr(self, 'set_%s' % name)
                else:
                    return globals()[name]

        def __setitem__(self, name, value):
            self._super.__setitem__(name, value)
            self.elements.append(value)

        def set_attrib(self, **kwargs):
            self.attrib = kwargs

        def set_text(self, text):
            self.text = text

        def set_tail(self, text):
            self.tail = text

    def __new__(cls, name, args, edict):
        get_element = etree.ElementTree.Element
        result = get_element(name, attrib=edict.attrib)
        result.text = edict.text
        result.tail = edict.tail
        for element in edict.elements:
            result.append(element)
        return result

다만 이를 지원하는 코드는 다소 취약하다는 점에 유의해야 합니다 – 네임스페이스를 attrib, texttail로 마법처럼 채워야 하고, make 문 본문 내부의 모든 이름 바인딩이 Element를 생성한다고 가정하기 때문입니다. 현재 상태로는 make 문 본문 중 어느 하나에 단순한 for 루프를 도입하면 이 코드가 중단됩니다. for 루프가 이름을 Element가 아닌 객체에 바인딩하기 때문입니다. 일종의 isinstance 검사나 속성 검사를 추가하여 이 문제를 우회할 수 있지만, 이렇게 해도 다소 취약한 해결책이 됩니다.

또한 with 문을 사용하면 훨씬 더 명시적인 구문으로 동일한 중첩을 제공할 수 있다는 점도 지적되었습니다.:

with Element('html') as html:
    with Element('body') as body:
        body.text = 'before first h1'
        with Element('h1', style='first') as h1:
            h1.text = 'first h1'
            h1.tail = 'after first h1'
        with Element('h1', style='second') as h1:
            h1.text = 'second h1'
            h1.tail = 'after second h1'

여기서 요소 이름을 반복하는 것이 DRY 위반으로 지나치다면, Element에 몇 가지 메서드를 추가하여 첫 번째를 제외한 모든 as 절을 제거할 수도 있습니다. [10]

그렇다면 다른 타입의 딕셔너리에서 블록을 실행하는 실제 사용 사례가 있습니까? 그렇다면 make 문을 이러한 사용 사례를 지원하도록 확장해야 합니까?

선택적 확장 기능

make 키워드 제거

make 키워드를 제거하여 이러한 문이 호출할 호출 가능 객체로 시작하도록 만들 수도 있습니다. 예를 들면 다음과 같습니다.:

namespace ns:
    badger = 42
    def spam():
        ...

interface C(...):
    ...

그러나 거의 모든 다른 Python 문은 키워드로 시작하며, 키워드를 제거하면 문서에서 이 구문을 찾기가 더 어려워집니다. 또한 이렇게 하면 문법이 더 복잡해지며, 지금까지 저(Steven Bethard)는 키워드 없이 이 기능을 구현하지 못했습니다.

Python 3000에서 __metaclass__ 제거

make 문은 범용적이므로, 그 부작용으로 클래스 객체에서 __metaclass__ 속성의 필요성을 대부분 제거합니다. 따라서 Python 3000에서는 다음과 같이 하는 대신:

class <name> <bases-tuple>:
    __metaclass__ = <metaclass>
    <block>

메타클래스를 make 문에서 호출 가능 객체로 사용하여 메타클래스를 지원할 수 있습니다.:

make <metaclass> <name> <bases-tuple>:
    <block>

__metaclass__ 후크를 제거하면 BUILD_CLASS 바이트코드가 약간 단순해집니다.

Python 3000에서 클래스 문 제거

make 문의 가장 극단적인 적용에서는 클래스 문 자체를 사용 중단하고 make type 문으로 대체할 수 있습니다.

참고 문헌