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

Python 개선 제안 한국어 번역

PEP 245 – Python 인터페이스 문법

Author:
Michel Pelletier <michel at users.sourceforge.net>
Status:
Rejected
Type:
Standards Track
Created:
11-Jan-2001
Python-Version:
2.2
Post-History:
21-Mar-2001

Table of Contents

번역·라이선스 안내

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

Note

원래 여기에 링크되어 있었으며 이 PEP에 관한 추가 자료 링크를 담고 있던 Zope 인터페이스 위키 페이지(https://www.zope.org/Wikis/Interfaces)는 더 이상 접속할 수 없지만, Wayback Machine 아카이브에서 찾아볼 수 있습니다. 또한 이 PEP가 논의되었던 Interface-Dev Zope 메일링 리스트는 폐쇄되었지만, 그 아카이브는 여전히 이용할 수 있습니다. (Interface-Dev는 Zope 인터페이스 개발 메일링 리스트입니다.)

거부 공지

이 PEP를 거부합니다. 벌써 5년이 지났습니다. 언젠가는 Python에 인터페이스가 생길 것으로 예상하지만, 그것이 이 PEP의 문법과 닮을 것이라고 기대하는 것은 순진한 생각일 것입니다. 또한 PEP 246도 완전히 다른 방안을 택하면서 거부될 예정입니다. 인터페이스는 어댑테이션(adaptation)에서도, 이를 대체할 무언가에서도 아무런 역할을 하지 않을 것입니다. GvR.

소개

이 PEP는 Python에서 인터페이스 객체를 생성하기 위해 제안된 문법을 설명합니다.

개요

Types-SIG는 Python에 정적 타입 시스템을 추가하는 것을 검토하는 것 외에도, Python을 위한 인터페이스 시스템을 고안하는 임무도 맡았습니다. 1998년 12월, Jim Fulton은 SIG의 논의를 바탕으로 한 인터페이스 시스템 프로토타입을 공개했습니다. 이 논의와 프로토타입에 관한 여러 쟁점과 배경 정보는 SIG 아카이브 [1]에서 찾아볼 수 있습니다.

2000년 말 무렵, Digital Creations는 Zope를 위한 더 나은 컴포넌트 모델 설계를 구상하기 시작했습니다 [2]. Zope의 향후 컴포넌트 모델은 인터페이스 객체에 크게 의존합니다. 이에 따라 Jim의 “Scarecrow” 인터페이스 프로토타입도 더 발전하게 되었습니다. 버전 2.3부터 Zope는 Interface 패키지(인터페이스 패키지)를 표준 소프트웨어로 함께 제공합니다. Zope의 Interface 패키지(인터페이스 패키지)는 이 PEP의 참조 구현으로 사용됩니다.

이 PEP가 제안하는 문법은 PEP 232에서 설명하는 문법 개선에 의존하며, PEP 233의 기반이 될 수 있는 하부 프레임워크를 설명합니다. 인터페이스 객체와 Proxy 객체에 관해 진행 중인 작업이 있으므로, 이 PEP의 선택적인 부분에 대해서는 [3]을 참고하시기 바랍니다.

문제

인터페이스는 소프트웨어를 개발하면서 생기는 여러 문제를 해결해 주기 때문에 중요합니다:

  • Python에는 흔히 “프로토콜”이라고 불리는 암묵적인 인터페이스가 많이 있습니다. 현재 이러한 프로토콜은 구현을 인트로스펙션하여 판별하지만, 이 방법마저 실패하는 경우가 많습니다. 예를 들어 __getitem__을 정의하면 시퀀스와 매핑을 모두 암시하게 됩니다(시퀀스는 연속된 정수 키를 사용합니다). 객체가 어떤 프로토콜을 구현하려는 것인지 개발자가 명시할 방법이 없습니다.
  • 개발자의 관점에서 Python은 타입과 클래스 사이의 분리로 인해 제약을 받습니다. 타입을 기대하는 경우, 사용하는 쪽에서는 ‘foo’가 문자열인지 판별하려고 ‘type(foo) == type(“”)’ 같은 코드를 씁니다. 클래스의 인스턴스를 기대하는 경우, 사용하는 쪽에서는 ‘foo’가 ‘MyString’ 클래스의 인스턴스인지 판별하려고 ‘isinstance(foo, MyString)’을 씁니다. 객체를 특정한 방식으로 올바르게 사용할 수 있는지 판별하는 통일된 모델이 없습니다.
  • Python의 동적 타이핑은 매우 유연하고 강력하지만, 타입 검사를 제공하는 정적 타입 언어의 장점은 갖추고 있지 않습니다. 정적 타입 언어는 훨씬 높은 타입 안전성을 제공하지만, 객체를 공통 서브클래싱으로만 일반화할 수 있고 구체적으로 사용하려면 캐스팅해야 하므로(예를 들어 Java에서처럼) 지나치게 장황해지는 경우가 많습니다.

인터페이스가 해결하려는 문서화 문제도 여럿 있습니다.

  • 개발자는 객체가 어떻게 동작하는지 파악하려고 여러분 시스템의 소스 코드를 들여다보는 데 많은 시간을 낭비합니다.
  • 여러분의 시스템을 처음 접하는 개발자는 객체가 어떻게 동작하는지 잘못 이해할 수 있으며, 이로 인해 사용 오류를 일으키고 나아가 그 오류를 퍼뜨릴 수도 있습니다.
  • 인터페이스가 없다는 것은 사용법을 소스에서 추론해야 한다는 뜻이므로, 개발자가 “내부 전용”으로 의도된 메서드와 속성을 사용하게 될 수 있습니다.
  • 코드를 살펴보는 일은 어려울 수 있으며, 고수가 작성한 코드를 제대로 이해하려는 초보 프로그래머에게는 몹시 의욕을 꺾는 일일 수 있습니다.
  • 많은 사람이 (문서화되지 않은 소프트웨어처럼) 불분명한 것을 이해하려고 무척 애쓰는 과정에서 많은 시간이 낭비됩니다. 인터페이스를 미리 문서화하는 데 들인 노력은 결국 이 시간의 상당 부분을 절약해 줄 것입니다.

인터페이스는 여러분의 객체에 대한 계약상 의무를 명시하는 방법, 객체 사용법에 대한 문서, 그리고 그 계약과 문서를 찾아볼 수 있는 내장 메커니즘을 제공함으로써 이러한 문제를 해결하려고 합니다.

Python에는 매우 유용한 인트로스펙션 기능이 있습니다. 이것이 대화형 인터프리터에서 개념을 탐색하는 일을 더 쉽게 만든다는 것은 잘 알려져 있습니다. Python은 객체에 대한 온갖 정보, 즉 타입, 독스트링(doc 문자열), 인스턴스 딕셔너리, 베이스 클래스, 바인딩되지 않은 메서드 등을 살펴볼 수 있게 해 주기 때문입니다.

이 기능들 중 다수는 소프트웨어의 구현을 인트로스펙션하고, 사용하고, 변경하는 데 맞춰져 있으며, 그중 하나(“doc 문자열”)는 문서를 제공하는 데 맞춰져 있습니다. 이 제안은 객체의 인터페이스를 기술하도록 이 자연스러운 인트로스펙션 프레임워크를 확장하는 방안을 설명합니다.

인터페이스 문법 개요

인터페이스 문법은 대부분 클래스 문법과 매우 비슷하지만, 앞으로의 필요나 논의 중에 제기되는 필요에 따라 인터페이스 문법의 새로운 가능성이 정의될 수도 있습니다.

문법에 대한 형식적인 BNF 기술은 이 PEP 뒷부분에서 제시하며, 여기에서는 설명을 위해 제안된 문법으로 만든 서로 다른 두 인터페이스의 예를 보입니다:

interface CountFishInterface:
    "Fish counting interface"

    def oneFish():
        "Increments the fish count by one"

    def twoFish():
        "Increments the fish count by two"

    def getFishCount():
        "Returns the fish count"

interface ColorFishInterface:
    "Fish coloring interface"

    def redFish():
        "Sets the current fish color to red"

    def blueFish():
        "Sets the current fish color to blue"

    def getFishColor():
        "This returns the current fish color"

이 코드를 평가하면 CountFishInterface와 ColorFishInterface라는 두 인터페이스가 만들어집니다. 이 인터페이스들은 interface 문으로 정의됩니다.

인터페이스와 그 메서드에 대한 산문 형태의 문서는 doc 문자열에서 가져옵니다. 메서드 시그니처 정보는 def 문의 시그니처에서 가져옵니다. def 문에 본문이 없다는 점에 주목하십시오. 인터페이스는 어떤 서비스도 구현하지 않으며, 단지 서비스를 기술할 뿐입니다. 인터페이스와 인터페이스 메서드에는 문서화 문자열이 필수이며, ‘pass’ 문을 제공할 수 없습니다. pass 문에 해당하는 인터페이스의 대응물은 빈 doc 문자열입니다.

다른 인터페이스를 “확장”하는 인터페이스를 만들 수도 있습니다. 여기에서 CountFishInterface와 ColorFishInterface를 확장하는 새로운 유형의 Interface(인터페이스)를 볼 수 있습니다.:

interface FishMarketInterface(CountFishInterface, ColorFishInterface):
    "This is the documentation for the FishMarketInterface"

    def getFishMonger():
        "Returns the fish monger you can interact with"

    def hireNewFishMonger(name):
        "Hire a new fish monger"

    def buySomeFish(quantity=1):
        "Buy some fish at the market"

FishMarketInterface는 CountFishInterface와 ColorfishInterface를 확장합니다.

인터페이스 단언

다음 단계는 인터페이스를 구현한다고 단언하는 구체적인 Python 클래스를 만들어 클래스와 인터페이스를 결합하는 것입니다. 다음은 이를 수행할 수 있는 FishMarket 컴포넌트의 예입니다:

class FishError(Error):
    pass

class FishMarket implements FishMarketInterface:
    number = 0
    color = None
    monger_name = 'Crusty Barnacles'

    def __init__(self, number, color):
        self.number = number
        self.color = color

    def oneFish(self):
        self.number += 1

    def twoFish(self):
        self.number += 2

    def redFish(self):
        self.color = 'red'

    def blueFish(self):
        self.color = 'blue'

    def getFishCount(self):
        return self.number

    def getFishColor(self):
        return self.color

    def getFishMonger(self):
        return self.monger_name

    def hireNewFishMonger(self, name):
        self.monger_name = name

    def buySomeFish(self, quantity=1):
        if quantity > self.count:
            raise FishError("There's not enough fish")
        self.count -= quantity
        return quantity

이 새 클래스 FishMarket은 FishMarketInterface를 구현하는 구체 클래스를 정의합니다. implements 문 뒤에 오는 객체를 “인터페이스 단언(interface assertion)”이라고 합니다. 인터페이스 단언은 인터페이스 객체일 수도 있고, 인터페이스 단언들의 튜플일 수도 있습니다.

이와 같이 class 문에 제공된 인터페이스 단언은 클래스의 __implements__ 클래스 속성에 저장됩니다. 위 예제를 해석하고 나면, ‘implements’ 내장 함수로 다음과 같이 검사할 수 있는 클래스 문을 얻게 됩니다:

>>> FishMarket
<class FishMarket at 8140f50>
>>> FishMarket.__implements__
(<Interface FishMarketInterface at 81006f0>,)
>>> f = FishMarket(6, 'red')
>>> implements(f, FishMarketInterface)
1
>>>

클래스는 둘 이상의 인터페이스를 실현할 수 있습니다. 예를 들어, 객체가 컨테이너 객체의 항목으로서 어떻게 동작하는지를 기술하는 ItemInterface라는 인터페이스가 있다고 가정해 봅시다. FishMarket 인스턴스가 FishMarketInterface뿐만 아니라 ItemInterface 인터페이스도 실현한다고 단언하고 싶다면, 인터페이스 객체들의 튜플을 담은 인터페이스 단언을 FishMarket 클래스에 제공할 수 있습니다:

class FishMarket implements FishMarketInterface, ItemInterface:
    # ...

어떤 클래스가 한 인터페이스를 구현하고, 동시에 다른 클래스가 구현하는 모든 인터페이스도 구현한다고 단언하고 싶을 때에도 인터페이스 단언을 사용할 수 있습니다:

class MyFishMarket implements FishMarketInterface, ItemInterface:
    # ...

class YourFishMarket implements FooInterface, MyFishMarket.__implements__:
    # ...

이 새 클래스 YourFishMarket은 FooInterface뿐만 아니라 MyFishMarket 클래스가 구현하는 인터페이스들도 구현한다고 단언합니다.

인터페이스 단언에 대해 조금 더 자세히 살펴볼 필요가 있습니다. 인터페이스 단언은 인터페이스 객체이거나 인터페이스 단언들의 튜플입니다. 예를 들어:

FooInterface

FooInterface, (BarInterface, BobInterface)

FooInterface, (BarInterface, (BobInterface, MyClass.__implements__))

위의 것들은 모두 유효한 인터페이스 단언입니다. 두 인터페이스가 같은 속성을 정의하는 경우, 단언에서 정보가 우선하는 순서는 위에서 아래로, 왼쪽에서 오른쪽 순입니다.

다른 인터페이스 제안 중에는 단순함을 위해 클래스와 인터페이스의 개념을 결합하여 간단한 인터페이스 강제를 제공하는 것들도 있습니다. 인터페이스 객체에는 이러한 동작을 구현하는 지연 클래스를 반환하는 deferred 메서드가 있습니다:

>>> FM = FishMarketInterface.deferred()
>>> class MyFM(FM): pass

>>> f = MyFM()
>>> f.getFishMonger()
Traceback (innermost last):
  File "<stdin>", line 1, in ?
Interface.Exceptions.BrokenImplementation:
An object has failed to implement interface FishMarketInterface

        The getFishMonger attribute was not provided.
>>>

이는 해당 인터페이스를 구현하기 위해 무엇을 빠뜨렸는지 알려 줌으로써 약간의 수동적인 인터페이스 강제를 제공합니다.

형식적 인터페이스 문법

Python 구문은 Python 레퍼런스 매뉴얼 [4]에 설명된 수정된 BNF 문법 표기법으로 정의됩니다. 이 절에서는 이 문법을 사용하여 제안된 인터페이스 구문을 설명합니다:

interfacedef:   "interface" interfacename [extends] ":" suite
extends:        "(" [expression_list] ")"
interfacename:  identifier

인터페이스 정의는 실행 가능한 문입니다. 먼저 extends 목록이 있으면 이를 평가합니다. extends 목록의 각 항목은 인터페이스 객체로 평가되어야 합니다.

그런 다음 인터페이스의 스위트가 새로 생성된 지역 네임스페이스와 원래의 전역 네임스페이스를 사용하여 새 실행 프레임(Python 레퍼런스 매뉴얼 4.1절 참조)에서 실행됩니다. 인터페이스의 스위트가 실행을 마치면 그 실행 프레임은 폐기되지만, 지역 네임스페이스는 인터페이스 요소로 저장됩니다. 그런 다음 베이스 인터페이스를 위한 extends 목록과 저장된 인터페이스 요소를 사용하여 인터페이스 객체가 생성됩니다. 인터페이스 이름은 원래의 지역 네임스페이스에서 이 인터페이스 객체에 바인딩됩니다.

이 PEP는 Python의 ‘class’ 문에 대한 확장도 제안합니다:

classdef:    "class" classname [inheritance] [implements] ":" suite
implements:  "implements" implist
implist:     expression-list

classname,
inheritance,
suite,
expression-list:  see the Python Reference Manual

클래스의 스위트가 실행되기 전에 ‘inheritance’ 문과 ‘implements’ 문이 있으면 이를 먼저 평가합니다. ‘inheritance’ 동작은 언어 레퍼런스 7.6절에 정의된 대로 변경되지 않습니다.

‘implements’가 있으면 상속 이후에 평가됩니다. 이 값은 반드시 인터페이스 명세로 평가되어야 합니다. 인터페이스 명세는 인터페이스이거나 인터페이스 명세의 튜플입니다. 유효한 인터페이스 명세가 있으면, 그 선언은 튜플 형태로 클래스 객체의 ‘__implements__’ 속성에 할당됩니다.

이 PEP는 함수 정의나 대입 문법에 대해 어떠한 변경도 제안하지 않습니다.

클래스와 인터페이스

위의 예제 인터페이스들은 메서드의 동작을 전혀 기술하지 않습니다. 일반적인 FishMarket 객체가 구현할 인터페이스를 기술할 뿐입니다.

다른 인터페이스를 확장하는 인터페이스와 다른 클래스를 서브클래싱하는 클래스가 서로 비슷하다는 점을 알아차리셨을 수도 있습니다. 둘은 비슷한 개념입니다. 그러나 인터페이스는 인터페이스를 확장하고, 클래스는 클래스를 서브클래스로 만든다는 점에 유의하는 것이 중요합니다. 클래스를 확장하거나 인터페이스의 서브클래스를 만들 수는 없습니다. 클래스와 인터페이스는 서로 별개입니다.

클래스의 목적은 객체가 동작하는 방식의 구현을 공유하는 것입니다. 인터페이스의 목적은 객체가 어떻게 구현되었는지가 아니라 객체를 어떻게 다루는지를 문서화하는 것입니다. 구현이 매우 다른 여러 클래스가 같은 인터페이스를 구현할 수 있습니다.

또한 인터페이스 기능을 조각조각 믹스인하는 여러 클래스로 하나의 인터페이스를 구현할 수도 있고, 반대로 하나의 클래스가 여러 인터페이스를 구현할 수도 있습니다. 이 때문에 인터페이스와 클래스를 혼동하거나 뒤섞어서는 안 됩니다.

인터페이스를 인식하는 내장 함수

인터페이스 객체를 고려할 때 Python 내장 함수 목록에 추가할 만한 유용한 확장은 implements()일 것입니다. 이 내장 함수는 객체와 인터페이스라는 두 인자를 받아, 객체가 해당 인터페이스를 구현하면 참 값을, 그렇지 않으면 거짓 값을 반환합니다. 예를 들면 다음과 같습니다:

>>> interface FooInterface: pass
>>> class Foo implements FooInterface: pass
>>> f = Foo()
>>> implements(f, FooInterface)
1

현재 이 기능은 참조 구현에서 Interface 패키지의 함수로 존재하며, 이를 사용하려면 “import Interface”가 필요합니다. (즉, 인터페이스 패키지를 임포트해야 합니다.) 이 함수를 내장 함수로 두는 것은 순전히 편의를 위한 것이며 인터페이스를 사용하는 데 반드시 필요하지는 않습니다. 클래스에 대한 isinstance()와 유사합니다.

하위 호환성

제안된 인터페이스 모델은 Python에 어떠한 하위 호환성 문제도 일으키지 않습니다. 그러나 제안된 문법은 하위 호환성 문제를 일으킵니다.

interface를 식별자로 사용하는 기존 코드는 모두 동작하지 않게 됩니다. interface를 새 키워드로 정의하면 이 밖에 다른 종류의 하위 비호환성이 생길 수도 있습니다. Python 문법에 대한 이 확장은 기존 문법을 하위 호환되지 않는 방식으로 변경하지 않습니다.

새로운 from __future__ Python 문법(PEP 236)과 새로운 경고 프레임워크(PEP 230)는 이 하위 비호환성을 해결하는 데 이상적입니다. 지금 인터페이스 문법을 사용하려면 개발자는 다음 문장을 사용할 수 있습니다:

from __future__ import interfaces

또한 interface 키워드를 식별자로 사용하는 모든 코드에 대해 Python이 경고를 발생시킵니다. 적절한 기간이 지나면 인터페이스 문법이 표준이 되고, 위의 import 문은 아무 일도 하지 않게 되며, interface라는 이름의 식별자는 모두 예외를 발생시키게 됩니다. 이 기간은 24개월로 제안합니다.

Python에 제안하는 변경 사항 요약

새로운 interface 키워드를 추가하고 클래스 문법을 implements로 확장합니다.

클래스 인터페이스가 __implements__를 포함하도록 확장합니다.

‘implements(obj, interface)’ 내장 함수(인터페이스 구현 여부 확인)를 추가합니다.

위험 요소

이 PEP는 Python 언어에 새로운 키워드 interface 하나를 추가할 것을 제안합니다. 이로 인해 기존 코드가 동작하지 않게 됩니다.

미해결 문제

목표

문법

아키텍처

반대 의견

이 PEP는 아직 python-dev에서 논의되지 않았습니다.

참고 문헌