방법 안내서¶
Argument Clinic에서 생성한 C 함수와 변수의 이름을 바꾸는 방법¶
Argument Clinic은 생성하는 함수의 이름을 자동으로 지정합니다. 생성된 이름이 기존 C 함수의 이름과 충돌하면 간혹 문제가 발생할 수 있습니다. 간단한 해결책이 있습니다. C 함수에 사용되는 이름을 재정의하십시오. 함수 선언 줄에 "as" 키워드를 추가하고 그 뒤에 사용하려는 함수 이름을 지정하기만 하면 됩니다. Argument Clinic은 해당 함수 이름을 베이스(생성된) 함수에 사용한 다음, 끝에 "_impl"을 추가하여 구현 함수의 이름으로 사용합니다.
예를 들어 pickle.Pickler.dump()에 대해 생성된 C 함수 이름을 바꾸려면 다음과 같이 작성합니다:
/*[clinic input]
pickle.Pickler.dump as pickler_dumper
...
이제 베이스 함수의 이름은 pickler_dumper()이고, 구현 함수의 이름은 pickler_dumper_impl()입니다.
이와 비슷하게 매개변수에 특정 Python 이름을 지정하고 싶지만, 그 이름을 C에서 사용하기에는 불편할 수 있습니다. Argument Clinic에서는 동일한 "as" 문법을 사용하여 매개변수에 Python과 C에서 서로 다른 이름을 지정할 수 있습니다:
/*[clinic input]
pickle.Pickler.dump
obj: object
file as file_obj: object
protocol: object = NULL
*
fix_imports: bool = True
여기서 Python에서 사용되는 이름(시그니처와 keywords 배열에서)은 file이지만, C 변수의 이름은 file_obj입니다.
이 방법으로 self 매개변수의 이름도 바꿀 수 있습니다!
PyArg_UnpackTuple을 사용하는 함수를 변환하는 방법¶
관련 PyArg_UnpackTuple()로 인자를 구문 분석하는 함수를 변환하려면 모든 인자를 작성하고 각각을 object로 지정하기만 하면 됩니다. 적절하게 타입을 형 변환하도록 type 인자를 지정할 수 있습니다. 모든 인자를 위치 전용으로 표시해야 합니다(마지막 인자 뒤에 /를 별도의 줄로 추가하십시오).
현재 생성되는 코드는 PyArg_ParseTuple()을 사용하지만, 곧 변경될 예정입니다.
선택적 그룹을 사용하는 방법¶
일부 레거시 함수는 인자를 구문 분석할 때 까다로운 방식을 사용합니다. 위치 인자의 개수를 센 다음, 위치 인자의 개수에 따라 여러 PyArg_ParseTuple() 호출 중 하나를 호출하는 switch 문을 사용합니다. (이러한 함수는 키워드 전용 인자를 받을 수 없습니다.) 이 방식은 PyArg_ParseTupleAndKeywords()가 만들어지기 전에 선택적 인자를 모의 구현하는 데 사용되었습니다.
이 방식을 사용하는 함수는 흔히 PyArg_ParseTupleAndKeywords(), 선택적 인자, 기본값을 사용하도록 변환할 수 있지만, 항상 가능한 것은 아닙니다. 이러한 레거시 함수 중 일부에는 PyArg_ParseTupleAndKeywords()가 직접 지원하지 않는 동작이 있습니다. 가장 명확한 예는 필수 인자의 왼쪽에 선택적 인자가 있는 내장 함수 range()입니다! 또 다른 예는 항상 함께 지정해야 하는 두 인자 그룹이 있는 curses.window.addch()입니다. (인자의 이름은 x와 y입니다. x를 전달하여 함수를 호출하면 y도 전달해야 하며, x를 전달하지 않으면 y도 전달할 수 없습니다.)
어떤 경우든 Argument Clinic의 목표는 기존의 모든 CPython 내장 함수에 대해 의미 체계를 변경하지 않고 인자 구문 분석을 지원하는 것입니다. 따라서 Argument Clinic은 이른바 선택적 그룹을 사용하는 이러한 대체 구문 분석 방식을 지원합니다. 선택적 그룹은 모두 함께 전달해야 하는 인자 그룹입니다. 선택적 그룹은 필수 인자의 왼쪽이나 오른쪽에 올 수 있습니다. 위치 전용 매개변수에만 사용할 수 있습니다.
참고
선택적 그룹은 오직 다음과 같은 경우에만 사용하기 위한 것입니다.
관련 PyArg_ParseTuple()를 여러 번 호출하는 함수를 변환하는 경우입니다! 인자를 파싱하는 데 어떤 다른 접근 방식을 사용하는 함수도 선택적 그룹을 사용해 Argument Clinic으로 거의 절대로 변환해서는 안 됩니다. 선택적 그룹을 사용하는 함수는 현재 Python에서 정확한 시그니처를 가질 수 없습니다. Python이 이 개념을 이해하지 못하기 때문입니다. 가능한 한 선택적 그룹을 사용하지 마십시오.
선택적 그룹을 지정하려면 함께 그룹화할 매개변수 앞의 단독 줄에 [를 추가하고, 이 매개변수들 뒤의 단독 줄에 ]를 추가하십시오. 다음은 curses.window.addch()가 선택적 그룹을 사용하여 처음 두 매개변수와 마지막 매개변수를 선택 사항으로 만드는 방법의 예입니다:
/*[clinic input]
curses.window.addch
[
x: int
X-coordinate.
y: int
Y-coordinate.
]
ch: object
Character to add.
[
attr: long
Attributes for the character.
]
/
...
참고 사항:
각 선택적 그룹마다 해당 그룹을 나타내는 추가 매개변수 하나가 impl 함수에 전달됩니다. 이 매개변수는
group_{direction}_{number}라는 이름의 int이며, 여기서{direction}은 그룹이 필수 매개변수 앞에 있는지 뒤에 있는지에 따라right또는left이고,{number}는 그룹이 필수 매개변수에서 얼마나 떨어져 있는지를 나타내는 단조 증가하는 숫자입니다(1부터 시작합니다). impl이 호출될 때 이 그룹이 사용되지 않았다면 이 매개변수는 0으로 설정되고, 사용되었다면 0이 아닌 값으로 설정됩니다. (사용 여부는 이번 호출에서 매개변수가 인자를 받았는지를 의미합니다.)필수 인자가 없으면 선택적 그룹은 필수 인자의 오른쪽에 있는 것처럼 동작합니다.
모호한 경우 인자 파싱 코드는 왼쪽의 매개변수(필수 매개변수 앞에 있는 매개변수)를 우선합니다.
선택적 그룹에는 위치 전용 매개변수만 포함할 수 있습니다.
선택적 그룹은 오직 레거시 코드를 위한 것입니다. 새 코드에는 선택적 그룹을 사용하지 마십시오.
“레거시 변환기” 대신 실제 Argument Clinic 변환기를 사용하는 방법¶
시간을 절약하고 Argument Clinic으로 처음 이식하기 위해 배워야 할 내용을 최소화할 수 있도록 위의 안내에서는 “레거시 변환기”를 사용하라고 설명합니다. “레거시 변환기”는 기존 코드를 Argument Clinic으로 더 쉽게 이식하도록 명시적으로 설계된 편의 기능입니다.
그러나 장기적으로는 모든 블록에서 Argument Clinic의 실제 변환기 문법을 사용하도록 하는 것이 바람직합니다. 그 이유는 무엇입니까? 몇 가지 이유는 다음과 같습니다:
정식 변환기는 훨씬 읽기 쉽고 의도도 더 명확합니다.
인자가 필요한 일부 형식 단위는 “레거시 변환기”로 지원되지 않습니다. 레거시 변환기 문법이 인자 지정을 지원하지 않기 때문입니다.
향후
PyArg_ParseTuple()이 지원하는 범위로 제한되지 않는 새로운 인자 파싱 라이브러리가 생길 수도 있습니다. 레거시 변환기를 사용하는 매개변수에는 이러한 유연성이 제공되지 않습니다.
따라서 조금 더 수고해도 괜찮다면 레거시 변환기 대신 일반 변환기를 사용하십시오.
요약하면 Argument Clinic (비레거시) 변환기의 문법은 Python 함수 호출과 비슷합니다. 하지만 함수에 명시적 인자가 없다면(모든 함수 인자가 기본값을 사용한다면) 괄호를 생략할 수 있습니다. 따라서 bool과 bool()은 완전히 동일한 변환기입니다.
Argument Clinic 변환기의 모든 인자는 키워드 전용입니다. 모든 Argument Clinic 변환기는 다음 인자를 받습니다:
- c_default
이 매개변수를 C에서 정의할 때의 기본값입니다. 구체적으로, 이는 “parse function”에서 선언된 변수의 이니셜라이저가 됩니다. 사용 방법은 기본값에 관한 절을 참조하십시오. 문자열로 지정합니다.
- annotation
이 매개변수의 어노테이션 값입니다. 관련 PEP 8에서 Python 라이브러리가 어노테이션을 사용해서는 안 된다고 규정하므로 현재는 지원되지 않습니다.
- unused
impl 함수 시그니처에서 인자를
Py_UNUSED로 감싸십시오.
또한 일부 변환기는 추가 인자를 받습니다. 다음은 이러한 인자와 그 의미의 목록입니다.
- accept
Python 타입(및 가능한 경우 유사 타입)의 집합이며, 허용되는 Python 인자를 이러한 타입의 값으로 제한합니다. (이는 범용 기능이 아니며, 원칙적으로 레거시 변환기 표에 나온 특정 타입 목록만 지원합니다.)
None을 허용하려면 이 집합에NoneType을 추가하십시오.- bitwise
부호 없는 정수에 대해서만 지원됩니다. 이 Python 인자의 네이티브 정수 값은 음수인 경우에도 범위 검사 없이 매개변수에 기록됩니다.
- converter
object변환기에서만 지원됩니다. 이 객체를 네이티브 타입으로 변환하는 데 사용할 C “변환기 함수”의 이름을 지정합니다.- encoding
문자열에 대해서만 지원됩니다. 이 문자열을 Python str(유니코드) 값에서 C
char *값으로 변환할 때 사용할 인코딩을 지정합니다.- subclass_of
object변환기에서만 지원됩니다. Python 값이 C로 표현된 Python 타입의 서브클래스여야 합니다.- type
object및self변환기에서만 지원됩니다. 변수를 선언하는 데 사용할 C 타입을 지정합니다. 기본값은"PyObject *"입니다.- zeroes
문자열에 대해서만 지원합니다. 참이면 값 안에 내장된 NUL 바이트(
'\\0')가 허용됩니다. 문자열의 길이는 문자열 매개변수 바로 뒤에서<parameter_name>_length라는 매개변수로 impl 함수에 전달됩니다.
가능한 모든 인자 조합이 작동하는 것은 아니라는 점에 유의하십시오. 일반적으로 이러한 인자는 특정 동작을 하는 PyArg_ParseTuple() 형식 단위로 구현됩니다. 예를 들어 현재는 bitwise=True도 지정하지 않으면 unsigned_short를 호출할 수 없습니다. 이것이 작동하리라고 생각하는 것은 지극히 타당하지만, 이러한 의미 체계는 기존의 어떤 형식 단위에도 대응하지 않습니다. 따라서 Argument Clinic은 이를 지원하지 않습니다. (적어도 아직은 지원하지 않습니다.)
아래 표는 레거시 변환기를 실제 Argument Clinic 변환기로 매핑한 결과를 보여 줍니다. 왼쪽에는 레거시 변환기가 있고, 오른쪽에는 이를 대체할 텍스트가 있습니다.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
예를 들어 다음은 적절한 변환기를 사용하는 pickle.Pickler.dump 예제입니다.:
/*[clinic input]
pickle.Pickler.dump
obj: object
The object to be pickled.
/
Write a pickled representation of obj to the open file.
[clinic start generated code]*/
실제 변환기의 한 가지 장점은 레거시 변환기보다 더 유연하다는 것입니다. 예를 들어 unsigned_int 변환기(및 모든 unsigned_ 변환기)는 bitwise=True를 지정하지 않고도 사용할 수 있습니다. 기본 동작은 값의 범위를 검사하며 음수를 허용하지 않습니다. 레거시 변환기로는 이렇게 할 수 없습니다!
Argument Clinic은 사용 가능한 모든 변환기를 보여 줍니다. 각 변환기가 받는 모든 매개변수와 각 매개변수의 기본값도 보여 줍니다. 전체 목록을 보려면 Tools/clinic/clinic.py --converters를 실행하기만 하면 됩니다.
Py_buffer 변환기를 사용하는 방법¶
Py_buffer 변환기(또는 's*', 'w*', '*y', 'z*' 레거시 변환기)를 사용할 때는 제공된 버퍼에 대해 PyBuffer_Release()를 절대로 호출해서는 안 됩니다. Argument Clinic이 이를 대신 수행하는 코드를 생성합니다(파싱 함수에서 수행합니다).
고급 변환기를 사용하는 방법¶
처음에는 고급 기능이어서 건너뛰었던 형식 단위를 기억하십니까? 이러한 형식 단위를 처리하는 방법은 다음과 같습니다.
핵심은 이러한 모든 형식 단위가 인자를 받는다는 것입니다—변환 함수, 타입 또는 인코딩을 지정하는 문자열을 받습니다. (하지만 “레거시 변환기”는 인자를 지원하지 않습니다. 그래서 첫 번째 함수에서는 이를 건너뛰었습니다.) 이제 형식 단위에 지정했던 인자는 변환기의 인자가 됩니다. 이 인자는 converter (O&의 경우), subclass_of (O!의 경우) 또는 encoding (e로 시작하는 모든 형식 단위의 경우) 중 하나입니다.
subclass_of를 사용할 때는 object()의 다른 사용자 지정 인자인 type도 사용할 수 있습니다. 이를 통해 매개변수에 실제로 사용되는 타입을 설정할 수 있습니다. 예를 들어 객체가 PyUnicode_Type의 서브클래스인지 확인하려면 object(type='PyUnicodeObject *', subclass_of='&PyUnicode_Type') 변환기를 사용하는 것이 좋습니다.
Argument Clinic 사용 시 발생할 수 있는 한 가지 문제는 e로 시작하는 포맷 단위에서 가능한 일부 유연성을 잃는다는 점입니다. 관련 PyArg_Parse*() 호출을 직접 작성할 때는 이론적으로 해당 호출에 전달할 인코딩 문자열을 런타임에 결정할 수 있습니다. 하지만 이제 이 문자열은 Argument Clinic 전처리 시점에 하드코딩해야 합니다. 이러한 제한은 의도적인 것입니다. 이로 인해 해당 포맷 단위를 훨씬 쉽게 지원할 수 있었으며, 향후 최적화도 가능해질 수 있습니다. 이러한 제약은 불합리해 보이지 않습니다. CPython 자체도 포맷 단위가 e로 시작하는 매개변수에는 항상 정적으로 하드코딩된 인코딩 문자열을 전달합니다.
매개변수에 기본값 할당하기¶
매개변수의 기본값으로는 여러 값 중 하나를 사용할 수 있습니다. 가장 단순하게는 문자열, int 또는 float 리터럴을 사용할 수 있습니다.
foo: str = "abc"
bar: int = 123
bat: float = 45.6
Python의 내장 상수도 사용할 수 있습니다.
yep: bool = True
nope: bool = False
nada: object = None
또한 NULL 기본값과 단순한 표현식을 특별히 지원하며, 이에 대해서는 다음 절에서 설명합니다.
NULL 기본값¶
문자열 및 객체 매개변수는 기본값이 없음을 나타내기 위해 None으로 설정할 수 있습니다. 하지만 이렇게 하면 C 변수가 Py_None으로 초기화됩니다. 편의를 위해 바로 이러한 용도의 NULL이라는 특수한 값이 있습니다. Python의 관점에서는 None 기본값처럼 동작하지만, C 변수는 NULL로 초기화됩니다.
기호 기본값¶
매개변수에 지정하는 기본값으로 임의의 표현식을 사용할 수는 없습니다. 현재 다음 항목을 명시적으로 지원합니다.
숫자 상수(정수 및 부동 소수점 수)
문자열 상수
True,False, 및None관련
sys.maxsize와 같이 모듈 이름으로 시작해야 하는 단순한 기호 상수
(향후에는 CONSTANT - 1과 같은 완전한 표현식을 허용하기 위해 이 기능이 더욱 정교해져야 할 수도 있습니다.)
기본값으로 사용하는 표현식¶
매개변수의 기본값은 단순한 리터럴 값 이상일 수 있습니다. 수학 연산자를 사용하고 객체의 어트리뷰트를 조회하는 완전한 표현식일 수도 있습니다. 하지만 명확히 드러나지 않는 몇 가지 의미 체계 때문에 이를 지원하는 일은 그리 단순하지 않습니다.
다음 예를 살펴보십시오.
foo: Py_ssize_t = sys.maxsize - 1
sys.maxsize는 플랫폼에 따라 값이 다를 수 있습니다. 따라서 Argument Clinic은 해당 표현식을 로컬에서 단순히 평가하여 C에 하드코딩할 수 없습니다. 그러므로 사용자가 함수의 시그니처를 요청할 때 런타임에 평가되도록 기본값을 저장합니다.
표현식을 평가할 때 어떤 네임스페이스를 사용할 수 있습니까? 내장 함수가 속한 모듈의 컨텍스트에서 평가됩니다. 따라서 모듈에 max_widgets라는 어트리뷰트가 있다면 이를 그대로 사용할 수 있습니다:
foo: Py_ssize_t = max_widgets
현재 모듈에서 심볼을 찾지 못하면 sys.modules에서 찾습니다. 예를 들어 이 방식으로 sys.maxsize를 찾을 수 있습니다. (사용자가 인터프리터에 어떤 모듈을 로드할지 미리 알 수 없으므로, Python 자체에서 미리 로드하는 모듈만 사용하는 것이 가장 좋습니다.)
기본값을 런타임에만 평가한다는 것은 Argument Clinic이 올바른 동등한 C 기본값을 계산할 수 없다는 뜻입니다. 따라서 이를 명시적으로 알려 주어야 합니다. 표현식을 사용할 때는 변환기의 c_default 매개변수를 사용하여 C에서 동등한 표현식도 지정해야 합니다:
foo: Py_ssize_t(c_default="PY_SSIZE_T_MAX - 1") = sys.maxsize - 1
또 다른 문제는 Argument Clinic이 사용자가 제공한 표현식이 유효한지 미리 알 수 없다는 것입니다. 적법해 보이는지 확인하기 위해 표현식을 구문 분석하지만, 실제로 알 수는 없습니다. 표현식을 사용할 때는 런타임에 유효하다고 보장되는 값을 지정하도록 각별히 주의해야 합니다!
마지막으로 표현식은 정적 C 값으로 나타낼 수 있어야 하므로 적법한 표현식에는 많은 제약이 있습니다. 사용할 수 없는 Python 기능의 목록은 다음과 같습니다:
함수 호출.
인라인 if 문 (
3 if foo else 5).자동 시퀀스 언패킹 (
*[1, 2, 3]).리스트/집합/딕셔너리 컴프리헨션 및 제너레이터 표현식.
튜플/리스트/집합/딕셔너리 리터럴.
반환 변환기 사용 방법¶
기본적으로 Argument Clinic이 생성하는 impl 함수는 PyObject *를 반환합니다. 하지만 C 함수는 흔히 어떤 C 타입을 계산한 다음 마지막 순간에 이를 PyObject *로 변환합니다. Argument Clinic이 입력을 Python 타입에서 네이티브 C 타입으로 변환하는 일을 처리한다면, 반환 값도 네이티브 C 타입에서 Python 타입으로 변환하도록 하면 되지 않겠습니까?
이것이 바로 “반환 변환기”가 하는 일입니다. 반환 변환기는 impl 함수가 어떤 C 타입을 반환하도록 변경한 다음, 생성된 (비 impl) 함수에 해당 값을 적절한 PyObject *로 변환하는 코드를 추가합니다.
반환 변환기의 문법은 매개변수 변환기의 문법과 비슷합니다. -> 표기법을 사용하여 함수 자체의 반환 어노테이션처럼 반환 변환기를 지정합니다.
예를 들면 다음과 같습니다:
/*[clinic input]
add -> int
a: int
b: int
/
[clinic start generated code]*/
반환 변환기는 매개변수 변환기와 거의 동일하게 동작합니다. 인자를 받으며, 모든 인자는 키워드 전용이고, 기본 인자를 변경하지 않는다면 괄호를 생략할 수 있습니다.
(함수에 "as"와 반환 변환기를 둘 다 사용한다면, "as"가 반환 변환기보다 앞에 와야 합니다.)
반환 변환기를 사용할 때 한 가지 추가로 복잡한 점이 있습니다. 오류가 발생했음을 어떻게 나타냅니까? 일반적으로 함수는 성공하면 유효한(NULL이 아닌) 포인터를 반환하고, 실패하면 NULL을 반환합니다. 하지만 정수 반환 변환기를 사용하면 모든 정수가 유효합니다. Argument Clinic은 오류를 어떻게 감지할 수 있습니까? 해결책은 각 반환 변환기가 오류를 나타내는 특수한 값을 암묵적으로 찾는 것입니다. 해당 값을 반환하고 오류가 설정되어 있으면(c:func:PyErr_Occurred가 참 값을 반환하면), 생성된 코드가 오류를 전파합니다. 그렇지 않으면 반환한 값을 평소처럼 인코딩합니다.
현재 Argument Clinic은 몇 가지 반환 변환기만 지원합니다.
bool
double
float
int
long
Py_ssize_t
size_t
unsigned int
unsigned long
이들 중 어느 것도 매개변수를 받지 않습니다. 이들 모두에서 오류를 나타내려면 -1을 반환하십시오.
Argument Clinic이 지원하는 모든 반환 변환기와 해당 매개변수(있는 경우)의 전체 목록을 보려면 Tools/clinic/clinic.py --converters를 실행하십시오.
기존 함수를 복제하는 방법¶
비슷해 보이는 함수가 여러 개 있다면 Clinic의 “clone” 기능을 사용할 수 있습니다. 기존 함수를 복제하면 다음 항목을 재사용합니다.
다음을 포함한 함수의 매개변수
매개변수의 이름,
모든 매개변수가 포함된 변환기,
기본값,
매개변수별 독스트링,
종류(위치 전용인지, 위치 또는 키워드인지, 키워드 전용인지 여부), 그리고
함수의 반환 변환기입니다.
원래 함수에서 복사되지 않는 유일한 항목은 독스트링이며, 구문을 사용하여 새 독스트링을 지정할 수 있습니다.
함수를 복제하는 구문은 다음과 같습니다.:
/*[clinic input]
module.class.new_function [as c_basename] = module.class.existing_function
Docstring for new_function goes here.
[clinic start generated code]*/
(함수는 서로 다른 모듈이나 클래스에 있을 수 있습니다. 예제에서 module.class라고 쓴 것은 두 함수 모두에 전체 경로를 사용해야 한다는 점을 보여 주기 위해서입니다.)
유감스럽게도 함수를 부분적으로 복제하거나, 함수를 복제한 후 수정하는 구문은 없습니다. 복제는 전부 아니면 전무입니다.
또한 복제 원본 함수는 현재 파일에서 앞서 정의되어 있어야 합니다.
Python 코드를 호출하는 방법¶
나머지 고급 주제에서는 C 파일 내부에 존재하면서 Argument Clinic의 런타임 상태를 수정하는 Python 코드를 작성해야 합니다. 방법은 간단합니다. Python 블록을 정의하기만 하면 됩니다.
Python 블록은 Argument Clinic 함수 블록과 다른 구분자 줄을 사용합니다. 다음과 같습니다.:
/*[python input]
# python code goes here
[python start generated code]*/
Python 블록 안의 모든 코드는 블록이 파싱될 때 실행됩니다. 블록 안에서 stdout에 기록된 모든 텍스트는 블록 뒤의 “output”으로 리디렉션됩니다.
다음은 C 코드에 정적 정수 변수를 추가하는 Python 블록의 예입니다.:
/*[python input]
print('static int __ignored_unused_variable__ = 0;')
[python start generated code]*/
static int __ignored_unused_variable__ = 0;
/*[python checksum:...]*/
“self 변환기” 사용법¶
Argument Clinic은 기본 변환기를 사용하여 “self” 매개변수를 자동으로 추가합니다. 이 매개변수의 type을 타입을 선언할 때 지정한 “인스턴스를 가리키는 포인터”로 자동 설정합니다. 하지만 Argument Clinic의 변환기를 재정의하고 직접 지정할 수 있습니다. 블록의 첫 번째 매개변수로 자체 self 매개변수를 추가하고, 해당 변환기가 self_converter의 인스턴스이거나 그 서브클래스의 인스턴스인지 확인하기만 하면 됩니다.
어떤 이점이 있습니까? 이를 통해 self의 타입을 재정의하거나 다른 기본 이름을 지정할 수 있습니다.
self를 캐스팅할 사용자 지정 타입은 어떻게 지정합니까? self 타입이 같은 함수가 한두 개뿐이라면, 사용할 타입을 type 매개변수로 전달하여 Argument Clinic의 기존 self 변환기를 직접 사용할 수 있습니다.:
/*[clinic input]
_pickle.Pickler.dump
self: self(type="PicklerObject *")
obj: object
/
Write a pickled representation of the given object to the open file.
[clinic start generated code]*/
반면 동일한 self 타입을 사용할 함수가 많다면 self_converter를 서브클래싱하되 type 멤버를 덮어써서 자체 변환기를 만드는 것이 가장 좋습니다.:
/*[python input]
class PicklerObject_converter(self_converter):
type = "PicklerObject *"
[python start generated code]*/
/*[clinic input]
_pickle.Pickler.dump
self: PicklerObject
obj: object
/
Write a pickled representation of the given object to the open file.
[clinic start generated code]*/
“정의 클래스” 변환기 사용법¶
Argument Clinic을 사용하면 메서드의 정의 클래스에 쉽게 접근할 수 있습니다. 이는 모듈 수준 상태를 가져와야 하는 힙 타입 메서드에 유용합니다. 관련 PyType_FromModuleAndSpec()을 사용하여 새 힙 타입을 모듈과 연결하십시오. 이제 정의 클래스에 PyType_GetModuleState()를 사용하여, 예를 들어 모듈 메서드에서 모듈 상태를 가져올 수 있습니다.
관련 Modules/zlibmodule.c의 예입니다. 먼저 Clinic 입력에 defining_class를 추가합니다.:
/*[clinic input]
zlib.Compress.compress
cls: defining_class
data: Py_buffer
Binary data to be compressed.
/
Argument Clinic 도구를 실행하면 다음 함수 시그니처가 생성됩니다.:
/*[clinic start generated code]*/
static PyObject *
zlib_Compress_compress_impl(compobject *self, PyTypeObject *cls,
Py_buffer *data)
/*[clinic end generated code: output=6731b3f0ff357ca6 input=04d00f65ab01d260]*/
이제 다음 코드에서 PyType_GetModuleState(cls)를 사용하여 모듈 상태를 가져올 수 있습니다.:
zlibstate *state = PyType_GetModuleState(cls);
각 메서드에는 이 변환기를 사용하는 인자가 하나만 있을 수 있으며, 이 인자는 self뒤에 와야 하고, self를 사용하지 않는 경우에는 첫 번째 인자로 와야 합니다. 이 인자의 타입은 PyTypeObject *입니다. 이 인자는 __text_signature__에 나타나지 않습니다.
defining_class 변환기는 METH_METHOD 규약을 사용할 수 없는 __init__()및 __new__() 메서드와 호환되지 않습니다.
슬롯 메서드에는 defining_class를 사용할 수 없습니다. 이러한 메서드에서 모듈 상태를 가져오려면 PyType_GetModuleByDef()를 사용하여 모듈을 찾은 다음 PyModule_GetState()를 사용하여 모듈 상태를 가져오십시오. 관련 Modules/_threadmodule.c의 setattro 슬롯 메서드에 있는 예입니다.:
static int
local_setattro(localobject *self, PyObject *name, PyObject *v)
{
PyObject *module = PyType_GetModuleByDef(Py_TYPE(self), &thread_module);
thread_module_state *state = get_thread_state(module);
...
}
관련 PEP 573도 참조하십시오.
사용자 정의 변환기를 작성하는 방법¶
변환기는 CConverter를 상속하는 Python 클래스입니다. 사용자 정의 변환기의 주된 목적은 O& 포맷 단위로 파싱되는 매개변수를 처리하는 것입니다. — 이러한 매개변수를 파싱한다는 것은 PyArg_ParseTuple() “변환기 함수”를 호출한다는 의미입니다.
변환기 클래스의 이름은 ConverterName_converter 형식이어야 합니다. 이 규칙을 따르면 변환기 클래스가 Argument Clinic에 자동으로 등록되며, 변환기 이름은 변환기 클래스 이름에서 _converter 접미사를 제거한 이름이 됩니다.
관련 CConverter.__init__()를 서브클래싱하는 대신 converter_init() 메서드를 작성하십시오. converter_init()은 항상 self 매개변수를 받습니다. self 뒤의 모든 추가 매개변수는 반드시 키워드 전용이어야 합니다. Argument Clinic에서 변환기에 전달된 모든 인자는 converter_init() 메서드에도 그대로 전달됩니다. 서브클래스에서 지정할 수 있는 멤버 목록은 CConverter를 참조하십시오.
다음은 Modules/zlibmodule.c에서 가져온 가장 간단한 사용자 정의 변환기 예입니다:
/*[python input]
class ssize_t_converter(CConverter):
type = 'Py_ssize_t'
converter = 'ssize_t_converter'
[python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=35521e4e733823c7]*/
이 블록은 Argument Clinic에 ssize_t 변환기를 추가합니다. ssize_t로 선언된 매개변수는 Py_ssize_t 타입으로 선언되고, ssize_t_converter() 변환기 C 함수를 호출하는 'O&' 포맷 단위로 파싱됩니다. ssize_t 변수는 기본값을 자동으로 지원합니다.
더 정교한 사용자 정의 변환기는 초기화와 정리를 처리하는 사용자 정의 C 코드를 삽입할 수 있습니다. CPython 소스 트리에서 사용자 정의 변환기의 더 많은 예를 확인할 수 있습니다. C 파일에서 CConverter 문자열을 grep으로 검색하십시오.
사용자 정의 반환 변환기를 작성하는 방법¶
사용자 정의 반환 변환기를 작성하는 방법은 사용자 정의 변환기를 작성하는 방법과 매우 비슷합니다. 다만 반환 변환기 자체가 훨씬 단순하므로 조금 더 간단합니다.
반환 변환기는 CReturnConverter를 서브클래싱해야 합니다. 사용자 정의 반환 변환기는 아직 널리 사용되지 않으므로 그 예도 아직 없습니다. 자체 반환 변환기를 작성하려면 Tools/clinic/clinic.py, 특히 CReturnConverter 및 모든 서브클래스의 구현을 읽어 보십시오.
METH_O 및 METH_NOARGS 함수를 변환하는 방법¶
관련 METH_O를 사용하는 함수를 변환하려면 함수의 단일 인자가 object 변환기를 사용하도록 하고, 인자를 위치 전용으로 표시하십시오:
/*[clinic input]
meth_o_sample
argument: object
/
[clinic start generated code]*/
관련 METH_NOARGS를 사용하는 함수를 변환하려면 아무 인자도 지정하지 않으면 됩니다.
여전히 self 변환기와 반환 변환기를 사용할 수 있으며, METH_O용 object 변환기에 type 인자를 지정할 수도 있습니다.
*args 매개변수(starargs / 가변 위치)를 변환하는 방법¶
*args에 적합한 변환기는 두 가지, 즉 array 및 tuple 변환기입니다.
array 변환기를 사용하면 구현 함수에 PyObject * 타입의 C 배열 args와 배열의 항목 수를 나타내는 Py_ssize_t 타입의 args_length 값이 제공됩니다. 예를 들면 다음과 같습니다:
/*[clinic input]
var_positional_sample
spam: int
*args: array
[clinic start generated code]*/
tuple 변환기를 사용하면 구현 함수에 표준 PyTupleObject 객체가 제공됩니다. 예를 들면 다음과 같습니다:
/*[clinic input]
var_positional_sample
spam: int
*args: tuple
[clinic start generated code]*/
Added in version 3.11.
tp_new 및 tp_init 함수를 변환하는 방법¶
관련 tp_new 함수와 tp_init 함수를 변환할 수 있습니다. 적절하게 __new__ 또는 __init__로 이름을 지정하기만 하면 됩니다. 참고 사항:
__new__에 대해 생성된 함수 이름은 기본적으로 생성되는 경우와 달리__new__로 끝나지 않습니다. 클래스 이름을 유효한 C 식별자로 변환한 것일 뿐입니다.이러한 함수에는
PyMethodDef#define이 생성되지 않습니다.__init__함수는PyObject *가 아니라int를 반환합니다.독스트링을 클래스 독스트링으로 사용하십시오.
__new__및__init__함수는 항상args객체와kwargs객체를 모두 받아야 하지만, 변환할 때는 이러한 함수에 원하는 시그니처를 지정할 수 있습니다. (함수가 키워드를 지원하지 않는 경우, 생성된 파싱 함수는 키워드를 받으면 예외를 발생시킵니다.)
Clinic의 출력 변경 및 리디렉션 방법¶
Clinic의 출력이 일반적인 수작업 편집 C 코드 사이에 섞여 있으면 불편할 수 있습니다. 다행히 Clinic은 설정할 수 있습니다. 출력을 버퍼에 모아 두었다가 나중에(또는 더 일찍!) 출력하거나 별도의 파일에 쓸 수 있습니다. Clinic이 생성한 출력의 모든 줄에 접두사나 접미사를 추가할 수도 있습니다.
이러한 방식으로 Clinic의 출력을 변경하면 가독성에 큰 도움이 될 수 있지만, Clinic 코드가 타입이 정의되기 전에 해당 타입을 사용하거나 코드가 Clinic이 생성한 코드의 정의 전에 이를 사용하려고 할 수 있습니다. 이러한 문제는 파일의 선언을 재배치하거나 Clinic이 생성한 코드가 배치되는 위치를 옮기면 쉽게 해결할 수 있습니다. (이 때문에 Clinic은 기본적으로 모든 것을 현재 블록에 출력합니다. 많은 사람이 이것이 가독성을 떨어뜨린다고 생각하지만, 사용 전 정의 문제를 해결하기 위해 코드를 재배치할 필요는 절대 없습니다.)
몇 가지 용어를 정의하는 것부터 시작합시다.
- 필드
이 문맥에서 필드는 Clinic 출력의 하위 섹션입니다. 예를 들어
PyMethodDef구조체의#define은methoddef_define이라는 필드입니다. Clinic은 함수 정의마다 출력할 수 있는 서로 다른 필드가 일곱 개 있습니다.docstring_prototype docstring_definition methoddef_define impl_prototype parser_prototype parser_definition impl_definition
모든 이름은
"<a>_<b>"형식이며, 여기서"<a>"는 표현되는 의미적 객체(파싱 함수, impl 함수, 독스트링 또는 methoddef 구조체)이고"<b>"는 필드가 어떤 종류의 문인지 나타냅니다."_prototype"으로 끝나는 필드 이름은 실제 본문/데이터가 없는 해당 항목의 전방 선언을 나타내며,"_definition"으로 끝나는 필드 이름은 해당 항목의 본문/데이터가 포함된 실제 정의를 나타냅니다. ("methoddef"은 특별하며, 전처리기 #define임을 나타내는"_define"으로 끝나는 유일한 항목입니다.)- 출력 대상
출력 대상은 Clinic이 출력을 쓸 수 있는 위치입니다. 기본 제공되는 출력 대상은 다섯 개입니다.
block기본 출력 대상: 현재 Clinic 블록의 출력 섹션에 출력됩니다.
buffer나중을 위해 텍스트를 저장할 수 있는 텍스트 버퍼입니다. 이곳으로 전송된 텍스트는 기존 텍스트의 끝에 추가됩니다. Clinic이 파일 처리를 마칠 때 버퍼에 텍스트가 남아 있으면 오류입니다.
fileClinic에서 자동으로 생성하는 별도의 “clinic 파일”입니다. 이 파일에 선택되는 파일 이름은
{basename}.clinic{extension}이며, 여기서basename과extension에는 현재 파일에 대해 실행한os.path.splitext()의 출력이 할당됩니다. (예:_pickle.c의file대상은_pickle.clinic.c에 기록됩니다.)중요:
file대상을 사용할 때 반드시 체크인해야 하는 것은 생성된 파일입니다!two-passbuffer같은 버퍼입니다. 그러나 2단계 버퍼는 한 번만 덤프할 수 있으며, 덤프 지점 이후의 Clinic 블록에서 보낸 텍스트까지 포함하여 모든 처리 과정에서 전송된 모든 텍스트를 출력합니다.suppress텍스트를 억제하여 버립니다.
Clinic은 출력을 재구성할 수 있는 다섯 가지 새로운 지시자를 정의합니다.
첫 번째 새 지시자는 dump입니다.
dump <destination>
이 지시자는 이름이 지정된 대상의 현재 내용을 현재 블록의 출력으로 덤프하고 해당 대상을 비웁니다. 이는 buffer 및 two-pass 대상에서만 작동합니다.
두 번째 새 지시자는 output입니다. output 지시자의 가장 기본적인 형태는 다음과 같습니다.
output <field> <destination>
이는 Clinic에 field를 destination으로 출력하도록 지시합니다. output은 Clinic에 모든 필드를 해당 destination으로 출력하도록 지시하는 everything이라는 특수한 메타 대상도 지원합니다.
output에는 다음과 같은 여러 다른 기능이 있습니다.
output push
output pop
output preset <preset>
output push와 output pop을 사용하면 내부 구성 스택에 구성을 푸시하거나 팝할 수 있으므로, 출력 구성을 일시적으로 수정한 다음 이전 구성을 손쉽게 복원할 수 있습니다. 변경 전에 푸시하여 현재 구성을 저장한 다음, 이전 구성을 복원하려는 시점에 팝하면 됩니다.
output preset 지시자는 Clinic의 출력을 다음과 같은 여러 내장 프리셋 구성 중 하나로 설정합니다.
blockClinic의 원래 시작 구성입니다. 모든 내용을 입력 블록 바로 뒤에 기록합니다.
parser_prototype과docstring_prototype을 억제하고, 나머지는 모두block에 기록합니다.file기록할 수 있는 모든 내용을 “clinic 파일”에 기록하도록 설계되었습니다. 그런 다음 파일 상단 근처에서
#include지시자로 이 파일을 포함합니다. 이를 작동시키려면 파일을 재배치해야 할 수도 있지만, 일반적으로는 여러typedef및PyTypeObject정의에 대한 전방 선언을 만들기만 하면 됩니다.parser_prototype과docstring_prototype을 억제하고,impl_definition은block에 기록하며, 나머지는 모두file에 기록합니다.기본 파일 이름은
"{dirname}/clinic/{basename}.h"입니다.bufferClinic의 출력 대부분을 모아 두었다가 파일의 끝부분 근처에 기록하십시오. 모듈이나 내장 타입을 구현하는 Python 파일의 경우, 해당 모듈이나 내장 타입의 정적 구조체 바로 위에 버퍼를 출력하는 것이 좋습니다. 이러한 구조체는 일반적으로 파일의 끝부분에 매우 가깝습니다. 파일 중간에 정적
PyMethodDef배열이 정의되어 있다면buffer사용 시file보다 훨씬 더 많은 편집이 필요할 수 있습니다.parser_prototype,impl_prototype,docstring_prototype을 억제하고,impl_definition을block에 기록하며, 나머지는 모두file에 기록하십시오.two-passbuffer프리셋과 비슷하지만, 전방 선언은two-pass버퍼에 기록하고 정의는buffer에 기록합니다. 이는buffer프리셋과 비슷하지만,buffer보다 편집이 덜 필요할 수 있습니다.two-pass버퍼는 파일의 앞부분 근처에 출력하고,buffer프리셋을 사용할 때와 마찬가지로buffer는 끝부분 근처에 출력하십시오.impl_prototype을 억제하고,impl_definition을block에 기록하며,docstring_prototype,methoddef_define,parser_prototype을two-pass에 기록하고, 나머지는 모두buffer에 기록합니다.partial-bufferbuffer프리셋과 비슷하지만, 더 많은 항목을block에 기록하고 생성된 코드 중 매우 큰 부분만buffer에 기록합니다. 이렇게 하면 블록 출력의 내용이 약간 늘어나는 작은 비용만으로buffer의 사용 전 정의 문제를 완전히 방지할 수 있습니다.buffer프리셋을 사용할 때와 마찬가지로buffer를 끝부분 근처에 출력하십시오.impl_prototype을 억제하고,docstring_definition및parser_definition을buffer에 기록하며, 나머지는 모두block에 기록합니다.
세 번째 새 지시어는 destination입니다:
destination <name> <command> [...]
이는 이름이 name인 목적지에 연산을 수행합니다.
정의된 하위 명령은 new 및 clear 두 가지입니다.
new 하위 명령은 다음과 같이 작동합니다:
destination <name> new <type>
이는 이름이 <name>이고 유형이 <type>인 새 목적지를 생성합니다.
목적지 유형은 다섯 가지입니다:
suppress텍스트를 버립니다.
block텍스트를 현재 블록에 기록합니다. 이는 Clinic이 원래 수행하던 방식입니다.
buffer위의 “buffer” 내장 목적지와 같은 단순한 텍스트 버퍼입니다.
file텍스트 파일입니다. 파일 목적지는 다음과 같이 파일 이름을 만드는 데 사용할 템플릿인 추가 인자를 받습니다.:
destination <name> new <type> <file_template>
템플릿 내부에서는 파일 이름의 일부로 대체될 문자열 세 개를 사용할 수 있습니다:
{path}디렉터리와 전체 파일 이름을 포함한 파일의 전체 경로입니다.
{dirname}파일이 있는 디렉터리의 이름입니다.
{basename}디렉터리를 포함하지 않은 파일 이름입니다.
{basename_root}확장자를 제거한 기본 파일 이름입니다(마지막 ‘.’ 앞까지의 모든 부분).
{basename_extension}마지막 ‘.’과 그 뒤의 모든 부분입니다. 기본 파일 이름에 마침표가 없으면 빈 문자열입니다.
파일 이름에 마침표가 없으면
{basename}과{filename}은 같고,{extension}은 비어 있습니다.{basename}{extension}은 항상{filename}과 정확히 같습니다.two-pass위의 “two-pass” 내장 대상과 같은 2패스 버퍼입니다.
clear 하위 명령은 다음과 같이 작동합니다:
destination <name> clear
대상에서 이 지점까지 누적된 모든 텍스트를 제거합니다. (어디에 필요할지는 모르겠지만, 누군가 실험할 때 유용할 수도 있다고 생각했습니다.)
네 번째 새 지시어는 set입니다:
set line_prefix "string"
set line_suffix "string"
set을 사용하면 Clinic의 내부 변수 두 개를 설정할 수 있습니다. line_prefix는 Clinic 출력의 모든 줄 앞에 추가되는 문자열이며, line_suffix는 Clinic 출력의 모든 줄 뒤에 추가되는 문자열입니다.
이 둘은 다음 두 가지 형식 문자열을 지원합니다:
{block comment start}C 파일에서 주석을 시작하는 텍스트 시퀀스인 문자열
/*로 변환됩니다.{block comment end}C 파일에서 주석을 끝내는 텍스트 시퀀스인 문자열
*/로 변환됩니다.
마지막 새 지시어는 직접 사용할 필요가 없는 preserve입니다:
preserve
이는 현재 출력 내용을 수정하지 않고 유지해야 한다고 Clinic에 알립니다. 이는 Clinic이 출력을 file 파일로 덤프할 때 내부적으로 사용됩니다. 이를 Clinic 블록으로 감싸면 Clinic이 기존 체크섬 기능을 사용하여 파일을 덮어쓰기 전에 파일이 수동으로 수정되지 않았는지 확인할 수 있습니다.
#ifdef 요령을 사용하는 방법¶
모든 플랫폼에서 사용할 수 있는 것은 아닌 함수를 변환하는 경우, 작업을 조금 더 쉽게 만드는 데 사용할 수 있는 요령이 있습니다. 기존 코드는 아마 다음과 같을 것입니다.:
#ifdef HAVE_FUNCTIONNAME
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */
그리고 맨 아래의 PyMethodDef 구조체에는 다음과 같은 기존 코드가 있을 것입니다:
#ifdef HAVE_FUNCTIONNAME
{'functionname', ... },
#endif /* HAVE_FUNCTIONNAME */
이 경우에는 다음과 같이 impl 함수의 본문을 #ifdef 내부에 넣어야 합니다.:
#ifdef HAVE_FUNCTIONNAME
/*[clinic input]
module.functionname
...
[clinic start generated code]*/
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */
그런 다음 PyMethodDef 구조체에서 이 세 줄을 제거하고 Argument Clinic이 생성한 매크로로 대체하십시오:
MODULE_FUNCTIONNAME_METHODDEF
(이 매크로의 실제 이름은 생성된 코드에서 찾을 수 있습니다. 또는 직접 계산할 수도 있습니다. 블록의 첫 번째 줄에 정의된 함수 이름에서 마침표를 밑줄로 바꾸고, 대문자로 변환한 다음, 끝에 "_METHODDEF"를 추가한 이름입니다.)
어쩌면 HAVE_FUNCTIONNAME이 정의되어 있지 않으면 어떻게 되는지 궁금할 수 있습니다. MODULE_FUNCTIONNAME_METHODDEF 매크로도 정의되지 않습니다!
바로 이 부분에서 Argument Clinic이 매우 영리하게 작동합니다. 실제로 Argument Clinic 블록이 #ifdef에 의해 비활성화될 수 있음을 감지합니다. 그런 경우에는 다음과 같은 약간의 추가 코드를 생성합니다.:
#ifndef MODULE_FUNCTIONNAME_METHODDEF
#define MODULE_FUNCTIONNAME_METHODDEF
#endif /* !defined(MODULE_FUNCTIONNAME_METHODDEF) */
즉, 이 매크로는 항상 작동합니다. 함수가 정의되어 있으면 후행 쉼표를 포함한 올바른 구조로 변환됩니다. 함수가 정의되어 있지 않으면 아무것도 생성되지 않습니다.
하지만 이 때문에 한 가지 까다로운 문제가 생깁니다. “block” 출력 프리셋을 사용할 때 Argument Clinic은 이 추가 코드를 어디에 배치해야 합니까? 출력 블록은 #ifdef에 의해 비활성화될 수 있으므로 그곳에 배치할 수 없습니다. (바로 그것이 핵심입니다!)
이 상황에서 Argument Clinic은 추가 코드를 “buffer” 대상에 기록합니다. 이 때문에 Argument Clinic에서 다음과 같은 경고가 표시될 수 있습니다.
Warning in file "Modules/posixmodule.c" on line 12357:
Destination buffer 'buffer' not empty at end of file, emptying.
이런 경우에는 파일을 열고 Argument Clinic이 파일에 추가한 dump buffer 블록을 찾은 다음(파일의 맨 아래에 있습니다), 해당 매크로가 사용되는 PyMethodDef 구조체 위로 옮기기만 하면 됩니다.
Python 파일에서 Argument Clinic을 사용하는 방법¶
실제로 Argument Clinic을 사용하여 Python 파일을 전처리할 수 있습니다. 물론 출력이 Python 인터프리터에 아무런 의미가 없으므로 Argument Clinic 블록을 사용하는 것은 의미가 없습니다. 하지만 Argument Clinic을 사용하여 Python 블록을 실행하면 Python을 Python 전처리기로 사용할 수 있습니다!
Python 주석은 C 주석과 다르므로 Python 파일에 포함된 Argument Clinic 블록은 약간 다른 형태입니다. 다음과 같은 형태입니다.
#/*[python input]
#print("def foo(): pass")
#[python start generated code]*/
def foo(): pass
#/*[python checksum:...]*/
Limited C API를 사용하는 방법¶
Argument Clinic input이 #define Py_LIMITED_API를 포함하는 C 소스 파일 안에 있으면 Argument Clinic은 인자를 구문 분석하기 위해 Limited API를 사용하는 C 코드를 생성합니다. 이렇게 하면 생성된 코드가 비공개 함수를 사용하지 않는다는 장점이 있습니다. 하지만 이 때문에 경우에 따라 Argument Clinic이 효율성이 떨어지는 코드를 생성할 수도 있습니다. 성능 저하의 정도는 매개변수(타입, 개수 등)에 따라 달라집니다.
Added in version 3.13.
생성된 시그니처를 재정의하는 방법¶
@text_signature 지시어를 사용하여 독스트링에 기본적으로 생성되는 시그니처를 재정의할 수 있습니다. 이는 Argument Clinic이 처리할 수 없는 복잡한 시그니처에 유용할 수 있습니다. @text_signature 지시어는 사용자 지정 시그니처를 나타내는 문자열 하나를 인자로 받습니다. 제공된 시그니처는 생성된 독스트링에 그대로 복사됩니다.
관련 Objects/codeobject.c의 예시:
/*[clinic input]
@text_signature "($self, /, **changes)"
code.replace
*
co_argcount: int(c_default="self->co_argcount") = unchanged
co_posonlyargcount: int(c_default="self->co_posonlyargcount") = unchanged
# etc ...
Return a copy of the code object with new values for the specified fields.
[clinic start generated output]*/
생성된 독스트링은 다음과 같습니다:
replace($self, /, **changes)
--
Return a copy of the code object with new values for the specified fields.
Argument Clinic에서 임계 영역을 사용하는 방법¶
@critical_section 지시어를 사용하여 Argument Clinic이 “impl” 함수 호출을 “Python 임계 영역”으로 감싸도록 지시할 수 있습니다. 전역 인터프리터 잠금(“GIL”)이 없는 CPython 빌드에서는 스레드 간 데드록을 유발하지 않으면서 스레드 안전성을 확보하기 위해 임계 영역이 필요합니다. 임계 영역에 진입하면 데코레이션된 함수의 첫 번째 인자와 연결된 객체별 잠금을 획득합니다. 임계 영역을 벗어날 때 잠금이 해제됩니다.
GIL이 있는 CPython 빌드에서 Python 임계 영역은 아무 작업도 수행하지 않습니다. 임계 영역에 관한 자세한 내용은 Include/internal/pycore_critical_section.h 및 PEP 703 문서를 참조하십시오.
관련 Modules/_io/bufferedio.c에서 가져온 예:
/*[clinic input]
@critical_section
_io._Buffered.close
[clinic start generated code]*/
생성된 글루 코드는 다음과 같습니다:
static PyObject *
_io__Buffered_close(buffered *self, PyObject *Py_UNUSED(ignored))
{
PyObject *return_value = NULL;
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io__Buffered_close_impl(self);
Py_END_CRITICAL_SECTION();
return return_value;
}
C 변수 이름을 @critical_section 지시어의 인자로 제공하여 객체를 한두 개 더 잠글 수 있습니다. 관련 Modules/_weakref.c에서 가져온 이 예는 추가 인자 하나(object라는 C 변수)를 받습니다:
/*[clinic input]
@critical_section object
_weakref.getweakrefcount -> Py_ssize_t
object: object
/
Return the number of weak references to 'object'.
[clinic start generated code]*/
생성된 글루 코드는 다음과 같습니다:
static PyObject *
_weakref_getweakrefs(PyObject *module, PyObject *object)
{
PyObject *return_value = NULL;
Py_BEGIN_CRITICAL_SECTION(object);
return_value = _weakref_getweakrefs_impl(module, object);
Py_END_CRITICAL_SECTION();
return return_value;
}
Added in version 3.13.
PyGetSetDef (“getter/setter”) 함수를 선언하는 방법¶
“getter”와 “setter”는 PyGetSetDef 구조체에 정의된 C 함수로, 클래스에 property와 유사한 접근을 가능하게 합니다. @getter 및 @setter 지시어를 사용하여 Argument Clinic으로 “impl” 함수를 생성할 수 있습니다.
관련 Modules/_io/textio.c에서 가져온 이 예는 @getter 및 @setter를 @critical_section 지시어와 함께 사용하는 방법을 보여 줍니다(이 지시어는 스레드 간 데드록을 유발하지 않으면서 스레드 안전성을 확보합니다):
/*[clinic input]
@critical_section
@getter
_io.TextIOWrapper._CHUNK_SIZE
[clinic start generated code]*/
/*[clinic input]
@critical_section
@setter
_io.TextIOWrapper._CHUNK_SIZE
[clinic start generated code]*/
생성된 글루 코드는 다음과 같습니다:
static PyObject *
_io_TextIOWrapper__CHUNK_SIZE_get(PyObject *self, void *Py_UNUSED(context))
{
PyObject *return_value = NULL;
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_TextIOWrapper__CHUNK_SIZE_get_impl((textio *)self);
Py_END_CRITICAL_SECTION();
return return_value;
}
static int
_io_TextIOWrapper__CHUNK_SIZE_set(PyObject *self, PyObject *value, void *Py_UNUSED(context))
{
int return_value;
if (value == NULL) {
PyErr_Format(PyExc_AttributeError,
"attribute '_CHUNK_SIZE' of '%.100s' objects cannot be deleted",
Py_TYPE(self)->tp_name);
return -1;
}
Py_BEGIN_CRITICAL_SECTION(self);
return_value = _io_TextIOWrapper__CHUNK_SIZE_set_impl((textio *)self, value);
Py_END_CRITICAL_SECTION();
return return_value;
}
참고
getter와 setter는 별도의 함수로 선언해야 합니다. “setter”의 value 매개변수는 Argument Clinic이 암시적으로 추가합니다. @getter에 독스트링을 추가하여 프로퍼티의 독스트링을 만들 수 있습니다. 동일한 어트리뷰트의 접근자는 C 기본 이름을 공유해야 하며, 동일한 접근자를 두 번 선언하면 오류가 발생합니다.
관련 PyGetSetDef의 setter 슬롯은 어트리뷰트를 설정할 때와 삭제할 때 모두 사용됩니다. 어트리뷰트를 삭제하려면 NULL을 값으로 하여 setter를 호출합니다. 위에서 볼 수 있듯이, 생성된 setter는 “impl” 함수를 호출하기 전에 AttributeError를 발생시켜 삭제를 거부합니다.
어트리뷰트를 삭제할 수 있다면 @setter 뒤에 @deleter 지시어를 추가하십시오. 그러면 Objects/funcobject.c에서 가져온 다음 예처럼 “impl” 함수가 NULL 값과 함께 호출되며 이 경우를 처리할 책임을 집니다:
/*[clinic input]
@critical_section
@setter
@deleter
function.__annotations__
[clinic start generated code]*/
static int
function___annotations___set_impl(PyFunctionObject *self, PyObject *value)
{
if (value == Py_None)
value = NULL;
/* Legal to del f.func_annotations.
* Can only set func_annotations to NULL (through C api)
* or a dict. */
if (value != NULL && !PyDict_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"__annotations__ must be set to a dict object");
return -1;
}
...
}
그러면 구현은 property로 데코레이션된 Python 메서드와 동일하게 작동합니다:
>>> import sys, _io
>>> a = _io.TextIOWrapper(sys.stdout)
>>> a._CHUNK_SIZE
8192
>>> a._CHUNK_SIZE = 30
>>> a._CHUNK_SIZE
30
Added in version 3.13.
매개변수를 위치 또는 키워드로 전달하는 것을 지원 중단 예정으로 지정하는 방법¶
Argument Clinic은 위치 또는 키워드 parameters에 대한 arguments를 위치나 키워드로 전달하는 것을 지원 중단 예정으로 지정하는 코드를 생성할 수 있는 문법을 제공합니다. 예를 들어 위치 전용 매개변수 a, 위치 또는 키워드 매개변수 b, c, d, 그리고 키워드 전용 매개변수 e까지 총 다섯 개의 매개변수를 갖는 모듈 수준 함수 foo.myfunc()가 있다고 가정하겠습니다:
/*[clinic input]
module foo
myfunc
a: int
/
b: int
c: int
d: int
*
e: int
[clinic start generated output]*/
이제 b 매개변수를 위치 전용으로, d 매개변수를 키워드 전용으로 만들려고 합니다. 그러나 Python의 하위 호환성 정책(PEP 387 참조)에 따라 이러한 변경을 적용하려면 두 번의 릴리스를 기다려야 합니다. 이 예제에서는 Python 3.12의 개발 단계에 있다고 가정합니다: 즉, b 매개변수에 대한 인자가 키워드로 전달되거나 d 매개변수에 대한 인자가 위치로 전달될 때마다 Python 3.12에서 폐지 예정 경고를 도입할 수 있으며, 빨라도 Python 3.14에서 각각 위치 전용과 키워드 전용으로 만들 수 있습니다.
Argument Clinic을 사용하여 [from ...] 구문으로 원하는 폐지 예정 경고를 발생시킬 수 있습니다. b 매개변수 바로 아래에 / [from 3.14] 줄을 추가하고 d 매개변수 바로 위에 * [from 3.14] 줄을 추가하면 됩니다.:
/*[clinic input]
module foo
myfunc
a: int
/
b: int
/ [from 3.14]
c: int
* [from 3.14]
d: int
*
e: int
[clinic start generated output]*/
다음으로 Argument Clinic 코드(make clinic)를 다시 생성하고 새로운 동작에 대한 단위 테스트를 추가하십시오.
이제 생성된 코드는 parameter d의 argument가 위치 인자로 전달되거나(예: myfunc(1, 2, 3, 4, e=5)), 매개변수 b의 인자가 키워드로 전달되면(예: myfunc(1, b=2, c=3, d=4, e=5)) DeprecationWarning을 표시합니다. 폐지 예정 기간이 끝났을 때, 즉 지정된 Python 버전의 알파 단계가 시작되었을 때 Argument Clinic 입력에서 [from ...] 줄이 제거되지 않은 경우 컴파일러 경고를 발생시키기 위한 C 전처리기 지시문도 생성됩니다.
예제로 돌아가 2년 뒤로 건너뛰어 봅시다. 이제 Python 3.14 개발은 알파 단계에 들어섰지만, myfunc()의 Argument Clinic 코드를 업데이트하는 것을 완전히 잊었습니다! 다행히 이제 컴파일러 경고가 생성됩니다.
In file included from Modules/foomodule.c:139:
Modules/clinic/foomodule.c.h:139:8: warning: In 'foomodule.c', update the clinic input of 'mymod.myfunc'. [-W#warnings]
# warning "In 'foomodule.c', update the clinic input of 'mymod.myfunc'. [-W#warnings]"
^
이제 a를 위치 전용으로, c를 키워드 전용으로 만들어 폐지 예정 단계를 마무리합니다; b 아래의 / [from ...] 줄을 a 아래 줄의 /로 바꾸고, d 위의 * [from ...] 줄을 e 위 줄의 *로 바꿉니다.:
/*[clinic input]
module foo
myfunc
a: int
b: int
/
c: int
*
d: int
e: int
[clinic start generated output]*/
마지막으로 make clinic을 실행하여 Argument Clinic 코드를 다시 생성하고, 새로운 동작을 반영하도록 단위 테스트를 업데이트하십시오.
참고
알파 및 베타 단계에서 입력 블록을 업데이트하는 것을 잊으면 릴리스 후보 단계가 시작될 때 컴파일러 경고가 컴파일러 오류로 바뀝니다.