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

Python 개선 제안 한국어 번역

PEP 436 – Argument Clinic(인자 클리닉) DSL

Author:
Larry Hastings <larry at hastings.org>
Discussions-To:
Python-Dev list
Status:
Final
Type:
Standards Track
Created:
22-Feb-2013
Python-Version:
3.4

Table of Contents

번역·라이선스 안내

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

초록

이 문서는 CPython 구현에서 내장 함수의 인자 처리를 쉽게 하기 위한 DSL인 “Argument Clinic”을 제안합니다.

근거와 목표

Python의 주 구현체인 “CPython”은 Python과 C를 섞어서 작성되어 있습니다. CPython의 구현 세부 사항 중 하나로 “내장(built-in)” 함수라고 불리는 것이 있는데, 이는 Python 프로그램에서 사용할 수 있지만 C로 작성된 함수입니다. Python 프로그램이 내장 함수를 호출하면서 인자를 전달하면, 그 인자는 Python 값에서 C 값으로 변환되어야 합니다. 이 과정을 “인자 파싱”이라고 합니다.

CPython 3.3 현재, 내장 함수는 거의 항상 두 함수 중 하나로 인자를 파싱합니다. 하나는 원래의 PyArg_ParseTuple(), [1] 다른 하나는 더 현대적인 PyArg_ParseTupleAndKeywords()입니다. [2] 전자는 위치 매개변수만 처리하고, 후자는 키워드 매개변수와 키워드 전용 매개변수도 처리하며 새 코드에는 후자가 권장됩니다.

두 함수 모두 호출자는 인자를 파싱하는 방법을 “형식 문자열”로 지정합니다. [3] 각 매개변수는 “형식 단위”에 대응하며, 형식 단위는 파싱 함수에 어떤 Python 타입을 받아들일지, 그리고 그것을 해당 매개변수에 알맞은 C 값으로 어떻게 변환할지 알려 주는 짧은 문자 시퀀스입니다.

PyArg_ParseTuple()은 처음 고안되었을 때는 합리적이었습니다. 당시에는 이러한 “포맷 단위”가 열두 개 남짓밖에 없었고, 각각이 뚜렷이 구별되어 이해하고 기억하기 쉬웠습니다. 그러나 수년에 걸쳐 PyArg_Parse 인터페이스는 여러 방식으로 확장되었습니다. 현대의 API는 사용하기가 다소 고통스러울 정도로 복잡합니다. 다음을 생각해 보십시오:

  • 이제 서로 다른 “포맷 단위”가 마흔 개나 있으며, 그중 몇몇은 길이가 세 문자에 이르기도 합니다. 이 때문에 프로그래머는 문서와 끊임없이 대조해 보지 않고서는 형식 문자열이 무엇을 말하는지 이해하기는커녕 파싱하기조차 어렵습니다.
  • 형식 문자열 속에 숨어 있을 수 있는 메타 형식 단위도 여섯 개 있습니다. (바로 "()|$:;"입니다.)
  • 포맷 단위가 추가될수록 구현자가 포맷 단위에 쓰기 쉬운 연상 기호를 고를 수 있을 가능성은 줄어듭니다. 원하는 문자가 이미 사용 중일 가능성이 높기 때문입니다. 다시 말해, 포맷 단위가 많아질수록 포맷 단위는 더 난해해집니다.
  • 여러 포맷 단위가 다른 포맷 단위와 미묘한 차이만 있을 뿐 거의 같습니다. 이 때문에 형식 문자열의 정확한 의미를 이해하기가 더욱 어려워지며, 원하는 형식 단위가 정확히 무엇인지 알아내기도 어려워질 수 있습니다.
  • 독스트링은 정적 C 문자열로 지정되는데, C 문자열 인용 규칙을 따라야 하므로 읽고 편집하기가 다소 번거롭습니다.
  • PyArg_ParseTupleAndKeywords()를 사용하는 함수에 새 매개변수를 추가할 때는 코드에서 서로 다른 여섯 곳을 수정해야 합니다: [4]
    • 인자를 저장할 변수를 선언합니다.
    • PyArg_ParseTupleAndKeywords()의 올바른 위치에 그 변수에 대한 포인터를 전달하고, “길이”나 “변환기” 인자가 있다면 이것도 올바른 순서로 전달합니다.
    • PyArg_ParseTupleAndKeywords()에 전달되는 “keywords”(키워드) 배열의 올바른 위치에 인자 이름을 추가합니다.
    • 형식 문자열의 올바른 위치에 형식 단위를 추가합니다.
    • 독스트링의 프로토타입에 매개변수를 추가합니다.
    • 독스트링에 매개변수를 문서화합니다.
  • 현재 내장 함수가 자신의 “시그니처” 정보를 제공하는 메커니즘은 없습니다(inspect.getfullargspec과 inspect.Signature를 참조하십시오). 기존 PyArg_Parse 함수들과 비슷한 메커니즘으로 이 정보를 추가하려면 같은 내용을 또다시 반복해야 합니다.

Argument Clinic(인자 클리닉)의 목표는 이 API를 이러한 단점을 하나도 물려받지 않는 메커니즘으로 대체하는 것입니다.

  • 각 매개변수는 한 번만 지정하면 됩니다.
  • 매개변수에 관한 모든 정보가 한곳에 모여 있습니다.
  • 각 매개변수에 대해 변환 함수를 지정하면, Argument Clinic(인자 클리닉)이 Python 값을 C 값으로 변환하는 작업을 대신 처리합니다.
  • Argument Clinic에서는 매개변수화된 변환 함수로 인자 처리 동작을 세밀하게 조정할 수도 있습니다.
  • 독스트링은 일반 텍스트로 작성합니다. 함수 독스트링은 필수이며, 매개변수별 독스트링도 작성할 것을 권장합니다.
  • 이로부터 Argument Clinic(인자 클리닉)은 CPython이 내부적으로 필요로 하는 평범하고 반복적인 코드와 자료 구조를 모두 대신 생성합니다. 인터페이스를 지정하고 나면, 다음 단계는 네이티브 C 타입을 사용하여 구현을 작성하는 것뿐입니다. 인자 파싱의 모든 세부 사항은 자동으로 처리됩니다.

Argument Clinic(인자 클리닉)은 전처리기로 구현됩니다. Argument Clinic의 워크플로는 Ned Batchelder의 [Cog]에서 직접 영감을 얻었습니다. Clinic을 사용하려면 C 소스 코드에 특별한 텍스트 문자열로 시작하고 끝나는 블록 주석을 추가한 다음, 해당 파일에 대해 Clinic을 실행하십시오. Clinic은 블록 주석을 찾아 그 내용을 처리하고, 출력 결과를 C 소스 파일의 해당 주석 바로 뒤에 기록합니다. Clinic의 출력이 소스 코드의 일부가 되도록 하려는 것이며, 이 출력은 버전 관리 시스템에 체크인되고 소스 패키지와 함께 배포됩니다. 따라서 Python은 여전히 바로 빌드할 수 있는 상태로 배포됩니다. 다만 개발이 약간 번거로워지기는 합니다. Clinic을 사용하여 새 함수를 추가하거나 기존 함수의 인자나 문서를 수정하려면 동작하는 Python 3 인터프리터가 필요합니다.

Argument Clinic(인자 클리닉)의 향후 목표는 다음과 같습니다.

  • 내장 함수의 시그니처 정보 제공,
  • Python의 대체 구현이 자동화된 라이브러리 호환성 테스트를 만들 수 있도록 지원,
  • 생성된 코드를 개선하여 인자 파싱 속도 향상.

DSL 문법 요약

Argument Clinic(인자 클리닉) DSL은 다음과 같이 C 파일에 내장된 주석으로 지정합니다. 오른쪽의 “Example” 열은 Argument Clinic(인자 클리닉) DSL에 대한 입력 예시를 보여 주며, 왼쪽의 “Section” 열은 각 줄이 무엇을 나타내는지 차례로 설명합니다.

Argument Clinic(인자 클리닉)의 DSL 문법은 Python def 문을 본떠서, Python 코어 개발자에게 어느 정도 친숙함을 줍니다.

+-----------------------+-----------------------------------------------------------------+
| Section               | Example                                                         |
+-----------------------+-----------------------------------------------------------------+
| Clinic DSL start      | /*[clinic]                                                      |
| Module declaration    | module module_name                                              |
| Class declaration     | class module_name.class_name                                    |
| Function declaration  | module_name.function_name  -> return_annotation                 |
| Parameter declaration |       name : converter(param=value)                             |
| Parameter docstring   |           Lorem ipsum dolor sit amet, consectetur               |
|                       |           adipisicing elit, sed do eiusmod tempor               |
| Function docstring    | Lorem ipsum dolor sit amet, consectetur adipisicing             |
|                       | elit, sed do eiusmod tempor incididunt ut labore et             |
| Clinic DSL end        | [clinic]*/                                                      |
| Clinic output         | ...                                                             |
| Clinic output end     | /*[clinic end output:<checksum>]*/                              |
+-----------------------+-----------------------------------------------------------------+

제안된 DSL 문법이 어떤 모습인지 보여 드리기 위해 몇 가지 Clinic 코드 블록 예시를 소개합니다. 첫 번째 블록은 매개변수 사이의 빈 줄과 인자별 독스트링을 포함하여, 일반적으로 선호되는 스타일을 반영합니다. 또한 로컬에서 만든 사용자 정의 변환기(path_t)도 포함합니다:

/*[clinic]
os.stat as os_stat_fn -> stat result

   path: path_t(allow_fd=1)
       Path to be examined; can be string, bytes, or open-file-descriptor int.

   *

   dir_fd: OS_STAT_DIR_FD_CONVERTER = DEFAULT_DIR_FD
       If not None, it should be a file descriptor open to a directory,
       and path should be a relative string; path will then be relative to
       that directory.

   follow_symlinks: bool = True
       If False, and the last element of the path is a symbolic link,
       stat will examine the symbolic link itself instead of the file
       the link points to.

Perform a stat system call on the given path.

{parameters}

dir_fd and follow_symlinks may not be implemented
  on your platform.  If they are unavailable, using them will raise a
  NotImplementedError.

It's an error to use dir_fd or follow_symlinks when specifying path as
  an open file descriptor.

[clinic]*/

두 번째 예시는 모든 매개변수 독스트링과 의미 없는 빈 줄을 생략한 최소한의 Clinic 코드 블록을 보여 줍니다:

/*[clinic]
os.access
   path: path
   mode: int
   *
   dir_fd: OS_ACCESS_DIR_FD_CONVERTER = 1
   effective_ids: bool = False
   follow_symlinks: bool = True
Use the real uid/gid to test for access to a path.
Returns True if granted, False otherwise.

{parameters}

dir_fd, effective_ids, and follow_symlinks may not be implemented
  on your platform.  If they are unavailable, using them will raise a
  NotImplementedError.

Note that most operations will use the effective uid/gid, therefore this
  routine can be used in a suid/sgid environment to test if the invoking user
  has the specified access to the path.

[clinic]*/

마지막 예시는 왼쪽 매개변수를 포함하여 선택적 매개변수 그룹을 처리하는 Clinic 코드 블록을 보여 줍니다:

/*[clinic]
curses.window.addch

   [
   y: int
     Y-coordinate.

   x: int
     X-coordinate.
   ]

   ch: char
     Character to add.

   [
   attr: long
     Attributes for the character.
   ]

   /

Paint character ch at (y, x) with attributes attr,
overwriting any character previously painter at that location.
By default, the character position and attributes are the
current settings for the window object.
[clinic]*/

Argument Clinic(인자 클리닉) DSL의 일반적인 동작

독스트링을 제외한 모든 줄에서 #을 줄 주석 구분자로 사용할 수 있습니다. 빈 줄은 항상 무시됩니다.

Python 자체와 마찬가지로, Argument Clinic(인자 클리닉) DSL에서는 앞쪽 공백이 의미를 가집니다. “function” 섹션의 첫 줄은 함수 선언입니다. 함수 선언 아래에 들여쓴 줄은 한 줄에 하나씩 매개변수를 선언하며, 그 아래에서 더 깊이 들여쓴 줄은 매개변수별 독스트링입니다. 마지막으로, 들여쓰기가 0번째 열로 돌아온 첫 줄에서 매개변수 선언이 끝나고 함수 독스트링이 시작됩니다.

매개변수 독스트링은 선택 사항이지만, 함수 독스트링은 필수입니다. 인자를 지정하지 않는 함수는 함수 선언 다음에 독스트링만 지정해도 됩니다.

모듈과 클래스 선언

C 파일이 모듈이나 클래스를 구현할 때는 이를 Clinic에 선언해야 합니다. 문법은 간단합니다.:

module module_name

또는

class module_name.class_name

(이것들은 실제로 특별한 문법이 아니며, Directives로 구현되어 있다는 점에 유의하십시오.)

모듈 이름이나 클래스 이름은 항상 최상위 모듈부터 시작하는, 점으로 구분된 전체 경로여야 합니다. 중첩된 모듈과 클래스도 지원됩니다.

함수 선언

함수 선언의 전체 형식은 다음과 같습니다:

dotted.name [ as legal_c_id ] [ -> return_annotation ]

점으로 구분된 이름은 최상위 패키지부터 시작하는 함수의 전체 이름이어야 합니다(예: “os.stat” 또는 “curses.window.addch”).

“as legal_c_id” 문법은 선택 사항입니다. Argument Clinic(인자 클리닉)은 함수 이름을 사용하여 생성되는 C 함수의 이름을 만듭니다. 경우에 따라 생성된 이름이 C 프로그램 네임스페이스의 다른 전역 이름과 충돌할 수 있습니다. “as legal_c_id” 문법을 사용하면 생성된 이름을 직접 정한 이름으로 재정의할 수 있습니다. “legal_c_id”를 유효한 C 식별자로 바꾸십시오. 이 구문을 생략하는 경우 “as” 키워드도 생략해야 합니다.

반환 어노테이션도 선택 사항입니다. 반환 어노테이션을 생략하는 경우 화살표(”->”)도 생략해야 합니다. 반환 어노테이션을 지정하는 경우 그 값은 ast.literal_eval과 호환되어야 하며, 반환 변환기로 해석됩니다.

매개변수 선언

매개변수 선언 줄의 전체 형식은 다음과 같습니다:

name: converter [ (parameter=value [, parameter2=value2]) ] [ = default]

“name”은 유효한 C 식별자여야 합니다. 이름과 콜론 사이에는 공백을 넣을 수 있습니다(다만 권장하는 스타일은 아닙니다). 콜론과 변환기 사이에는 공백을 넣을 수 있으며, 넣는 것을 권장합니다.

“converter”는 Argument Clinic(인자 클리닉)에 등록된 “변환 함수” 중 하나의 이름입니다. Clinic에는 여러 내장 변환기가 함께 제공되며, 새 변환기를 동적으로 추가할 수도 있습니다. 변환기를 선택하면 입력으로 허용되는 Python 타입이 자동으로 제한되고, 출력 변수(하나 또는 여러 개)의 타입도 함께 지정됩니다. 많은 변환기의 이름이 C 타입이나 Python 타입의 이름과 비슷하지만, 변환기 이름으로는 올바른 Python 식별자라면 무엇이든 사용할 수 있습니다.

변환기 뒤에 괄호가 오면, 그 괄호 안에는 변환 함수에 전달할 매개변수가 들어갑니다. 이 문법은 Python 함수 호출에 인자를 전달하는 방식과 같습니다. 매개변수는 “키워드 전용 매개변수”처럼 항상 이름을 붙여야 하며, 매개변수에 전달하는 값은 문법적으로 Python 리터럴 값과 비슷합니다. 이 매개변수들은 항상 선택 사항이므로, 모든 변환 함수는 매개변수 없이 호출할 수 있습니다. 이때는 괄호를 완전히 생략해도 되며, 이는 항상 빈 괄호를 지정한 것과 같습니다. 이 매개변수에 전달하는 값은 ast.literal_eval과 호환되어야 합니다.

“기본값(default)”은 Python 리터럴 값입니다. 기본값은 선택 사항이며, 기본값을 지정하지 않는 경우에는 등호도 생략해야 합니다. 기본값이 없는 매개변수는 암묵적으로 필수 매개변수가 됩니다. 기본값은 생성된 C 코드 안에서 “실행 시점에” 동적으로 할당되며, Python 값으로 지정하더라도 생성된 C 코드에서는 네이티브 C 값으로 변환됩니다. 이처럼 수동으로 변환하는 단계를 거치기 때문에 허용되는 기본값은 많지 않습니다.

이것이 Python 함수 선언이라면 매개변수 선언은 뒤따르는 쉼표나 닫는 괄호로 구분될 것입니다. 그러나 Argument Clinic(인자 클리닉)은 둘 다 사용하지 않으며, 매개변수 선언은 줄바꿈으로 구분됩니다. 뒤따르는 쉼표나 닫는 괄호는 허용되지 않습니다.

첫 번째 매개변수 선언이 해당 Clinic 코드 블록에 있는 모든 매개변수 선언의 들여쓰기를 결정합니다. 이후의 모든 매개변수는 같은 수준으로 들여 써야 합니다.

레거시 변환기

기존 코드를 Argument Clinic(인자 클리닉)으로 변환하는 편의를 위해, Clinic은 PyArg_ParseTuple 형식 단위에 대응하는 레거시 변환기 집합을 제공합니다. 레거시 변환기는 형식 단위를 담은 C 문자열로 지정합니다. 예를 들어, Python “int”를 받아 C int를 내보내는 매개변수 “foo”를 지정하려면 다음과 같이 작성할 수 있습니다:

foo : "i"

(C 문자열과 더 비슷하게 보이도록, 이때는 항상 큰따옴표를 사용해야 합니다.)

이 변환기들은 PyArg_ParseTuple 형식 단위와 비슷하지만, 구현이 파싱을 위해 PyArg_Parse 함수를 호출한다는 보장은 없습니다.

이 문법은 매개변수를 지원하지 않습니다. 따라서 입력 매개변수가 필요한 형식 단위("O!", "O&", "es", "es#", "et", "et#")는 지원하지 않습니다. 이러한 변환이 필요한 매개변수에는 레거시 문법을 사용할 수 없습니다. (하지만 기본값은 여전히 지정할 수 있습니다.)

매개변수 독스트링

매개변수 선언 아래에 있으면서 그 선언보다 더 깊이 들여 쓴 모든 줄은 해당 매개변수의 독스트링입니다. 이러한 줄은 모두 첫 줄이 왼쪽 끝에 붙을 때까지 “내어 쓰기(dedent)”됩니다.

매개변수 줄의 특수 문법

매개변수 섹션에서 사용할 수 있는 특수 기호는 네 가지입니다. 각 기호는 반드시 한 줄에 단독으로 나타나야 하며, 매개변수 선언과 같은 수준으로 들여써야 합니다. 네 가지 기호는 다음과 같습니다:

*
이후의 모든 매개변수가 키워드 전용임을 지정합니다.
[
매개변수의 선택적 “그룹”이 시작됨을 지정합니다. “그룹”은 다른 “그룹” 안에 중첩될 수 있다는 점에 유의하십시오. 아래의 Functions With Positional-Only Parameters를 참조하십시오. 현재 [는 모든 매개변수가 위치 전용으로 표시된 함수에서만 사용할 수 있다는 점에 유의하십시오. 아래의 /를 참조하십시오.
]
매개변수의 선택적 “그룹”을 끝냅니다.
/
앞에 오는 모든 인자가 위치 전용임을 지정합니다. 현재 Argument Clinic은 위치 전용 인자와 위치 전용이 아닌 인자를 모두 가진 함수를 지원하지 않습니다. 따라서 함수에 /를 지정한다면, 현재로서는 항상 마지막 매개변수 뒤에 두어야 합니다. 또한 Argument Clinic(인자 클리닉)은 현재 위치 전용 매개변수의 기본값을 지원하지 않습니다.

(/의 의미는 Guido가 예전에 제안한 Python의 위치 전용 매개변수 문법을 따릅니다. [5] )

함수 독스트링

함수 선언 뒤에 오는 줄 중 앞쪽 공백이 없는 첫 번째 줄이 함수 독스트링의 첫 줄입니다. Clinic 블록의 이후 모든 줄은 독스트링의 일부로 간주되며, 앞쪽 공백도 그대로 유지됩니다.

함수 독스트링 안에서 문자열 {parameters}가 한 줄에 단독으로 나타나면, Argument Clinic(인자 클리닉)은 독스트링이 있는 모든 매개변수의 목록을 삽입하며, 각 매개변수 뒤에는 해당 독스트링이 이어집니다. 매개변수 이름은 한 줄에 단독으로 놓이고, 독스트링은 그다음 줄부터 시작하며, 독스트링의 모든 줄은 공백 두 칸으로 들여씁니다. (매개변수별 독스트링이 없는 매개변수는 생략됩니다.) 목록 전체는 {parameters} 토큰 앞에 있던 앞쪽 공백만큼 들여씁니다.

문자열 {parameters}가 독스트링에 나타나지 않으면, Argument Clinic(인자 클리닉)은 독스트링 끝에 이를 하나 덧붙입니다. 이때 독스트링이 빈 줄로 끝나지 않으면 그 위에 빈 줄을 삽입하며, 매개변수 목록은 0번 열에서 시작합니다.

변환기

Argument Clinic(인자 클리닉)에는 변환 함수의 레지스트리가 미리 초기화되어 들어 있습니다. 변환기 함수의 예는 다음과 같습니다:

int
__int__를 구현하는 Python 객체를 받아 C int를 생성합니다.
byte
Python int를 받아 unsigned char를 생성합니다. 정수는 반드시 [0, 256) 범위 안에 있어야 합니다.
str
Python str 객체를 받아 C char *를 생성합니다. ascii 코덱을 사용하여 문자열을 자동으로 인코딩합니다.
PyObject
모든 객체를 받아들이며, 아무런 변환 없이 C PyObject *를 내보냅니다.

모든 변환기는 다음 매개변수를 받아들입니다:

doc_default
Python 문맥에서 매개변수의 실제 기본값 대신 사용할 Python 값입니다. 다시 말해, 이 값을 지정하면 독스트링과 Signature에서 매개변수의 기본값으로 이 값이 사용됩니다. (아직 정해지지 않은 대안 의미: 문자열이 eval()을 사용하여 Python 값으로 만들어 낼 수 있는 유효한 Python 표현식이라면, 그 문자열에 eval()을 적용한 결과가 Signature에서 기본값으로 사용됩니다.) 기본값이 없으면 무시됩니다.
required
일반적으로 기본값이 있는 매개변수는 모두 자동으로 선택적 매개변수가 됩니다. “required”가 설정된 매개변수는 기본값이 있더라도 필수(선택적이지 않음)로 간주됩니다. 생성된 문서에도 기본값이 표시되지 않습니다.

또한 변환기는 개별적으로 다음 선택적 매개변수 중 하나 이상을 받아들일 수 있습니다:

annotation
이 매개변수에 대한 매개변수별 어노테이션을 명시적으로 지정합니다. 일반적으로 어노테이션(있는 경우)을 생성하는 것은 변환 함수의 책임입니다.
bitwise
부호 없는 정수를 받아들이는 변환기용입니다. 전달된 Python 정수가 부호 있는 정수라면, 음수이더라도 비트를 그대로 복사합니다.
encoding
str을 받아들이는 변환기용입니다. 유니코드 문자열을 char *로 인코딩할 때 사용할 인코딩입니다.
immutable
불변 값만 받아들입니다.
length
이터러블 타입을 받아들이는 변환기용입니다. 변환기가 이터러블의 길이도 함께 내보내도록 요청하며, 이 길이는 Py_ssize_t 변수에 담겨 _impl 함수에 전달됩니다. 이 변수의 이름은 이 매개변수의 이름 뒤에 “_length”를 덧붙인 것입니다.
nullable
이 변환기는 일반적으로 None을 받아들이지 않지만, 이 경우에는 받아들여야 합니다. Python 쪽에서 None이 주어지면, 대응하는 C 인자는 NULL이 됩니다. (이 변환기가 내보내는 _impl 인자는 아마도 포인터 타입일 것입니다.)
types
이 객체에 허용되는 Python 타입을 나타내는 문자열의 리스트입니다. Python 프로토콜을 나타내는 네 가지 문자열도 있습니다:
  • “buffer”(버퍼)
  • “매핑”
  • “number”
  • “sequence”
zeroes
문자열 타입을 받는 변환기에 사용합니다. 변환된 값 안에 0 바이트가 포함되는 것을 허용해야 합니다.

반환 변환기

반환 변환기는 개념적으로 변환기의 역연산을 수행합니다. 즉, 네이티브 C 값을 그에 대응하는 Python 값으로 변환합니다.

지시문

인자 클리닉(Argument Clinic)은 Clinic 코드 블록 안에 “지시문”을 넣는 것도 허용합니다. 지시문은 C의 pragma와 비슷합니다. 지시문은 인자 클리닉(Argument Clinic)의 동작을 수정하는 문장입니다.

지시문의 형식은 다음과 같습니다:

directive_name [argument [second_argument [ ... ]]]

지시문은 위치 인자만 받습니다.

Clinic 코드 블록에는 하나 이상의 지시문이나 함수 선언 중 하나가 반드시 들어 있어야 합니다. 둘 다 들어 있을 수도 있으며, 이 경우 모든 지시문이 함수 선언보다 앞에 와야 합니다.

내부적으로 지시문은 Python 호출 가능 객체에 직접 대응됩니다. 지시문의 인자는 str() 타입의 위치 인자로 호출 가능 객체에 직접 전달됩니다.

가능한 지시문의 예로는 Clinic 출력의 생성, 억제, 리디렉션 등이 있습니다. 또한 프로토타입에서는 “module”(모듈) 키워드와 “class”(클래스) 키워드가 지시문으로 구현되어 있습니다.

Python 코드

인자 클리닉(Argument Clinic)에서는 C 파일 안에 Python 코드를 포함할 수도 있으며, 이 코드는 Argument Clinic이 파일을 처리할 때 그 자리에서 실행됩니다. 삽입된 코드는 다음과 같습니다:

/*[python]

# this is python code!
print("/" + "* Hello world! *" + "/")

[python]*/
/* Hello world! */
/*[python end:da39a3ee5e6b4b0d3255bfef95601890afd80709]*/

위의 "/* Hello world! */" 줄은 바로 앞 주석에 있는 Python 코드를 실행해서 생성한 것입니다.

어떤 Python 코드든 사용할 수 있습니다. 인자 클리닉의 Python 코드 섹션으로 Clinic과 직접 상호 작용할 수도 있습니다. Argument Clinic Programmatic Interfaces를 참조하십시오.

출력

인자 클리닉은 출력을 C 파일 안의 Clinic 코드 섹션 바로 다음에 직접 씁니다. “python” 섹션의 경우, builtins.print로 출력한 모든 내용이 출력이 됩니다. “clinic” 섹션의 경우, 출력은 다음을 포함하는 유효한 C 코드입니다:

  • 함수에 맞는 methoddef 구조체를 제공하는 #define
  • “impl” 함수의 프로토타입 – 이 함수를 구현하기 위해 여러분이 작성할 부분입니다
  • 모든 인자 처리를 담당하며 여러분의 “impl” 함수를 호출하는 함수
  • “impl” 함수의 정의 줄
  • 그리고 출력의 끝을 나타내는 주석이 붙습니다.

impl 함수의 본문은 출력 바로 뒤에 작성하도록 되어 있습니다. 즉, 출력 끝 주석 바로 뒤에 여는 중괄호를 쓰고, 그 안의 본문에서 내장 함수를 구현합니다. (처음에는 조금 이상하게 느껴지지만, 의외로 편리합니다.)

인자 클리닉(Argument Clinic)이 impl 함수의 매개변수를 대신 정의합니다. 이 함수는 원래 전달된 “self” 매개변수와 여러분이 정의한 모든 매개변수를 받습니다. 추가로 생성되는 매개변수(“length” 매개변수, 그리고 다음 절에서 다루는 “group” 매개변수)를 받을 수도 있습니다.

인자 클리닉(Argument Clinic)은 출력 섹션에 대한 체크섬도 기록합니다. 이는 중요한 안전장치입니다. 출력을 직접 수정하면 Clinic이 체크섬이 일치하지 않는다는 것을 알아차리고 파일 덮어쓰기를 거부합니다. (”-f” 명령줄 인자로 Clinic이 강제로 덮어쓰게 할 수 있습니다. “-o” 명령줄 인자를 사용할 때도 Clinic은 체크섬을 무시합니다.)

마지막으로, 인자 클리닉(Argument Clinic)은 정의된 클래스와 모듈에 대한 PyMethodDef 배열의 상용구 정의도 생성할 수 있습니다.

위치 전용 매개변수를 갖는 함수

C로 구현된 Python 내장 함수의 상당수는 인자를 처리하기 위해 예전의 위치 전용 API(PyArg_ParseTuple())를 사용합니다. 경우에 따라 이러한 내장 함수는 전달된 인자의 개수에 따라 인자를 다르게 파싱합니다. 이 때문에 당혹스러울 만큼의 유연성이 생길 수 있습니다. 선택적 매개변수들이 그룹을 이루어, 모두 지정하거나 아무것도 지정하지 않아야 하는 경우가 있습니다. 그리고 때로는 이 그룹들이 왼쪽에! 있기도 합니다. (대표적인 예: curses.window.addch().)

인자 클리닉(Argument Clinic)은 매개변수를 그룹으로 지정할 수 있도록 하여 이러한 레거시 사용 사례를 지원합니다. 선택적 매개변수 그룹은 각각 대괄호로 표시합니다. 이 그룹들은 필수 매개변수의 오른쪽 또는 왼쪽에 올 수 있다는 점에 유의하십시오!

Clinic이 생성하는 impl 함수에는 그룹마다 “int group_{left|right}_<x>” 매개변수가 추가됩니다. 여기서 x는 각 그룹에 부여되는 번호로, 필수 인자에서 멀어질수록 단조 증가합니다. 이 인자는 해당 호출에서 그룹이 지정되었으면 0이 아닌 값이 되고, 지정되지 않았으면 0이 됩니다.

이 모드로 동작할 때는 기본 인자를 지정할 수 없다는 점에 유의하십시오.

또한 함수에 그룹 집합을 지정할 때, 인자 개수에서 유효한 그룹 집합으로 가는 유효한 매핑이 여러 개 생기도록 지정할 수도 있다는 점에 유의하십시오. 이런 경우 Clinic은 오류 메시지를 출력하고 중단합니다. 위치 전용 동작은 레거시 사용 사례만을 위한 것이고, 이런 특이한 동작을 사용하는 레거시 함수들은 모두 모호하지 않은 매핑을 가지므로 이는 문제가 되지 않을 것입니다.

현재 상태

이 글을 쓰는 시점에 인자 클리닉(Argument Clinic)의 동작하는 프로토타입 구현을 온라인에서 사용할 수 있습니다(다만 이 글을 읽는 시점에는 문법이 이미 바뀌었을 수 있습니다). [6] 이 프로토타입은 기존 PyArg_Parse API를 사용하여 코드를 생성합니다. 수수께끼 같은 "w*"를 제외한 현재의 모든 포맷 단위로의 변환을 지원합니다. Argument Clinic을 사용하는 예제 함수들은 위치 전용 인자 파싱을 포함한 모든 주요 기능을 활용합니다.

인자 클리닉(Argument Clinic) 프로그래밍 인터페이스

또한 프로토타입은 현재 실험적인 확장 메커니즘을 제공하여, 새로운 타입에 대한 지원을 즉석에서 추가할 수 있게 합니다. 사용 예는 프로토타입의 Modules/posixmodule.c를 참조하십시오.

앞으로 인자 클리닉(Argument Clinic)은 Python 코드를 통해 함수 선언을 조회하거나 수정하거나 아예 새로 구성할 수 있을 만큼 자동화될 것으로 예상됩니다. 여러분만의 사용자 정의 DSL을 동적으로 추가하는 것까지 가능해질지도 모릅니다!

참고 사항 / 미정

  • 내장 함수에 inspect.Signature 메타데이터를 제공하는 API는 현재 논의 중입니다. 인자 클리닉(Argument Clinic)은 해당 프로토타입이 실현 가능해지면 이를 지원할 것입니다.
  • Alyssa Coghlan은 a) 함수당 왼쪽 선택적 그룹을 최대 하나만 지원하고, b) 모호한 경우에는 오른쪽 그룹보다 왼쪽 그룹을 우선하자고 제안합니다. 이렇게 하면 range()를 포함한 기존의 모든 사용 사례를 해결할 수 있습니다.
  • 이상적으로는 인자 클리닉(Argument Clinic)이 일반적인 Python 빌드 과정의 일부로 자동 실행되기를 바랍니다. 하지만 이는 부트스트래핑 문제를 일으킵니다. 시스템에 Python 3이 없다면 Python 3을 빌드하기 위해 Python 3 실행 파일이 필요합니다. 해결할 수 있는 문제라고 확신하지만, 최선의 해결책이 무엇인지는 모르겠습니다. (이를 지원하려면 Windows용 해결책도 별도로 마련해야 합니다.)
  • 관련하여 덧붙이면, inspect.Signature에는 curses.window.addch의 y와 x로 이루어진 왼쪽 선택적 블록과 같은 인자 블록을 표현할 방법이 없습니다. 분명히 변칙적인 이 매개변수 방식을 어디까지 지원할 것입니까?
  • PyCon US 2013 Language Summit에서 인자 클리닉(Argument Clinic)이 함수의 실제 문서(Sphinx로 처리되는 ReST 형식)도 생성하도록 하는 방안이 논의되었습니다. 구체적인 실행 방안은 아직 정해지지 않았지만, 이를 위해서는 독스트링을 ReST로 작성해야 하고 Python이 ReST -> ascii 변환기를 함께 배포해야 합니다. CPython 소스 트리를 Clinic을 사용하도록 대규모로 전환하기 전에 이에 대한 결정을 내리는 것이 가장 좋습니다.
  • Guido는 “함수 독스트링”을 출력 중간에 인라인으로 직접 손으로 작성하는 방식을 다음과 같이 제안했습니다:
    /*[clinic]
      ... prototype and parameters (including parameter docstrings) go here
    [clinic]*/
    ... some output ...
    /*[clinic docstring start]*/
    ... hand-edited function docstring goes here   <-- you edit this by hand!
    /*[clinic docstring end]*/
    ... more output
    /*[clinic output end]*/
    

    이 방식을 시도해 보았지만 마음에 들지 않습니다 – 어설프다고 생각합니다. DSL 출력 중간에 손으로 편집한 내용이 섬처럼 끼어 있기보다는, 작성하는 모든 내용이 한곳에 모여 있는 편을 선호합니다.

  • 인자 클리닉(Argument Clinic)은 자동 튜플 언패킹(PyArg_ParseTuple()의 “(OOO)” 스타일 형식 문자열)을 지원하지 않습니다.
  • 인자 클리닉(Argument Clinic)은 일부 동적 특성과 유연성을 없앱니다. PyArg_ParseTuple()을 사용하면 이론적으로 “es”/”et” 형식 단위에 런타임에 서로 다른 인코딩을 전달할 수 있습니다. 제가 아는 한 CPython 자체는 이렇게 하지 않지만, 외부 사용자는 그렇게 할 수도 있습니다. (참고로, regrtest가 실행하는 코드 중 “es”를 사용하는 곳은 없으며, 실행되는 “et”의 사용처는 _ssl.c에 있는 하나를 제외하면 모두 socketmodule.c에 있습니다. 이들은 모두 정적이며, 인코딩으로 "idna"를 지정합니다.)

감사의 글

PEP 작성자는 Cog의 영리한 설계를 뻔뻔하게 베껴 올 수 있도록 허락해 준 Ned Batchelder에게 감사드립니다. 그의 말을 빌리면 Cog는 “한 번도 써 볼 기회가 없었던, 제가 가장 좋아하는 도구”입니다. [bugtracker issue]와 python-dev에서 피드백을 주신 모든 분께도 감사드립니다. PyCon US 2013에서 이 주제에 관해 두 시간 동안 대면으로 열띤 심층 논의를 함께해 준 Alyssa (Nick) Coghlan과 Guido van Rossum에게 특별히 감사드립니다.

참고 문헌