튜토리얼¶
Argument Clinic의 작동 방식을 파악하는 가장 좋은 방법은 함수가 Argument Clinic과 함께 작동하도록 변환하는 것입니다. 다음은 함수가 Argument Clinic과 함께 작동하도록 변환하기 위해 따라야 하는 최소한의 단계입니다. CPython에 체크인할 코드는 문서 뒷부분에서 살펴볼 advanced concepts 중 반환 변환기 사용 방법와 “self 변환기” 사용법 같은 개념을 사용하여 변환을 더 진행해야 합니다. 하지만 이 따라 하기를 통해 배울 수 있도록 여기서는 간단하게 진행하겠습니다.
먼저, CPython 트렁크에서 새로 업데이트한 체크아웃으로 작업하고 있는지 확인하십시오.
그다음, PyArg_ParseTuple() 또는 PyArg_ParseTupleAndKeywords()을 호출하며 아직 Argument Clinic과 함께 작동하도록 변환되지 않은 파이썬 내장 함수를 찾으십시오. 이 튜토리얼에서는 _pickle.Pickler.dump를 사용하겠습니다.
관련 PyArg_Parse*() 함수 호출에서 다음 형식 단위 중 하나를 사용한다면…:
O&
O!
es
es#
et
et#
… 또는 PyArg_ParseTuple()을 여러 번 호출한다면 다른 함수를 선택해야 합니다. (이러한 경우에는 고급 변환기를 사용하는 방법를 참조하십시오.)
또한 함수가 동일한 인자에 대해 서로 다른 타입을 지원하면서 PyArg_ParseTuple() 또는 PyArg_ParseTupleAndKeywords()을 여러 번 호출하거나, 인자를 구문 분석하는 데 PyArg_Parse*() 함수 이외의 것을 사용한다면 Argument Clinic으로 변환하기에 적합하지 않을 가능성이 큽니다. Argument Clinic은 제네릭 함수나 다형성 매개변수를 지원하지 않습니다.
그다음, 함수 위에 다음 상용구를 추가하여 입력 블록을 만드십시오.:
/*[clinic input]
[clinic start generated code]*/
독스트링을 잘라내어 [clinic] 줄 사이에 붙여 넣고, 올바르게 따옴표로 묶인 C 문자열로 만드는 불필요한 요소를 모두 제거하십시오. 작업을 마치면 왼쪽 여백에 맞춰진 텍스트만 남아야 하며, 어떤 줄도 80자를 넘어서는 안 됩니다. Argument Clinic은 독스트링 내부의 들여쓰기를 보존합니다.
이전 독스트링의 첫 줄이 함수 시그니처처럼 보였다면 그 줄을 버리십시오. 이제 독스트링에는 해당 줄이 필요하지 않습니다 — 나중에 내장 함수에 help()를 사용하면 함수의 시그니처를 기반으로 첫 줄이 자동 생성됩니다.
독스트링 요약 줄 예시:
/*[clinic input]
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
독스트링에 “요약” 줄이 없으면 Argument Clinic이 오류를 표시하므로 요약 줄이 있는지 확인합시다. “요약” 줄은 독스트링의 시작 부분에 있는 하나의 80열 줄로 구성된 문단이어야 합니다. (독스트링 규약은 PEP 257을 참조하십시오.)
예제 독스트링은 요약 줄로만 구성되어 있으므로 이 단계에서는 예제 코드를 변경할 필요가 없습니다.
이제 독스트링 위에 함수 이름을 입력하고 빈 줄을 하나 추가하십시오. 이는 함수의 파이썬 이름이자 함수의 전체 점 표기 경로여야 합니다 — 모듈 이름으로 시작하고 모든 하위 모듈을 포함해야 하며, 함수가 클래스의 메서드라면 클래스 이름도 포함해야 합니다.
이 예제에서 _pickle은 모듈이고, Pickler는 클래스이며, dump()는 메서드이므로 이름은 _pickle.Pickler.dump()가 됩니다.:
/*[clinic input]
_pickle.Pickler.dump
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
이 C 파일에서 해당 모듈이나 클래스가 Argument Clinic과 함께 사용되는 것이 처음이라면 모듈 및/또는 클래스를 선언해야 합니다. 올바른 Argument Clinic 작성 관례에 따르면 포함 파일과 정적 선언을 파일 상단에 두는 것과 마찬가지로, 이러한 선언을 C 파일 상단 근처의 별도 블록에 두는 것이 좋습니다. 예제 코드에서는 두 블록을 서로 나란히 표시하겠습니다.
클래스와 모듈의 이름은 파이썬에서 보이는 이름과 같아야 합니다. 상황에 따라 PyModuleDef 또는 PyTypeObject에 정의된 이름을 확인하십시오.
클래스를 선언할 때는 C에서 해당 타입의 두 가지 측면도 지정해야 합니다. 즉, 이 클래스의 인스턴스를 가리키는 포인터에 사용할 타입 선언과 이 클래스의 PyTypeObject를 가리키는 포인터를 지정해야 합니다.:
/*[clinic input]
module _pickle
class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
[clinic start generated code]*/
/*[clinic input]
_pickle.Pickler.dump
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
함수의 각 매개변수를 선언하십시오. 각 매개변수는 별도의 줄에 작성해야 합니다. 모든 매개변수 줄은 함수 이름과 독스트링보다 들여쓰기해야 합니다. 이러한 매개변수 줄의 일반적인 형식은 다음과 같습니다:
name_of_parameter: converter
매개변수에 기본값이 있으면 변환기 뒤에 추가하십시오:
name_of_parameter: converter = default_value
Argument Clinic의 “기본값” 지원은 상당히 정교합니다. 자세한 내용은 매개변수에 기본값 할당하기를 참조하십시오.
다음으로 매개변수 아래에 빈 줄을 추가하십시오.
“변환기”란 무엇입니까? 변환기는 C에서 사용되는 변수의 타입과 런타임에 Python 값을 C 값으로 변환하는 방법을 모두 설정합니다. 지금은 “레거시 변환기”라고 하는 것을 사용합니다 — 이는 오래된 코드를 Argument Clinic으로 더 쉽게 이식하기 위한 편의 문법입니다.
각 매개변수에 대해 PyArg_Parse() 포맷 인자에서 해당 매개변수의 “포맷 단위”를 복사하고, 그것을 따옴표로 묶은 문자열로 지정하여 변환기로 사용하십시오. “포맷 단위”는 format 매개변수에서 1~3개 문자로 이루어진 부분 문자열의 공식 명칭으로, 변수의 타입과 변수를 변환하는 방법을 인자 구문 분석 함수에 알려 줍니다. 포맷 단위에 관한 자세한 내용은 Parsing arguments and building values을 참조하십시오.
z#와 같은 여러 문자로 된 포맷 단위에는 2개 또는 3개 문자로 이루어진 전체 문자열을 사용하십시오.
예시:
/*[clinic input]
module _pickle
class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
[clinic start generated code]*/
/*[clinic input]
_pickle.Pickler.dump
obj: 'O'
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
함수의 포맷 문자열에 |가 있어 일부 매개변수에 기본값이 있음을 나타내는 경우에는 이를 무시해도 됩니다. Argument Clinic은 매개변수에 기본값이 있는지 여부를 바탕으로 어떤 매개변수가 선택 사항인지 추론합니다.
함수의 포맷 문자열에 $가 있어 키워드 전용 인자를 받는다는 의미인 경우, 첫 번째 키워드 전용 인자 앞의 별도 줄에 *를 지정하고 매개변수 줄과 동일하게 들여쓰기하십시오.
_pickle.Pickler.dump()에는 둘 다 없으므로 예시는 변경되지 않습니다.
다음으로 기존 C 함수가 PyArg_ParseTupleAndKeywords()가 아닌 PyArg_ParseTuple()을 호출한다면 모든 인자는 위치 전용입니다.
Argument Clinic에서 매개변수를 위치 전용으로 표시하려면 마지막 위치 전용 매개변수 뒤의 별도 줄에 /를 추가하고 매개변수 줄과 동일하게 들여쓰기하십시오.
예시:
/*[clinic input]
module _pickle
class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
[clinic start generated code]*/
/*[clinic input]
_pickle.Pickler.dump
obj: 'O'
/
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
각 매개변수에 매개변수별 독스트링을 작성하면 도움이 될 수 있습니다. 매개변수별 독스트링은 선택 사항이므로 원한다면 이 단계를 건너뛰어도 됩니다.
그렇지만 매개변수별 독스트링을 추가하는 방법은 다음과 같습니다. 매개변수별 독스트링의 첫 번째 줄은 매개변수 정의보다 더 많이 들여쓰기해야 합니다. 이 첫 번째 줄의 왼쪽 여백이 전체 매개변수별 독스트링의 왼쪽 여백을 정하며, 작성하는 모든 텍스트는 이만큼 내어쓰기됩니다. 원한다면 여러 줄에 걸쳐 원하는 만큼 텍스트를 작성할 수 있습니다.
예시:
/*[clinic input]
module _pickle
class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
[clinic start generated code]*/
/*[clinic input]
_pickle.Pickler.dump
obj: 'O'
The object to be pickled.
/
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
파일을 저장하고 닫은 다음 Tools/clinic/clinic.py를 실행하십시오. 운이 좋다면 모든 것이 제대로 처리되었을 것입니다—이제 블록에 출력이 생겼으며 .c.h 파일도 생성되었습니다! 생성된 코드를 확인하려면 텍스트 편집기에서 파일을 다시 불러오십시오.:
/*[clinic input]
_pickle.Pickler.dump
obj: 'O'
The object to be pickled.
/
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
static PyObject *
_pickle_Pickler_dump(PicklerObject *self, PyObject *obj)
/*[clinic end generated code: output=87ecad1261e02ac7 input=552eb1c0f52260d9]*/
당연히 Argument Clinic이 출력을 생성하지 않았다면 입력에서 오류를 발견했기 때문입니다. Argument Clinic이 문제없이 파일을 처리할 때까지 오류를 계속 수정하고 다시 시도하십시오.
가독성을 위해 대부분의 연결 코드는 .c.h 파일에 생성되었습니다. 이를 원본 .c 파일에 포함해야 하며, 일반적으로 clinic 모듈 블록 바로 뒤에 포함합니다.:
#include "clinic/_pickle.c.h"
Argument Clinic이 생성한 인자 구문 분석 코드가 기존 코드와 기본적으로 동일한지 다시 확인하십시오.
먼저 두 곳에서 동일한 인자 구문 분석 함수를 사용하는지 확인하십시오. 기존 코드는 PyArg_ParseTuple() 또는 PyArg_ParseTupleAndKeywords() 중 하나를 호출해야 하며, Argument Clinic이 생성한 코드가 정확히 동일한 함수를 호출하는지 확인하십시오.
둘째, PyArg_ParseTuple() 또는 PyArg_ParseTupleAndKeywords()에 전달되는 포맷 문자열은 콜론이나 세미콜론까지 기존 함수에서 직접 작성한 문자열과 정확히 동일해야 합니다.
Argument Clinic은 항상 함수 이름 앞에 :를 붙여 포맷 문자열을 생성합니다. 사용법 도움말을 제공하기 위해 기존 코드의 포맷 문자열이 ;로 끝나는 경우에도 이 변경은 무해하므로 걱정하지 마십시오.
셋째, 길이 변수, 인코딩 문자열 또는 변환 함수 포인터처럼 포맷 단위에 두 개의 인자가 필요한 매개변수는 두 호출의 두 번째 인자가 정확히 동일한지 확인하십시오.
넷째, 블록의 출력 부분에서 이 내장 함수에 적합한 정적 PyMethodDef 구조체를 정의하는 전처리기 매크로를 찾을 수 있습니다.:
#define __PICKLE_PICKLER_DUMP_METHODDEF \
{"dump", (PyCFunction)__pickle_Pickler_dump, METH_O, __pickle_Pickler_dump__doc__},
이 정적 구조체는 이 내장 함수의 기존 정적 PyMethodDef 구조체와 정확히 동일해야 합니다.
이 항목 중 하나라도 어떤 식으로든 다르다면 둘이 동일해질 때까지 Argument Clinic 함수 명세를 조정하고 Tools/clinic/clinic.py를 다시 실행하십시오.
출력의 마지막 줄이 “impl” 함수의 선언이라는 점에 유의하십시오. 여기에 내장 함수의 구현이 들어갑니다. 수정 중인 함수의 기존 프로토타입을 삭제하되 여는 중괄호는 남겨 두십시오. 이제 인자 구문 분석 코드와 인자를 저장하는 모든 변수의 선언을 삭제하십시오. 이제 Python 인자가 이 impl 함수의 인자가 되었다는 점에 유의하고, 구현에서 이러한 변수에 다른 이름을 사용했다면 수정하십시오.
조금 이상하므로 다시 한번 설명하겠습니다. 이제 코드는 다음과 같은 형태여야 합니다.:
static return_type
your_function_impl(...)
/*[clinic end generated code: input=..., output=...]*/
{
...
Argument Clinic이 체크섬 줄과 그 바로 위의 함수 프로토타입을 생성했습니다. 함수의 여는 중괄호와 닫는 중괄호를 작성하고 그 안에 구현을 작성해야 합니다.
예제:
/*[clinic input]
module _pickle
class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
[clinic start generated code]*/
/*[clinic end generated code: checksum=da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
/*[clinic input]
_pickle.Pickler.dump
obj: 'O'
The object to be pickled.
/
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
PyDoc_STRVAR(__pickle_Pickler_dump__doc__,
"Write a pickled representation of obj to the open file.\n"
"\n"
...
static PyObject *
_pickle_Pickler_dump_impl(PicklerObject *self, PyObject *obj)
/*[clinic end generated code: checksum=3bd30745bf206a48f8b576a1da3d90f55a0a4187]*/
{
/* Check whether the Pickler was initialized correctly (issue3664).
Developers often forget to call __init__() in their subclasses, which
would trigger a segfault without this check. */
if (self->write == NULL) {
PyErr_Format(PicklingError,
"Pickler.__init__() was not called by %s.__init__()",
Py_TYPE(self)->tp_name);
return NULL;
}
if (_Pickler_ClearBuffer(self) < 0) {
return NULL;
}
...
이 함수의 PyMethodDef 구조체가 포함된 매크로를 기억하십니까? 이 함수의 기존 PyMethodDef 구조체를 찾아 매크로 참조로 바꾸십시오. 내장 함수가 모듈 범위에 있다면 파일 끝에 매우 가까운 곳에 있을 가능성이 높으며, 내장 함수가 클래스 메서드라면 구현부 아래쪽이지만 비교적 가까운 곳에 있을 가능성이 높습니다.
매크로 본문에는 후행 쉼표가 포함되어 있으므로 기존 정적 PyMethodDef 구조체를 매크로로 바꿀 때 끝에 쉼표를 추가하지 마십시오.
예제:
static struct PyMethodDef Pickler_methods[] = {
__PICKLE_PICKLER_DUMP_METHODDEF
__PICKLE_PICKLER_CLEAR_MEMO_METHODDEF
{NULL, NULL} /* sentinel */
};
Argument Clinic이 _Py_ID의 새 인스턴스를 생성할 수 있습니다. 예를 들면 다음과 같습니다.:
&_Py_ID(new_unique_py_id)
그런 경우 이 시점에 미리 컴파일된 식별자 목록을 다시 생성하려면 make regen-global-objects를 실행해야 합니다.
마지막으로 컴파일한 다음, 회귀 테스트 스위트의 관련 부분을 실행하십시오. 이 변경으로 인해 새로운 컴파일 시간 경고나 오류가 발생해서는 안 되며, 한 가지 차이점을 제외하면 외부에서 볼 수 있는 Python의 동작에도 변화가 없어야 합니다. 그 한 가지 차이점은 이제 함수에 대해 실행한 inspect.signature()가 유효한 시그니처를 제공해야 한다는 것입니다!
축하합니다. 첫 번째 함수를 Argument Clinic과 함께 작동하도록 포팅했습니다!