HOWTO по Клинике Cпоров
автор: | Ларри Гастингс |
---|
Аннотация
Клиника Споров — это препроцессор для C файлов CPython. Её цель — автоматизировать создание шаблона, связанного с написанием кода парсинга аргументов для «встроенных» программ. В этом документе рассказывается, как преобразовать вашу первую C функцию для работы с Клиникой Споров, а затем представлены некоторые дополнительные темы по использованию Клиники Споров.
В настоящее время Клиника Споров считается внутренней для CPython. Её использование не поддерживается для файлов за пределами CPython, и не даётся никаких гарантий относительно обратной совместимости для будущих версий. Другими словами: если вы поддерживаете внешнее C расширение для CPython, вы можете поэкспериментировать с Клиникой Споров в собственном коде. Но версия Клиники Споров, которая поставляется со следующей версией CPython может быть полностью несовместима и разрушает весь ваш код.
Цели Клиники Споров
Основная цель Клиники Споров — взять на себя ответственность за весь код
парсинга аргументов внутри CPython. Это означает, что когда вы конвертируете
функцию для работы с Клиникой Споров, функция больше не должна выполнять
какой-либо собственный парсинг аргументов, — сгенерированный код
Клиники Споров, должен быть для вас «черным ящиком», где CPython вызывается в
вверху, и ваш код вызывается внизу, а PyObject *args
(и, возможно,
PyObject *kwargs
) волшебным образом преобразуется в нужные переменные и типы C.
Чтобы Клиника Споров могла выполнить свою основную задачу, она должна быть простой в использовании. В настоящее время работа с библиотекой синтаксического анализа аргументов CPython является рутинной работой, требующей сохранения избыточной информации в удивительном количестве мест. Когда вы используете Клинику Споров, вам не нужно повторяться.
Очевидно, что никто не захочет использовать Клинику Споров, если она не решает их проблему, без создания собственных новых проблем. Поэтому крайне важно, чтобы Клиника Споров сгенерировала правильный код. Было бы неплохо, если бы код тоже был быстрее, но, по крайней мере, он не должен приводить к серьёзному снижению скорости. (В конце концов, Клиника Споров должна сделать возможное значительное ускорение, мы могли бы переписать её генератором кода для создания индивидуального кода синтаксического анализа аргументов, вместо того, чтобы вызывать универсальную библиотеку парсинга аргументов CPython. Это сделало бы возможным парсинг аргументов максимально быстрым!)
Кроме того, Клиника Споров должна быть достаточно гибкой, чтобы работать с любым подходом к парсингу аргументов. В Python существуют функции с очень странным поведением при синтаксическом анализе; цель Клиники Споров — поддерживать их всех.
Наконец, первоначальной мотивацией для Клиники Споров было обеспечение «сигнатур» интроспекции для встроенных функций CPython. Раньше функции запросов самоанализа вызывали исключение, если вы передали встроенный. С Клиникой Споров это в прошлом!
При работе с Клиникой Споров следует помнить об одной идее: чем больше информации вы передадите ей, тем лучше она сможет выполнять свою работу. По общему признанию, сейчас Клиника Споров относительно проста. Но по мере развития она будет становиться всё более изощрённой, и она должна уметь делать много интересных и умных вещей со всей информацией, которую вы ей предоставляете.
Основные понятия и использование
Клиника Споров поставляется с CPython; вы найдете её в
Tools/clinic/clinic.py
. Если вы запустите этот сценарий, указав файл C в
качестве аргумента :
$ python3 Tools/clinic/clinic.py foo.c
Клиника Споров просканирует файл в поисках строк, которые выглядят следующим образом:
/*[clinic input]
Когда она её находит, она считывает все до строки, которая выглядит примерно так:
[clinic start generated code]*/
Всё, что находится между этими двумя строками, предоставляются для Клиники Споров. Все эти строки, включая начальную и конечную строки комментариев, вместе называются «блоком» Клиники Споров.
Когда Клиника Споров распарсит один из этих блоков, она генерирует выходные данные. Этот вывод перезаписывается в файл C сразу после блока, за ним следует комментарий, содержащий контрольную сумму. Блок Клиники Споров теперь выглядит так:
/*[clinic input]
... clinic input goes here ...
[clinic start generated code]*/
... clinic output goes here ...
/*[clinic end generated code: checksum=...]*/
Если вы запустите Клинику Споров для того же файла во второй раз, Клиника Споров отбросит старый вывод и запишет новый вывод с новой строкой контрольной суммы. Однако, если вход не изменился, выход тоже не изменится.
Вы никогда не должны изменять выходную часть блока Клиники Споров. Вместо этого изменяйте ввод, пока он не предоставит желаемый результат. (Это цель контрольной суммы — определить, изменил ли кто-то вывод, поскольку эти изменения будут потеряны в следующий раз, когда Клиника Споров запишет новый вывод.)
Для ясности, далее приведена терминология, которую мы будем использовать в Клинике Споров:
- Первая строка комментария (
/*[clinic input]
) — это стартовая строка. - Последняя строка начального комментария (
[clinic start generated code]*/
) — это конечная строка. - Последняя строка (
/*[clinic end generated code: checksum=...]*/
) — это строка с контрольной суммой. - Между начальной и конечной строками находится ввод.
- Между конечной строкой и строкой контрольной суммы находится вывод.
- Весь текст в совокупности, от начальной строки до строки контрольной суммы включительно, представляет собой блок. (Блок, который не был успешно обработан Клиникой Споров, ещё не имеет вывода или строки контрольной суммы, но по-прежнему считается блоком.)
Преобразование первой функции
Лучший способ понять, как работает Клиника Споров — это преобразовать функцию для работы с ней. Итак, вот минимум шагов, которые вам нужно выполнить, чтобы преобразовать функцию для работы с Клиникой Споров. Обратите внимание, что для регистрируемого в CPython кода, вам действительно следует пойти дальше по преобразованию, используя некоторые из расширенных концепций, их вы увидите позже в документе (например, «обратные конвертеры» и «самоконвертеры»). Но мы сделаем это просто для данного HOWTO, чтобы вы могли учиться.
Давайте погрузимся!
Убедитесь, что вы работаете с недавно обновленной веткой CPython.
Найдите встроенный Python, который вызывает
PyArg_ParseTuple()
илиPyArg_ParseTupleAndKeywords()
и ещё не был преобразован для работы с Клиникой Споров. В моем примере я использую_pickle.Pickler.dump()
.Если при вызове функции
PyArg_Parse
используются любые из следующих единиц формата :O& O! es es# et et#
или, если она содержит несколько вызовов
PyArg_ParseTuple()
, вам следует выбрать другую функцию. Клиника Споров поддерживает все эти сценарии. Но это сложные темы — давайте сделаем что-нибудь попроще для вашей первой функции.Кроме того, если у функции несколько вызовов
PyArg_ParseTuple()
илиPyArg_ParseTupleAndKeywords()
, где она поддерживает разные типы для одного и того же аргумента, или если функция использует что-то помимо функций PyArg_Parse для анализа своих аргументов, она, вероятно, не подходит для преобразования в Клинике Споров. Клиника Споров не поддерживает общие функции или полиморфные параметры.Добавьте следующий шаблон над функцией, создав блок:
/*[clinic input] [clinic start generated code]*/
Вырежьте строку документации и вставьте её между строками
[clinic]
, удалив весь мусор, который делает её строкой C с правильными кавычками. Когда вы закончите, у вас должен остаться только текст на левом поле, длина строки не должна превышать 80 символов. (Клиника Споров сохранит отступы внутри строки документации.)Если в старой строке документации была первая строка, которая выглядела как сигнатура функции, выбросьте эту строку. (Строке документации она больше не нужна — когда вы в будущем будете использовать
help()
в своей встроенной программе, первая строка будет построена автоматически на основе сигнатуры функции.)Пример:
/*[clinic input] Write a pickled representation of obj to the open file. [clinic start generated code]*/
Если в вашей строке документации нет строки «резюме», Клиника Споров пожалуется. Так что давайте убедимся, что оно есть. Строка «резюме» должна быть абзацем, состоящим из одной строки из 80 столбцов в начале строки документации.
(Строка документации нашего примера состоит исключительно из строки резюме, поэтому пример кода не нужно изменять на этом этапе.)
Над строкой документации вводится имя функции, следующей за пустой строкой. Это должно быть имя функции Python и полный путь к функции, разделенный точками — она должна начинаться с имени модуля, включать все подмодули, а если функция является методом класса, она должна включать имя класса тоже.
Пример:
/*[clinic input] _pickle.Pickler.dump Write a pickled representation of obj to the open file. [clinic start generated code]*/
Если это первый раз, когда модуль или класс использовались с Клиникой Споров в C файле, вы должны объявить модуль и/или класс. Правильная гигиена Клиники Споров предпочитает объявлять их в отдельном блоке где-то в верхней части файла C, точно так же, как файлы include и static идут вверху. (В нашем примере кода мы просто покажем два блока рядом друг с другом.)
Имя класса и модуля должно совпадать с тем, которое видит Python. Проверьте имя, определенное в
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 = default_value
Если параметр содержит значение по умолчанию, добавьте его после преобразователя :
name_of_parameter: converter = default_value
Поддержка «значений по умолчанию» в Клинике Споров довольно сложна; пожалуйста см. раздел ниже о значениях по умолчанию для получения дополнительной информации.
Добавьте пустую строку под параметрами.
Что такое «конвертер»? Он устанавливает как тип переменной, используемой в C, так и метод преобразования значения Python в значение C во время выполнения. Сейчас вы собираетесь использовать так называемый «устаревший конвертер» — удобный синтаксис, призванный упростить перенос старого кода в Клинику Споров.
Для каждого параметра скопируйте «единицу формата» для этого параметра из аргумента формата
PyArg_Parse()
и укажите его в качестве преобразователя в виде строки в кавычках. («Единица формата» — это формальное имя подстроки, состоящей из одного-трёх символов, в параметреformat
, который сообщает функции синтаксического анализа аргументов, каков тип переменной и как ее преобразовать. Подробнее о единицах формата см. Анализ аргументов и сборка значений. )Для единиц формата с несколькими символами, таких как
z#
, используйте всю строку из двух или трёх символов.Пример:
/*[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]*/
Если в строке формата вашей функции указано
|
, что означает, что некоторые параметры имеют значения по умолчанию, вы можете игнорировать это. Клиника Споров определяет, какие параметры являются необязательными, в зависимости от того, имеют ли они значения по умолчанию.Если ваша функция содержит
$
в строке формата, что означает, что она принимает только ключевые аргументы, укажите*
в отдельной строке перед первым только ключевым аргументом с таким же отступом, как и строки параметров.(
_pickle.Pickler.dump
не содержит ни того, ни другого, поэтому наш образец не изменился.)Если существующая функция C вызывает
PyArg_ParseTuple()
(в отличие отPyArg_ParseTupleAndKeywords()
), то все её аргументы являются только позиционными.Чтобы пометить все параметры как позиционные в Клинике Споров, добавьте
/
в отдельную строку после последнего параметра с таким же отступом, как и строки параметров.В настоящее время это всё или ничего; либо все параметры являются позиционными, либо ни один из них не является. (В будущем Клиника Споров может ослабить это ограничение.)
Пример:
/*[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]*/
Очевидно, что если Клиника Споров не выдала никаких выходных данных, это потому, что она обнаружила ошибку во входных данных. Продолжайте исправлять ошибки и повторяйте попытки, пока Клиника Споров не обработает ваш файл без жалоб.
Для удобства чтения большая часть связующего кода была сгенерирована в файл
.c.h
. Вам нужно будет включить это в исходный файл.c
, обычно сразу после блока клинического модуля:#include "clinic/_pickle.c.h"
Ещё раз проверьте, что созданный Клиникой Споров код для синтаксического анализа аргументов выглядит в основном так же, как и существующий код.
Во-первых, убедитесь, что в обоих местах используется одна и та же функция анализа аргументов. Существующий код должен вызывать
PyArg_ParseTuple()
илиPyArg_ParseTupleAndKeywords()
; убедитесь, что код, сгенерированный Клиникой Споров, вызывает точно такую же функцию.Во-вторых, строка формата, передаваемая в
PyArg_ParseTuple()
илиPyArg_ParseTupleAndKeywords()
, должна быть точно такой же, как написанная вручную в существующей функции, с точностью до двоеточия или точки с запятой.(Клиника Споров всегда генерирует свои строки формата с
:
, за которым следует имя функции. Если строка формата существующего кода заканчивается на;
, чтобы облегчить использование, это изменение безвредно — не беспокойтесь об этом.)В-третьих, для параметров, единицы формата которых требуют двух аргументов (например, переменной длины, строки кодировки или указателя на функцию преобразования), убедитесь, что второй аргумент в точности равен между двумя вызовами.
В-четвертых, внутри выходной части блока вы найдете макрос препроцессора, определяющий соответствующую статическую структуру
PyMethodDef
для этой встроенной функции:#define __PICKLE_PICKLER_DUMP_METHODDEF \ {"dump", (PyCFunction)__pickle_Pickler_dump, METH_O, __pickle_Pickler_dump__doc__},
Статическая структура должна быть точно такой же, как существующая статическая структура
PyMethodDef
для этой встроенной программы.Если какие-либо из этих элементов как-нибудь отличаются, настройте спецификацию функции Клиники Споров и повторно запустите
Tools/clinic/clinic.py
, пока они не станут одинаковыми.Обратите внимание, что последняя строка вывода — это объявление вашей функции «impl». Вот где идёт встроенная реализация. Удалите существующий прототип функции, которую вы изменяете, но оставьте открывающую фигурную скобку. Теперь удалите код синтаксического анализа аргументов и объявления всех переменных, в которые она выгружает аргументы. Обратите внимание, что аргументы Python теперь являются аргументами функции impl; если реализация использовала разные имена для этих переменных, исправьте это.
Повторим ещё раз, просто потому, что это немного странно. Теперь ваш код должен выглядеть так:
static return_type your_function_impl(...) /*[clinic end generated code: checksum=...]*/ { ...
Клиника Споров сгенерировала строку контрольной суммы и прототип функции чуть выше неё. Вы должны написать открывающую (и закрывающую) фигурные скобки для функции и реализации внутри.
Пример:
/*[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 */ };
Скомпилируйте, а затем запустите соответствующие части набора регрессионных тестов. Это изменение не должно приводить к появлению каких-либо новых предупреждений или ошибок во время компиляции, а также не должно быть видимых извне изменений в поведении Python.
Ну, за исключением одного отличия:
inspect.signature()
, запущенный в вашей функции, теперь должен предоставить правильную сигнатуру!Поздравляем, вы перенесли свою первую функцию для работы с Клиникой Споров!
Продвинутые темы
Теперь, когда у вас есть некоторый опыт работы с Клиникой Споров, пришло время для некоторых продвинутых тем.
Символические значения по умолчанию
Значение по умолчанию, которое вы указываете для параметра, не может быть произвольным выражением. В настоящее время явно поддерживаются :
- Числовые константы (целые числа и числа с плавающей запятой)
- Строковые константы
True
,False
иNone
- Простые символьные константы, такие как
sys.maxsize
, которые должны начинаться с имени модуля
Если вам интересно, это реализовано в from_builtin()
в файле Lib/inspect.py
.
(В будущем может потребоваться ещё более сложная разработка, чтобы можно было
использовать полные выражения, такие как CONSTANT - 1
.)
Переименование функций и переменных C, созданных Клиникой Споров
Клиника Споров автоматически называет функции, которые она генерирует для вас.
Иногда это может вызвать проблему, если сгенерированное имя конфликтует с
именем существующей функции C. Есть простое решение: переопределить имена,
используемые для функций C. Просто добавьте ключевое слово "as"
в строку
объявления функции, а затем имя функции, которую вы хотите использовать.
Клиника Споров будет использовать это имя функции для базовой
(сгенерированной) функции, затем добавит "_impl"
в конец и будет использовать
это имя для имени функции impl.
Например, если бы мы хотели переименовать имена функций C, сгенерированные для
pickle.Pickler.dump
, это выглядело бы так:
/*[clinic input]
pickle.Pickler.dump as pickler_dumper
...
Базовая функция теперь будет называться pickler_dumper()
, а функция impl
теперь будет называться pickler_dumper_impl()
.
Точно так же у вас может возникнуть проблема, когда вы хотите дать параметру
имя Python, но это имя может быть неудобным в C. Клиника Споров
позволяет вам давать параметру разные имена в Python и в C, используя тот же
синтаксис "as"
:
/*[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()
, но это скоро изменится.
Дополнительные группы
У некоторых устаревших функций сложный подход к синтаксическому анализу
своих аргументов: они подсчитывают количество позиционных аргументов, а затем
используют оператор switch
для вызова одного из нескольких различных
вызовов PyArg_ParseTuple()
в зависимости от количества позиционных
аргументов. (Эти функции не могут принимать аргументы, состоящие только из
ключевых слов.) этот подход использовался для имитации необязательных
аргументов ещё до создания PyArg_ParseTupleAndKeywords()
.
Хотя функции, использующие этот подход, часто можно преобразовать для
использования PyArg_ParseTupleAndKeywords()
, дополнительных аргументов
и значений по умолчанию, это не всегда возможно. У некоторых из этих устаревших
функций есть поведение, которое PyArg_ParseTupleAndKeywords()
напрямую
не поддерживает. Наиболее очевидным примером является встроенная функция
range()
, у которой есть необязательный аргумент слева от
обязательного аргумента! Другой пример — curses.window.addch()
, у которого
есть группа всегда указываемых вместе двух аргументов.
(Аргументы называются x
и y
; если вы вызываете функцию, передающуюся в
x
, вы также должны передать y
; и если вы не передадите x
, вы также
не сможете передать y
.)
В любом случае целью Клиники Споров является поддержка синтаксического анализа аргументов для всех существующих встроенных команд CPython без изменения их семантики. Поэтому Клиника Споров поддерживает этот альтернативный подход к синтаксическому анализу с использованием так называемого необязательных групп. Необязательные группы — это группы аргументов, которые должны передаваться вместе. Они могут быть слева или справа от требуемых аргументов. Их можно использовать только с позиционными параметрами.
Примечание
Дополнительные группы предназначены только для использования при преобразовании
функций, которые выполняют несколько вызовов, в PyArg_ParseTuple()
!
Функции, использующие любой другой подход к синтаксическому анализу аргументов,
почти никогда не следует преобразовывать в Клинике Споров с использованием
дополнительных групп. Функции, использующие необязательные группы, в настоящее
время не могут иметь точных сигнатур в 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, представляющую группу, будет
передан один дополнительный параметр. У параметра будет тип int с именем
group_{direction}_{number}
, где{direction}
— это либоright
, либоleft
, в зависимости от того, находится ли группа до или после требуемых параметров, а{number}
— это монотонно возрастающее число (начиная с 1), указывающее, как далеко группа находится от обязательные параметры. При вызове impl этот параметр будет установлен в ноль, если группа не использовалась, и в ненулевое значение, если группа использовалась. (Под используемым или неиспользованным я подразумеваю, получили ли параметры аргументы в этом вызове.) - Если нет обязательных аргументов, необязательные группы будут вести себя так, как если бы они находились справа от требуемых аргументов.
- В случае неоднозначности код разбора аргументов отдает предпочтение параметрам слева (перед обязательными параметрами).
- Дополнительные группы могут содержать только позиционные параметры.
- Дополнительные группы предназначены только для устаревшего кода. Пожалуйста, не используйте необязательные группы для нового кода.
Использование реальных конвертеров Клиники Споров вместо «устаревших конвертеров»
Чтобы сэкономить время и свести к минимуму объем обучения, который вам нужно изучить для достижения первого порта в Клинике Споров, в приведённом выше пошаговом руководстве предлагается использовать «устаревшие конвертеры». «Устаревшие конвертеры» — это удобство, специально разработанное для облегчения переноса существующего кода в Клинике Споров. И чтобы было ясно, их использование допустимо при переносе кода на Python 3.4.
Однако в долгосрочной перспективе мы, вероятно, захотим, чтобы все наши блоки использовали реальный синтаксис Клиники Споров для конвертеров. Почему? Пара причин:
- Правильные конвертеры намного легче читать и понятнее по своему назначению.
- Существуют некоторые единицы формата, которые не поддерживаются как «устаревшие конвертеры», поскольку они требуют аргументов, а устаревший синтаксис преобразователя не поддерживает указание аргументов.
- В будущем у нас может появиться новая библиотека парсинга аргументов, которая не
ограничивается тем, что поддерживает
PyArg_ParseTuple()
; гибкость не будет доступна для параметров, использующих устаревшие конвертеры.
Поэтому, если вы не против немного дополнительных усилий, воспользуйтесь обычными конвертерами вместо устаревших конвертеров.
В двух словах, синтаксис конвертеров Клиники Споров (не устаревших)
выглядит как вызов Python функции. Однако, если у функции нет явных аргументов
(все функции принимают значения по умолчанию), вы можете опустить круглые
скобки. Таким образом, bool
и bool()
— это абсолютно одинаковые
конвертеры.
Все аргументы конвертеров Клиники Споров только ключевые. Все конвертеры Клиники Споров принимают следующие аргументы:
c_default
- Значение по умолчанию для этого параметра, если оно определено в C. В частности, это будет инициализатор для переменной, объявленной в «функции парсинга». См. раздел значений по умолчанию, чтобы узнать, как это использовать. Указывается в виде строки.
annotation
- Значение аннотации для этого параметра. В настоящее время не поддерживается, поскольку PEP 8 требует, чтобы библиотека Python не могла использовать аннотации.
Кроме того, некоторые конвертеры принимают дополнительные аргументы. Вот список этих аргументов с указанием их значений:
accept
Набор типов Python (и, возможно, псевдотипов); это ограничивает допустимый аргумент Python значениями этих типов. (Это средство не общего назначения; как правило, оно поддерживает только определенные списки типов, как показано в устаревшей таблице преобразователя.)
Чтобы принять
None
, добавьте в наборNoneType
.bitwise
- Поддерживается только для целых чисел без знака. Собственное целочисленное значение этого аргумента Python будет записано в параметр без какой-либо проверки диапазона, даже для отрицательных значений.
converter
- Поддерживается только конвертером
object
. Задаёт имя C «функции конвертера», которое будет использоваться для преобразования объекта в собственный тип.encoding
- Поддерживается только для строк. Задаёт кодировку для использования при преобразовании этой строки из значения Python str (Юникод) в значение C
char *
.subclass_of
- Поддерживается только для конвертера
object
. Требует, чтобы значение Python было подклассом типа Python, как выражено в C.type
- Поддерживается только для конвертеров
object
иself
. Задает тип C, который будет использоваться для объявления переменной. Значение по умолчанию —"PyObject *"
.zeroes
- Поддерживается только для строк. Если истинно, внутри значения разрешены встроенные байты NUL (
'\\0'
). Длина строки будет передана в функцию impl сразу после параметра строки как параметр с именем<parameter_name>_length
.
Обратите внимание, что не все возможные комбинации аргументов будут работать.
Обычно эти аргументы реализуются PyArg_ParseTuple
единиц формата
с определенным поведением. Например, в настоящее время вы не можете вызвать
unsigned_short
, не указав также bitwise=True
. Хотя вполне
разумно думать, что это сработает, данная семантика не сопоставляется ни с одной
существующей единицей формата. Так что Клиника Споров не поддерживает это.
(Или, по крайней мере, пока.)
Ниже приведена таблица, в которой показано преобразование устаревших конвертеров в настоящие конвертеры Клиники Споров. Слева находится устаревший конвертер, справа текст, которым вы его заменили.
'B' |
unsigned_char(bitwise=True) |
'b' |
unsigned_char |
'c' |
char |
'C' |
int(accept={str}) |
'd' |
double |
'D' |
Py_complex |
'es' |
str(encoding='name_of_encoding') |
'es#' |
str(encoding='name_of_encoding', zeroes=True) |
'et' |
str(encoding='name_of_encoding', accept={bytes, bytearray, str}) |
'et#' |
str(encoding='name_of_encoding', accept={bytes, bytearray, str}, zeroes=True) |
'f' |
float |
'h' |
short |
'H' |
unsigned_short(bitwise=True) |
'i' |
int |
'I' |
unsigned_int(bitwise=True) |
'k' |
unsigned_long(bitwise=True) |
'K' |
unsigned_long_long(bitwise=True) |
'l' |
long |
'L' |
long long |
'n' |
Py_ssize_t |
'O' |
object |
'O!' |
object(subclass_of='&PySomething_Type') |
'O&' |
object(converter='name_of_c_function') |
'p' |
bool |
'S' |
PyBytesObject |
's' |
str |
's#' |
str(zeroes=True) |
's*' |
Py_buffer(accept={buffer, str}) |
'U' |
unicode |
'u' |
Py_UNICODE |
'u#' |
Py_UNICODE(zeroes=True) |
'w*' |
Py_buffer(accept={rwbuffer}) |
'Y' |
PyByteArrayObject |
'y' |
str(accept={bytes}) |
'y#' |
str(accept={robuffer}, zeroes=True) |
'y*' |
Py_buffer |
'Z' |
Py_UNICODE(accept={str, NoneType}) |
'Z#' |
Py_UNICODE(accept={str, NoneType}, zeroes=True) |
'z' |
str(accept={str, NoneType}) |
'z#' |
str(accept={str, NoneType}, zeroes=True) |
'z*' |
Py_buffer(accept={buffer, str, NoneType}) |
В качестве примера приведём наш образец 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
. Их
поведение по умолчанию выполняет проверку диапазона значения, и они не
принимают отрицательные числа. Вы просто не можете этого сделать с устаревшим
конвертером!
Клиника Споров покажет вам все доступные конвертеры. Для каждого конвертера он
покажет вам все параметры, которые он принимает, а также значение по умолчанию
для каждого параметра. Просто запустите Tools/clinic/clinic.py
--converters
, чтобы увидеть полный список.
Py_buffer
При использовании конвертера Py_buffer
(или устаревших конвертеров
's*'
, 'w*'
, '*y'
или 'z*'
) вы не должны вызывать в
предоставленном буфере. Клиника Споров генерирует код, который делает это за
вас (в функции синтаксического анализа).
Расширенные конвертеры
Помните те единицы формата, которые вы пропустили в первый раз, потому что они были продвинутыми? Вот как с этим справиться.
Хитрость в том, что все эти единицы формата принимают аргументы — либо функции
преобразования, либо типы, либо строки, определяющие кодировку. (Но «устаревшие
конвертеры» не поддерживают аргументы. Вот почему мы пропустили их для
вашей первой функции.) Аргумент, который вы указали для единицы формата, теперь
является аргументом для преобразователя; этот аргумент либо converter
(для O&
), либо subclass_of
(для O!
), либо encoding
(для всех
единиц формата, начинающихся с e
).
При использовании subclass_of
вы также можете использовать другой
настраиваемый аргумент для object()
: type
, который позволяет вам
установить тип, фактически используемый для параметра. Например, если вы хотите
убедиться, что объект является подклассом PyUnicode_Type
, вы, вероятно,
захотите использовать конвертер
object(type='PyUnicodeObject *', subclass_of='&PyUnicode_Type')
.
Одна из возможных проблем с использованием Клиники Споров: она снижает
возможную гибкость для единиц формата, начиная с e
. При написании вызова
PyArg_Parse
вручную, вы теоретически можете решить во время выполнения,
какую строку кодирования передать в PyArg_ParseTuple()
. Но теперь эта
строка должна быть жестко закодирована во время предварительной обработки
аргументов. Это ограничение является преднамеренным; это значительно упростило
поддержку этого блока формата и может позволить провести оптимизацию в будущем.
Это ограничение не кажется необоснованным; сам 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
.
Выражения, указанные как значения по умолчанию
Значение по умолчанию для параметра может быть больше, чем просто буквальное значение. Это может быть целое выражение с использованием математических операторов и поиска атрибутов объектов. Однако поддержка не совсем простая из-за некоторой неочевидной семантики.
Рассмотрим следующий пример:
foo: Py_ssize_t = sys.maxsize - 1
sys.maxsize
может содержать разные значения на разных платформах. Поэтому
Клиника Споров не может просто вычислить это выражение локально и жёстко
запрограммировать его на C. Поэтому она сохраняет значение по умолчанию таким
образом, чтобы оно было вычислено во время выполнения, когда пользователь
запрашивает сигнатуру функции.
Какое пространство имён доступно при вычислении выражения? Оно вычисляется в
контексте модуля, из которого пришла встроенная команда. Итак, если в вашем
модуле есть атрибут под названием «max_widgets
», вы можете просто
использовать его :
foo: Py_ssize_t = max_widgets
Если символ не найден в текущем модуле, он не выполняет поиск в
sys.modules
. Вот как он может найти sys.maxsize
например. (Поскольку вы
заранее не знаете, какие модули пользователь загрузит в свой интерпретатор,
лучше ограничиться модулями, которые предварительно загружены самим Python.)
Вычисление значений по умолчанию только во время выполнения означает, что Клиника
Споров не может вычислить правильное эквивалентное значение по умолчанию C. Так
что вам нужно сказать это прямо. При использовании выражения необходимо также
указать эквивалентное выражение на языке C, используя параметр c_default
для преобразователя :
foo: Py_ssize_t(c_default="PY_SSIZE_T_MAX - 1") = sys.maxsize - 1
Еще одна сложность: Клиника Споров не может знать заранее, действительно ли предоставленное вами выражение. Он анализирует его, чтобы убедиться, что оно выглядит законным, но не может фактически знать. Вы должны быть очень осторожны при использовании выражений для указания значений, которые гарантированно будут действительны во время выполнения!
Наконец, поскольку выражения должны быть представлены как статические значения C, существует множество ограничений на допустимые выражения. Вот список функций Python, которые вам не разрешено использовать:
- Вызов функций.
- Встроенные операторы if (
3 if foo else 5
). - Автоматическая последовательность распаковки (
*[1, 2, 3]
). - Список/множество/словарь и выражений генератора.
- Кортеж/список/множество/словарь литералов.
Использование возвратного конвертера
По умолчанию функция impl, которую генерирует для вас Клиника Споров,
возвращает PyObject *
. Но ваша функция C часто вычисляет некоторый тип C, а
затем в последний момент преобразует его в PyObject *
. Клиника Споров
выполняет преобразование ваших входных данных из типов Python в родные типы C —
почему бы не преобразовать возвращаемое вами значение из родного типа C в тип
Python?
Вот что делает «конвертер возврата». Он изменяет вашу функцию impl, чтобы она
возвращала некоторый тип C, а затем добавляет код к сгенерированной (не-impl)
функции для обработки преобразования этого значения в соответствующий
PyObject *
.
Синтаксис конвертеров возврата аналогичен синтаксису конвертеров параметров. Вы указываете конвертер возврата, как если бы это была аннотация возврата к самой функции. Преобразователи возврата ведут себя так же, как конвертеры параметров; они принимают аргументы, все аргументы состоят только из ключевых слов, и если вы не меняете ни один из аргументов по умолчанию, вы можете пропустить круглые скобки.
(Если вы используете как "as"
, так и конвертером возврата для своей
функции, "as"
должен стоять перед конвертером возврата.)
Есть ещё одно осложнение при использовании конвертеров возврата: как вы
указываете на возникновение ошибки? Обычно функция возвращает действительный
указатель (не NULL
) в случае успеха и NULL
в случае ошибки. Но если вы
используете конвертер возврата целых чисел, все числа действительны. Как
Клиника Споров обнаруживает ошибку? Её решение: каждый конвертер
возврата неявно ищет специальное значение, указывающее на ошибку. Если вы
возвращаете это значение и была установлена ошибка (PyErr_Occurred()
возвращает истинное значение), то сгенерированный код распространит ошибку. В
противном случае он будет кодировать возвращаемое вами значение как обычно.
В настоящее время Клиника Споров поддерживает только несколько обратных конвертеров :
bool
int
unsigned int
long
unsigned int
size_t
Py_ssize_t
float
double
DecodeFSDefault
Ни один из них не принимает параметров. Для первых трёх вернёт -1, чтобы
указать на ошибку. Для DecodeFSDefault
возвращаемый тип — const char *
;
возвращает указатель NULL
, чтобы указать на ошибку.
(Существует также экспериментальный конвертер NoneType
, который позволяет
возвращать Py_None
в случае успеха или NULL
в случае неудачи, не
увеличивая счетчик ссылок на Py_None
. Я не уверен, что это добавляет
достаточно ясности, чтобы его стоило использовать.)
Чтобы увидеть все поддерживаемые Клиникой Споров конвертеры возврата, а
также их параметры (если есть), просто запустите
Tools/clinic/clinic.py --converters
для получения полного списка.
Клонирование существующих функций
Если у вас есть несколько похожих функций, вы можете использовать функцию «клонирования» Клиники. Когда вы клонируете существующую функцию, вы повторно используете :
- Её параметры, в том числе;
- Её имена;
- Её конвертеры, со всеми параметрами;
- Значения по умолчанию;
- Её строки документации для каждого параметра;
- Её вид (независимо от того, являются ли они только позиционными, позиционными или ключевыми, или только ключевыми), и;
- Её конвертер возврата.
Единственное, что не скопировано из исходной функции — это её строка документации; синтаксис позволяет указать новую строку документации.
Синтаксис для клонирования функции:
/*[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
Остальные сложные темы требуют от вас написания кода Python, который находится внутри вашего файла C и изменяет состояние выполнения Клиники Споров. Это просто: вы просто определяете блок Python.
В блоке Python используются другие разделительные строки, чем в функциональном блоке Клиники Споров. Это выглядит так:
/*[python input]
# python code goes here
[python start generated code]*/
Весь код внутри блока Python выполняется в момент его анализа. Весь текст, записанный в стандартный вывод внутри блока, перенаправляется на «вывод» после блока.
В качестве примера приведем блок Python, который добавляет статическую целочисленную переменную в код C:
/*[python input]
print('static int __ignored_unused_variable__ = 0;')
[python start generated code]*/
static int __ignored_unused_variable__ = 0;
/*[python checksum:...]*/
Использование «самоконвертера»
Клиника Споров автоматически добавляет для вас параметр self, используя
конвертер по умолчанию. Он автоматически устанавливает type
этого параметра
на «указатель на экземпляр», который вы указали при объявлении типа. Однако вы
можете переопределить конвертер Клиники Споров и указать его
самостоятельно. Просто добавьте свой собственный параметр self
в качестве
первого параметра в блок и убедитесь, что его преобразователь является
экземпляром self_converter
или его подклассом.
В чём смысл? Это позволяет вам переопределить тип self
или дать ему другое
имя по умолчанию.
Как указать пользовательский тип, в который нужно преобразовать self
? Если
у вас есть только одна или две функции с одним и тем же типом для self
, вы
можете напрямую использовать существующий преобразователь self
от Клиники
Споров, передав тип, который вы хотите использовать, в качестве параметра
type
:
/*[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]*/
Написание собственного конвертера
Как мы намекали в предыдущем разделе … вы можете писать свои собственные
конвертеры! Конвертер — это просто класс Python, унаследованный от
CConverter
. Основное назначение настраиваемого конвертера — если у вас
есть параметр, использующий единицу формата O&
этого параметра,
означает вызов «функции конвертера» PyArg_ParseTuple()
.
Ваш класс конвертера должен называться *something*_converter
. Если имя
соответствует этому соглашению, тогда ваш класс конвертера будет автоматически
зарегистрирован в Клинике Споров; его имя будет именем вашего класса с
удаленным суффиксом _converter
. (Это достигается с помощью метакласса.)
Вы не должны создавать подклассы CConverter.__init__
. Вместо этого вы
должны написать функцию converter_init()
. converter_init()
всегда
принимает параметр self
; после этого все дополнительные параметры должны
будут содержать только ключевые. Любые аргументы, переданные конвертеру в
Клинике Споров, будут переданы на ваш converter_init()
.
Есть несколько дополнительных полей CConverter
, которые вы можете указать
в своем подклассе. Вот текущий список :
type
- Тип C, используемый для этой переменной.
type
должен быть строкой Python, определяющей тип, напримерint
. Если это тип указателя, строка типа должна заканчиваться на' *'
. default
- Значение Python по умолчанию для этого параметра в виде значения
Python. Или магическое значение
unspecified
, если нет значения по умолчанию. py_default
default
, как он должен отображаться в коде Python в виде строки. ИлиNone
, если нет по умолчанию.c_default
default
, как он должен отображаться в коде C, в виде строки. ИлиNone
, если нет по умолчанию.c_ignored_default
- Значение по умолчанию, используемое для инициализации переменной C, когда нет значения по умолчанию, но не указание значения по умолчанию может привести к предупреждению о «неинициализированной переменной». Это может легко произойти при использовании групп опций — хотя правильно написанный код на самом деле никогда не будет использовать это значение, переменная действительно передается в impl, и компилятор C будет жаловаться на «использование» неинициализированного значения. Это значение всегда должно быть непустой строкой.
converter
- Имя функции конвертера C в виде строки.
impl_by_reference
- Логическое значение. Если истинно, Клиника Споров добавит
&
перед именем переменной при передаче её в функцию impl. parse_by_reference
- Логическое значение. Если истинно, Клиника Споров добавит
&
перед именем переменной при передаче вPyArg_ParseTuple()
.
Вот простейший пример специального конвертера из 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]*/
Этот блок добавляет в Клинику Споров конвертер с именем ssize_t
.
Параметры, объявленные как ssize_t
, будут объявлены как тип Py_ssize_t
и будут проанализированы модулем формата 'O&'
, который вызовет функцию
конвертера ssize_t_converter
. Переменные ssize_t
автоматически
поддерживают значения по умолчанию.
Более сложные пользовательские конвертеры могут вставлять собственный код
C для обработки инициализации и очистки. Вы можете увидеть больше примеров
пользовательских конвертеров в дереве исходных текстов CPython; grep’ните файлы
C для строки CConverter
.
Написание собственного конвертера возврата
Написание специального конвертера возврата очень похоже на написание специального конвертера. За исключением того, что это несколько проще, потому что обратные конвертеры сами по себе намного проще.
У возвратных конвертеров должен быть подкласс 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
, просто не
указывайте никаких аргументов.
Вы по-прежнему можете использовать собственный преобразователь, преобразователь
возврата и указать аргумент type
для преобразователя объектов для
METH_O
.
Функции tp_new и tp_init
Вы можете преобразовывать функции tp_new
и tp_init
. Просто назовите их
__new__
или __init__
соответственно. Примечания:
- Имя функции, созданное для
__new__
, не оканчивается на__new__
, как это было бы по умолчанию. Это просто имя класса, преобразованное в действительный идентификатор C. - Для этих функций генерируется номер
PyMethodDef
#define
. - Функции
__init__
возвращаютint
, а неPyObject *
. - Используйте строку документации в качестве строки документации класса.
- Хотя функции
__new__
и__init__
всегда должны принимать объектыargs
иkwargs
, при преобразовании вы можете указать любую сигнатуру для этих функций, которая вам нравится. (Если ваша функция не поддерживает ключевые аргументы, сгенерированная функция парсера вызовет исключение, если она получит что-либо другое.)
Изменение и перенаправление вывода Клиники
Может быть неудобно, если выходные данные Клиники перемежаются с обычным редактируемым вручную кодом C. К счастью, Клиника настраивается: вы можете буферизовать его вывод для печати позже (или раньше!) или записать его вывод в отдельный файл. Вы также можете добавить префикс или суффикс к каждой строке сгенерированного вывода Клиники.
Хотя изменение вывода Клиники таким образом может быть благом для читабельности, это может привести к тому, что код Клиники будет использовать типы до того, как они будут определены, или ваш код попытается использовать код, сгенерированный Клиникой, до того, как он будет определён. Эти проблемы можно легко решить, переставив объявления в файле или переместив сгенерированный код Клиники. (Вот почему по умолчанию Клиника выводит всё в текущий блок; хотя многие считают, что это затрудняет читаемость, никогда не потребуется перекомпоновывать код, чтобы исправить проблемы, связанные с определением перед использованием.)
Начнем с определения терминологии:
- field
Поле в данном контексте является подразделом вывода Клиники. Например,
#define
для структурыPyMethodDef
— это поле с именемmethoddef_define
. В Клинике есть семь различных полей, которые можно выводить для каждого определения функции: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.)- destination
Пункт назначения — это место, куда Клиника может записывать выходные данные. Есть пять встроенных пунктов назначения :
block
- Назначение по умолчанию: печатается в разделе вывода текущего блока Клиники.
buffer
- Текстовый буфер, в котором вы можете сохранить текст на будущее. Отправленный здесь текст добавляется в конец любого существующего текста. Когда Клиника завершает обработку файла, в буфере остается какой-либо текст. Это ошибка.
file
Отдельный «файл Клиники», который будет автоматически создан Клиникой. Имя файла, выбранное для файла,
{basename}.clinic{extension}
, гдеbasename
иextension
были назначены выходным данным изos.path.splitext()
, запущенного в текущем файле. (Пример: место назначенияfile
для_pickle.c
будет записано в_pickle.clinic.c
.)Важно: когда используется
file
пункт назначения, вы должны проверить в сгенерированный файл!two-pass
- Буфер типа
buffer
. Однако двухпроходный буфер можно сбросить только один раз, и он распечатывает весь текст, отправленный в него во время всей обработки, даже из блоков Клиники после точки сброса. suppress
- Текст подавляется и выбрасывается.
Клиника определяет пять новых директив, которые позволяют вам перенастроить его вывод.
Первая новая директива — dump
:
dump <destination>
Она выгружает текущее содержимое указанного адресата в вывод текущего блока и
очищает его. Это работает только с адресами buffer
и two-pass
.
Вторая новая директива — output
. Самая основная форма output
выглядит
так:
output <field> <destination>
output
также поддерживает специальный метаадрес, называемый everything
,
который сообщает Клинике о необходимости вывода всех полей в это место
назначения.
output
имеет ряд других функций
output push
output pop
output preset <preset>
output push
и output pop
позволяют отправлять и извлекать конфигурации
во внутреннем стеке конфигурации, чтобы вы могли временно изменить конфигурацию
вывода, а затем легко восстановить предыдущую конфигурацию. Просто нажмите
перед изменением, чтобы сохранить текущую конфигурацию, затем нажмите, когда вы
хотите восстановить предыдущую конфигурацию.
output preset
устанавливает для вывода Клиникой одну из нескольких встроенных
предустановленных конфигураций, а именно:
block
Оригинальная стартовая конфигурация Клиники. Пишет все сразу после блока ввода.
Подавить
parser_prototype
иdocstring_prototype
, все остальное записать вblock
.file
Предназначен для записи в «файл Клиники» всего, что может. Затем вы
#include
этот файл в верхней части файла. Возможно, вам придется изменить порядок файла, чтобы это работало, хотя обычно это означает просто создание предварительных объявлений для различных определенийtypedef
иPyTypeObject
.Подавить
parser_prototype
иdocstring_prototype
, записатьimpl_definition
вblock
, а всё остальное записать вfile
.Имя файла по умолчанию
"{dirname}/clinic/{basename}.h"
.buffer
Сохранить большую часть вывода из Клиники, чтобы записать в файл ближе к концу. Для файлов Python, реализующих модули или встроенные типы, рекомендуется выгружать буфер непосредственно над статическими структурами для вашего модуля или встроенного типа; обычно это очень близко к концу. Использование
buffer
может потребовать даже большего редактирования, чемfile
, если ваш файл имеет статические массивыPyMethodDef
, определенные в середине файла.Подавить
parser_prototype
,impl_prototype
, иdocstring_prototype
, записатьimpl_definition
вblock
, а всё остальное записать вfile
.two-pass
Подобен предустановке
buffer
, но записывает предварительные объявления в буферtwo-pass
, а определения вbuffer
. Это похоже на предустановкуbuffer
, но может потребовать меньшего редактирования, чемbuffer
. Сброс буфераtwo-pass
в верхней части файла и сбросbuffer
ближе к концу, как если бы вы использовали пресетbuffer
.Подавить
impl_prototype
, записатьimpl_definition
вblock
, writedocstring_prototype
,methoddef_define
, иparser_prototype
totwo-pass
, а всё остальное записать вbuffer
.partial-buffer
Подобен предустановке
buffer
, но записывает больше вещей в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
- Записывает текст в текущий блок. Это то, чем изначально занималась Клиника.
buffer
- Простой текстовый буфер, подобный встроенному назначению «buffer» выше.
file
Текстовый файл. Место назначения файла принимает дополнительный аргумент, шаблон для создания имени файла, например:
destination <name> new <type> <file_template>Шаблон может использовать внутри три строки, которые будут заменены битами имени файла :
- {path}
- Полный путь к файлу, включая каталог и полное имя файла.
- {dirname}
- Имя каталога, в котором находится файл.
- {basename}
- Просто имя файла, не включая каталог.
- {basename_root}
- Имя базы с обрезанным расширением (все до последнего „.“, но не включая его).
- {basename_extension}
- Последний „.“ и все после него. Если базовое имя не содержит точки, это будет пустая строка.
Если в имени файла нет точек, {basename} и {filename} совпадают, а {extension} пусто. «{basename} {extension}» всегда в точности совпадает с «{filename}». «
two-pass
- Двухпроходный буфер, подобный «двухпроходному» встроенному назначению выше.
Подкоманда clear
работает следующим образом:
destination <name> clear
Она удаляет весь накопленный текст до этого момента в месте назначения. (Мне не известно, для чего это может потребоваться, но предполагается, что это будет полезно для экспериментов.)
Четвертая новая директива — set
:
set line_prefix "string"
set line_suffix "string"
set
позволяет вам установить две внутренние переменные в Клинике.
line_prefix
— это строка, добавляемая к каждой строке вывода
Клиники; line_suffix
— это добавляемая к каждой строке
вывода Клиники строка.
Оба они поддерживают две строки формата:
{block comment start}
- Преобразуется в строку
/*
, текстовую последовательность начала комментария для файлов C.{block comment end}
- Преобразуется в строку
*/
, текстовую последовательность конечного комментария для файлов C.
Последняя новая директива, которую вам не нужно использовать напрямую,
называется preserve
:
preserve
Она используется внутри Клиники при выгрузке вывода в файлы file
; упаковка
её в блок Клиники позволяет Клинике использовать свои существующие функции
контрольной суммы, чтобы гарантировать, что файл не был изменён вручную, прежде
чем он будет перезаписан.
Уловка #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
, заменив их макросом
Клиники Споров, созданным :
MODULE_FUNCTIONNAME_METHODDEF
(Настоящее имя этого макроса можно найти внутри сгенерированного кода. Или вы
можете рассчитать его самостоятельно: это имя вашей функции, как определено в
первой строке вашего блока, но с точками, замененными на подчеркивания, в
верхнем регистре и добавлением "_METHODDEF"
в конец.)
Возможно, вам интересно: а что, если HAVE_FUNCTIONNAME
не определен? Макрос
MODULE_FUNCTIONNAME_METHODDEF
тоже не будет определён!
Он фактически обнаруживает, что блок Клиники Споров может быть деактивирован
#ifdef
. Когда это происходит, он генерирует небольшой дополнительный код,
который выглядит следующим образом:
#ifndef MODULE_FUNCTIONNAME_METHODDEF
#define MODULE_FUNCTIONNAME_METHODDEF
#endif /* !defined(MODULE_FUNCTIONNAME_METHODDEF) */
Это означает, что макрос работает всегда. Если функция определена, он превращается в правильную структуру, включая конечную запятую. Если функция не определена, превращается в ничто.
Однако это вызывает одну щекотливую проблему: куда Клиника Споров должна
поместить этот дополнительный код при использовании предустановки вывода
«блока»? Она не может войти в выходной блок, потому что он может быть
деактивирован #ifdef
. (В этом весь смысл!)
В этой ситуации Клиника Споров записывает дополнительный код в «буферное» место назначения. Это может означать, что вы получили жалобу от Клиники Споров:
Warning in file "Modules/posixmodule.c" on line 12357:
Destination buffer 'buffer' not empty at end of file, emptying.
Когда это произойдет, просто откройте свой файл, найдите добавленный
Клиникой Споров в ваш файл блок dump buffer
(он будет в самом низу), а затем
переместите его над структурой PyMethodDef
, в которой используется данный
макрос.
Использование Клиники Споров в файлах Python
Фактически можно использовать Клинику Споров для предварительной обработки файлов Python. Конечно, нет смысла использовать блоки Клиники Споров, поскольку вывод не будет иметь никакого смысла для интерпретатора Python. Но использование Клиники Споров для запуска блоков Python позволяет использовать Python в качестве препроцессора Python!
Поскольку комментарии Python отличаются от комментариев C, блоки Клиники Споров, встроенные в файлы Python, выглядят немного иначе. Они выглядят так:
#/*[python input]
#print("def foo(): pass")
#[python start generated code]*/
def foo(): pass
#/*[python checksum:...]*/