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>을 실행하여 만들어진 dict입니다. 이는 대체로 다음 코드에 대한 문법적 설탕이며,:

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

클래스가 아닌 다른 무언가를 생성할 때 문의 의도를 더 명확하게 표현하는 데 도움을 주기 위한 것입니다. 물론 이러한 문에 다른 문법을 쓸 수도 있지만, class 문과 강하게 대응되도록 유지하면 클래스와 메타클래스가 어떻게 동작하는지에 대한 이해가 make 문이 어떻게 동작하는지에 대한 이해로도 이어지기를 기대합니다.

이 PEP는 python-dev 메일링 리스트에서 Michele Simionato가 한 제안 [1]에 기반합니다.

철회 공지

이 PEP는 Guido의 요청 [2]에 따라 철회되었습니다. Guido는 이 제안을 마음에 들어 하지 않았으며, 특히 프로퍼티 사용 사례에서 프로퍼티의 인스턴스 메서드가 다른 인스턴스 메서드와 다른 수준에 놓이게 되고, 프로퍼티 함수에 고정된 이름을 써야 한다는 점을 좋아하지 않았습니다.

동기

class 문은 Python에 두 가지 유용한 기능을 제공합니다.

  1. 문 블록을 실행하고, 그 결과로 생긴 바인딩을 dict로 만들어 메타클래스에 전달합니다.
  2. 생성되는 클래스가 자신에게 대입될 이름을 알 수 있게 하여 DRY(don’t repeat yourself, 반복하지 말 것) 원칙을 장려합니다.

따라서 다음과 같은 간단한 class 문에서는:

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

메타클래스(type)가 대략 다음과 같이 호출됩니다.:

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

class 문은 위 대입문에 대한 문법적 설탕일 뿐이지만, 분명 매우 유용한 종류의 문법적 설탕입니다. C의 반복을 피할 뿐만 아니라, dict를 일련의 문으로 표현할 수 있게 하여 dict 생성도 단순하게 만들어 줍니다.

역사적으로 타입 인스턴스(다른 말로 클래스 객체)는 이러한 종류의 문법적 지원을 받는 유일한 객체였습니다. 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. 모듈을 패키지로 바꾸고, roletypes와 format을 하위 모듈로 바꾼 다음, 속성을 하위 모듈로 옮기십시오.
  2. roletypes와 format 클래스를 만들고, 속성들을 클래스로 옮깁니다.

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

이런 상황에서는 몇 가지 속성을 담을 “네임스페이스”를 간단히 선언할 수 있다면 좋을 것입니다. 새 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 문이 끝날 때 roletypes와 format은 네임스페이스 인스턴스가 됩니다.

예: 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의 property 타입은 함수 인자 세 개와 독스트링 인자 하나를 받는데, 이 인자들은 프로퍼티에만 관련이 있음에도 프로퍼티보다 먼저 선언한 다음 property 호출에 인자로 전달해야 합니다. 예를 들면 다음과 같습니다.:

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

dict 생성자의 시그니처가 다르기 때문에 현재 불가능합니다. 이런 종류의 것을 지원할 필요가 있습니까? Carl Banks가 제안한 한 가지 방법은 __make__ 매직 메서드를 추가하여, 이 메서드가 있으면 __call__ 대신 호출되도록 하는 것입니다. 타입의 경우 __make__ 메서드는 __call__과 동일하므로 불필요하겠지만, dict는 대략 다음과 같은 __make__ 메서드를 dict 타입에 정의함으로써 make 문을 지원할 수 있습니다:

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

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

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

그렇다면 문제는, 많은 타입이 make 문을 대체 생성자로 사용하기를 원하겠느냐는 것입니다. 그리고 그렇다면, 그 대체 생성자가 원래 생성자와 같은 이름을 가져야 합니까?

블록이 실행되는 dict의 사용자 정의

make 문의 사용자가 코드가 실행될 dict 객체를 결정할 수 있어야 합니까? 이렇게 하면 일반 dict 객체로는 충분하지 않은 상황, 예를 들어 순서와 중복된 이름을 허용해야 하는 경우에도 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__ 메서드를 호출하여 블록을 실행할 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, text, tail로 네임스페이스를 마법처럼 채워야 하며, 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]

그렇다면 다른 타입의 dict에서 블록을 실행해야 하는 실제 사용 사례가 있습니까? 그리고 있다면, 이를 지원하도록 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에서 class 문 제거

make 문을 가장 극단적으로 적용하면, class 문 자체를 폐기하고 make type 문을 대신 사용하도록 할 수도 있습니다.

참고 문헌