Руководство по использованию Argument Clinic
- author
-
Larry Hastings
Аннотация
Argument Clinic — это препроцессор для файлов CPython. Его цель — автоматизировать весь шаблонный код, связанный с обработкой аргументов для «встроенных» функций. Этот документ покажет вам, как преобразовать вашу первую функцию C для работы с Argument Clinic, а затем представит некоторые продвинутые темы использования Argument Clinic.
В настоящее время Argument Clinic считается внутренним инструментом для CPython. Его использование не поддерживается для файлов за пределами CPython, и никакие гарантии не даются относительно обратной совместимости в будущих версиях. Другими словами: если вы поддерживаете внешнее расширение C для CPython, вы можете экспериментировать с Argument Clinic в собственном коде. Но версия Argument Clinic, поставляемая с следующей версией CPython, *может* быть полностью несовместимой и сломать весь ваш код.
Цели Argument Clinic
Основной целью Argument Clinic является взятие на себя ответственности за весь код обработки аргументов внутри CPython. Это означает, что после преобразования функции для работы с Argument Clinic эта функция больше не должна выполнять свою собственную обработку аргументов — код, сгенерированный Argument Clinic, должен быть для вас «черным ящиком», где CPython вызывается вверху, а ваш код вызывается внизу, с PyObject *args (и, возможно, PyObject *kwargs) магически преобразованными в необходимые переменные и типы C.
Для достижения своей основной цели Argument Clinic должен быть простым в использовании. В настоящее время работа с библиотекой обработки аргументов CPython — это утомительная задача, требующая поддержки избыточной информации в большом количестве мест. При использовании Argument Clinic вам не нужно повторяться.
Очевидно, никто не захочет использовать Argument Clinic, если это не решает их проблему — и не создает новых проблем. Поэтому крайне важно, чтобы Argument Clinic генерировал корректный код. Было бы неплохо, если бы код был быстрее, но как минимум он не должен вносить значительного снижения производительности. (В конечном итоге Argument Clinic *должен* обеспечить значительное ускорение — мы могли бы переписать генератор кода для создания специализированного кода обработки аргументов вместо вызова универсальной библиотеки обработки аргументов CPython. Это сделает обработку аргументов максимально быстрой!)
Кроме того, Argument Clinic должен быть достаточно гибким, чтобы работать с любым подходом к обработке аргументов. В Python есть некоторые функции с очень странными особенностями обработки; целью Argument Clinic является поддержка всех из них.
Наконец, первоначальной мотивацией для Argument Clinic было предоставление «подписей» для интроспекции встроенных функций CPython. Раньше функции запроса интроспекции выбрасывали исключение, если вы передавали встроенную функцию. С Argument Clinic это в прошлом!
Одной из идей, которую вы должны иметь в виду, работая с Argument Clinic: чем больше информации вы ему предоставите, тем лучше он сможет справиться с задачей. В настоящее время Argument Clinic, безусловно, относительно прост. Но по мере развития он станет более сложным и должен уметь выполнять множество интересных и умных действий со всей предоставляемой вами информацией.
Основные концепции и использование
Argument Clinic поставляется с CPython; вы найдете его в Tools/clinic/clinic.py. Если вы запустите этот скрипт, указав файл C в качестве аргумента:
$ python3 Tools/clinic/clinic.py foo.c
Argument Clinic будет сканировать файл в поисках строк, которые выглядят точно так же:
/*[clinic input]
Найдя одну, он прочитает все до строки, которая выглядит точно так же:
[clinic start generated code]*/
Все между этими двумя строками являются вводом для Argument Clinic. Все эти строки, включая начальные и конечные комментарии, в совокупности называются «блоком» Argument Clinic.
Когда Argument Clinic обрабатывает один из этих блоков, он генерирует вывод. Этот вывод перезаписывается в файл C сразу после блока, за которым следует комментарий, содержащий контрольную сумму. Блок Argument Clinic теперь выглядит так:
/*[clinic input] ... clinic input goes here ... [clinic start generated code]*/ ... clinic output goes here ... /*[clinic end generated code: checksum=...]*/
Если вы запустите Argument Clinic на том же файле во второй раз, Argument Clinic отбросит старый вывод и запишет новый вывод с новой контрольной суммой. Однако, если вход не изменился, вывод тоже не изменится.
Вы никогда не должны изменять часть вывода блока Argument Clinic. Вместо этого измените вход, пока не получите нужный вывод. (Для этого и нужна контрольная сумма — для обнаружения, если кто-то изменил вывод, так как эти изменения будут потеряны при следующем запуске Argument Clinic.)
Для ясности, вот терминология, которую мы будем использовать с Argument Clinic:
- Первая строка комментария (
/*[clinic input]) — это стартовая строка. - Последняя строка начального комментария (
[clinic start generated code]*/) — это конечная строка. - Последняя строка (
/*[clinic end generated code: checksum=...]*/) — это строка контрольной суммы. - Между стартовой и конечной строками находится вход.
- Между конечной строкой и строкой контрольной суммы находится выход.
- Весь текст, начиная со стартовой строки и включая строку контрольной суммы, является блоком. (Блок, который еще не был успешно обработан Argument Clinic, не имеет вывода или строки контрольной суммы, но он все еще считается блоком.)
Преобразование вашей первой функции
Лучший способ понять, как работает Argument Clinic, — преобразовать функцию для работы с ним. Вот минимальные шаги, которые вам нужно будет выполнить, чтобы преобразовать функцию для работы с Argument Clinic. Обратите внимание, что для кода, который вы планируете включить в CPython, вы действительно должны выполнить преобразование дальше, используя некоторые продвинутые концепции, которые вы увидите позже в документе (например, «конвертеры возвращаемых значений» и «конвертеры self»). Но мы сохраним это просто для этого руководства, чтобы вы могли узнать.
Давайте приступим!
- Убедитесь, что вы работаете с недавно обновлённой версией CPython trunk.
- Найдите встроенную функцию Python, которая использует либо
PyArg_ParseTuple(), либоPyArg_ParseTupleAndKeywords(), и которая ещё не была преобразована с помощью Argument Clinic. В моём примере используется_pickle.Pickler.dump(). -
Если вызов функции
PyArg_Parseиспользует любой из следующих форматов:O& O! es es# et et#
или если он содержит несколько вызовов
PyArg_ParseTuple(), вам следует выбрать другую функцию. Argument Clinic поддерживает все эти сценарии. Но это продвинутые темы — давайте сделаем что-то более простое для вашей первой функции.Также, если функция имеет несколько вызовов
PyArg_ParseTuple()илиPyArg_ParseTupleAndKeywords(), где она поддерживает разные типы для одного и того же аргумента, или если функция использует что-то кроме функций PyArg_Parse для разбора аргументов, она, вероятно, не подходит для преобразования с помощью Argument Clinic. Argument Clinic не поддерживает универсальные функции или полиморфные параметры. -
Добавьте следующий шаблон над функцией, создавая наш блок:
/*[clinic input] [clinic start generated code]*/
-
Вырежьте строку документации и вставьте её между строками
[clinic], удалив весь мусор, который делает её правильно оформленной строкой C-строки. Когда вы закончите, у вас должна остаться только текст, выровненный по левому краю, без строк, превышающих 80 символов. (Argument Clinic сохранит отступы внутри строки документации.)Если старая строка документации содержала первую строку, похожую на сигнатуру функции, удалите её. (Строке документации она больше не нужна — когда вы в будущем будете использовать
help()для своей встроенной функции, первая строка будет автоматически построена на основе сигнатуры функции.)Пример:
/*[clinic input] Write a pickled representation of obj to the open file. [clinic start generated code]*/
-
Если ваша строка документации не содержит строки «резюме», Argument Clinic выдаст ошибку. Поэтому убедимся, что она есть. Строка «резюме» должна быть абзацем, состоящим из одной строки длиной 80 символов в начале строки документации.
(Наша примерная строка документации состоит только из строки «резюме», поэтому для этого шага код примера не должен меняться.)
-
Выше строки документации введите имя функции, а затем пустую строку. Это должно быть имя функции на Python, полный путь к функции — она должна начинаться с имени модуля, включать любые подмодули, и если функция является методом класса, она должна включать имя класса.
Пример:
/*[clinic input] _pickle.Pickler.dump Write a pickled representation of obj to the open file. [clinic start generated code]*/
-
Если этот модуль или класс используется с Argument Clinic в этом файле C впервые, необходимо объявить модуль и/или класс. Правильная работа Argument Clinic предполагает объявление этих элементов в отдельном блоке где-то в верхней части файла C, так же как include файлы и статические переменные помещаются вверху. (В нашем примере кода мы просто покажем оба блока рядом.)
Имя класса и модуля должно совпадать с тем, что видит 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
Если параметр имеет значение по умолчанию, добавьте его после преобразователя:
name_of_parameter: converter = default_value
Поддержка «значений по умолчанию» в Argument Clinic довольно сложная; для получения дополнительной информации см. раздел ниже о значениях по умолчанию.
Добавьте пустую строку под параметрами.
Что такое «преобразователь»? Он устанавливает тип переменной, используемой в C, и метод преобразования значения Python в значение C во время выполнения. Пока вы будете использовать так называемый «наследственный преобразователь» — удобный синтаксис, предназначенный для облегчения переноса старого кода в Argument Clinic.
Для каждого параметра скопируйте «формат единицы» для этого параметра из аргумента формата
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]*/ -
Если ваша функция имеет
|в строке формата, что означает, что некоторые параметры имеют значения по умолчанию, вы можете её пропустить. Argument Clinic определяет, какие параметры являются необязательными, исходя из того, есть ли у них значения по умолчанию или нет.Если ваша функция имеет
$в строке формата, что означает, что она принимает только ключевые аргументы, укажите*в отдельной строке перед первым ключевым аргументом, с тем же отступом, что и строки параметров.(
_pickle.Pickler.dumpне содержит ни того, ни другого, поэтому наш пример не изменился.) -
Если существующая функция C вызывает
PyArg_ParseTuple()(в отличие отPyArg_ParseTupleAndKeywords()), то все её аргументы являются позиционными.Чтобы пометить все параметры как позиционные только в Argument Clinic, добавьте
/в отдельной строке после последнего параметра, с тем же отступом, что и строки параметров.В настоящее время это «все или ничего»; либо все параметры являются позиционными только, либо ни один из них. (В будущем Argument Clinic может ослабить это ограничение.)
Пример:
/*[clinic input] module _pickle class _pickle.Pickler "PicklerObject *" "&Pickler_Type" [clinic start generated code]*/ /*[clinic input] _pickle.Pickler.dump obj: 'O' / Write a pickled representation of obj to the open file. [clinic start generated code]*/ -
Полезно написать строку документации для каждого параметра. Но строки документации для параметров необязательны; вы можете пропустить этот шаг, если хотите.
Вот как добавить строку документации для параметра. Первая строка строки документации параметра должна быть отступлена дальше, чем определение параметра. Левый отступ этой первой строки определяет левый отступ для всей строки документации параметра; весь текст, который вы напишете, будет выравниваться по этому отступу. Вы можете написать столько текста, сколько вам нужно, на нескольких строках, если хотите.
Пример:
/*[clinic input] module _pickle class _pickle.Pickler "PicklerObject *" "&Pickler_Type" [clinic start generated code]*/ /*[clinic input] _pickle.Pickler.dump obj: 'O' The object to be pickled. / Write a pickled representation of obj to the open file. [clinic start generated code]*/ -
Сохраните и закройте файл, затем запустите
Tools/clinic/clinic.pyна нём. Если всё получилось, ваш блок теперь имеет вывод, и был сгенерирован файл.c.h! Откройте файл в вашем текстовом редакторе, чтобы увидеть:/*[clinic input] _pickle.Pickler.dump obj: 'O' The object to be pickled. / Write a pickled representation of obj to the open file. [clinic start generated code]*/ static PyObject * _pickle_Pickler_dump(PicklerObject *self, PyObject *obj) /*[clinic end generated code: output=87ecad1261e02ac7 input=552eb1c0f52260d9]*/Очевидно, если Argument Clinic не вывел никакого результата, это означает, что он обнаружил ошибку в вашем вводе. Продолжайте исправлять ошибки и повторно запускать Argument Clinic, пока он не обработает ваш файл без ошибок.
Для повышения читабельности большая часть «склейки» кода сгенерирована в файл
.c.h. Вам нужно будет включить его в ваш исходный файл.c, обычно сразу после блока модуля clinic:#include "clinic/_pickle.c.h"
-
Тщательно проверьте, что сгенерированный Argument Clinic код разбора аргументов выглядит примерно так же, как существующий код.
Во-первых, убедитесь, что в обоих местах используется одна и та же функция разбора аргументов. Существующий код должен вызывать либо
PyArg_ParseTuple(), либоPyArg_ParseTupleAndKeywords(); убедитесь, что сгенерированный Argument Clinic код вызывает точно ту же функцию.Во-вторых, строка формата, передаваемая в
PyArg_ParseTuple()илиPyArg_ParseTupleAndKeywords(), должна быть точно такой же, как написанная вручную в существующей функции, до двоеточия или точки с запятой.(Argument Clinic всегда генерирует строки форматов с
:за которым следует имя функции. Если строка формата существующего кода заканчивается на;, чтобы предоставить помощь по использованию, это изменение безвредно — не беспокойтесь об этом.)В-третьих, для параметров, чьи форматы единиц требуют два аргумента (например, переменной длины, строки кодировки или указателя на функцию преобразования), убедитесь, что второй аргумент точно такой же в обоих вызовах.
В-четвертых, внутри сгенерированной части блока вы найдёте макрос препроцессора, определяющий соответствующую статическую структуру
PyMethodDefдля этой встроенной функции:#define __PICKLE_PICKLER_DUMP_METHODDEF \ {"dump", (PyCFunction)__pickle_Pickler_dump, METH_O, __pickle_Pickler_dump__doc__},Эта статическая структура должна быть точно такой же, как существующая статическая структура
PyMethodDefдля этой встроенной функции.Если любой из этих пунктов отличается каким-либо образом, скорректируйте спецификацию вашей функции Argument Clinic и перезапустите
Tools/clinic/clinic.pyдо тех пор, пока они не будут одинаковыми. -
Обратите внимание, что последняя строка её вывода — объявление вашей функции «impl». Именно сюда идёт реализация встроенной функции. Удалите существующий прототип функции, которую вы изменяете, но оставьте открывающую фигурную скобку. Теперь удалите её код разбора аргументов и объявления всех переменных, в которые она помещает аргументы. Обратите внимание, как аргументы Python теперь являются аргументами этой функции impl; если реализация использовала другие имена для этих переменных, исправьте это.
Давайте повторим, это немного странно. Ваш код теперь должен выглядеть так:
static return_type your_function_impl(...) /*[clinic end generated code: checksum=...]*/ { ...Argument Clinic сгенерировал строку контрольной суммы и прототип функции над ней. Вы должны написать открывающие (и закрывающие) фигурные скобки для функции и реализацию внутри.
Пример:
/*[clinic input] module _pickle class _pickle.Pickler "PicklerObject *" "&Pickler_Type" [clinic start generated code]*/ /*[clinic end generated code: checksum=da39a3ee5e6b4b0d3255bfef95601890afd80709]*/ /*[clinic input] _pickle.Pickler.dump obj: 'O' The object to be pickled. / Write a pickled representation of obj to the open file. [clinic start generated code]*/ PyDoc_STRVAR(__pickle_Pickler_dump__doc__, "Write a pickled representation of obj to the open file.\n" "\n" ... static PyObject * _pickle_Pickler_dump_impl(PicklerObject *self, PyObject *obj) /*[clinic end generated code: checksum=3bd30745bf206a48f8b576a1da3d90f55a0a4187]*/ { /* Check whether the Pickler was initialized correctly (issue3664). Developers often forget to call __init__() in their subclasses, which would trigger a segfault without this check. */ if (self->write == NULL) { PyErr_Format(PicklingError, "Pickler.__init__() was not called by %s.__init__()", Py_TYPE(self)->tp_name); return NULL; } if (_Pickler_ClearBuffer(self) < 0) return NULL; ...
-
Вспомните макрос с структурой
PyMethodDefдля этой функции? Найдите существующую структуруPyMethodDefдля этой функции и замените её ссылкой на макрос. (Если встроенная функция находится на уровне модуля, она, вероятно, будет в конце файла; если это метод класса, она, вероятно, будет ниже, но относительно близко к реализации.)Обратите внимание, что тело макроса содержит запятую в конце. Поэтому, когда вы замените существующую статическую структуру
PyMethodDefмакросом, не добавляйте запятую в конец.Пример:
static struct PyMethodDef Pickler_methods[] = { __PICKLE_PICKLER_DUMP_METHODDEF __PICKLE_PICKLER_CLEAR_MEMO_METHODDEF {NULL, NULL} /* sentinel */ }; -
Скомпилируйте, а затем запустите соответствующие части набора регрессионных тестов. Это изменение не должно создавать новых предупреждений или ошибок во время компиляции, и поведение Python внешне не должно измениться.
Ну, за исключением одного различия:
inspect.signature()для вашей функции теперь должны обеспечивать корректную сигнатуру!Поздравляем, вы портировали свою первую функцию для работы с Argument Clinic!
Дополнительные темы
Теперь, когда у вас есть некоторый опыт работы с Argument Clinic, пришло время изучить дополнительные темы.
Символьные значения по умолчанию
Значение по умолчанию, которое вы предоставляете для параметра, не может быть произвольным выражением. В настоящее время явно поддерживаются следующие:
- Числовые константы (целые и вещественные)
- Строковые константы
-
True,False, иNone - Простые символические константы, такие как
sys.maxsize, которые должны начинаться с имени модуля
Если вас это интересует, это реализовано в from_builtin() в Lib/inspect.py.
(В будущем, возможно, потребуется сделать это ещё сложнее, чтобы разрешить полные выражения, такие как CONSTANT - 1.)
Переименование функций и переменных C, сгенерированных Argument Clinic
Argument Clinic автоматически назначает имена генерируемым функциям. Иногда это может вызвать проблему, если сгенерированное имя совпадает с именем существующей функции C. Существует простое решение: переопределите имена используемых функций C. Просто добавьте ключевое слово "as" в строку объявления вашей функции, после чего укажите желаемое имя функции. Argument Clinic будет использовать это имя для базовой (сгенерированной) функции, а затем добавит "_impl" в конец и будет использовать это имя для имплементационной функции.
Например, если мы хотим переименовать имена функций C, сгенерированные для pickle.Pickler.dump, это будет выглядеть так:
/*[clinic input] pickle.Pickler.dump as pickler_dumper ...
Базовая функция теперь будет называться pickler_dumper(), а функция реализации — pickler_dumper_impl().
Аналогично, у вас может возникнуть проблема, когда вы хотите присвоить параметру определённое имя в Python, но это имя может быть неудобным в C. Argument Clinic позволяет присвоить параметру разные имена в 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).
В любом случае, цель 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}, где{direction}— это либоrightилиleftв зависимости от того, находится ли группа перед или после обязательных параметров, а{number}— это монотонно возрастающее число (начиная с 1), указывающее, насколько далеко группа расположена от обязательных параметров. Когда impl вызывается, этот параметр будет установлен в ноль, если эта группа не использовалась, и установлен в ненулевое значение, если эта группа использовалась. (Под использовавшейся или неиспользовавшейся я имею в виду, были ли параметрам переданы аргументы в этом вызове.) - Если обязательных аргументов нет, дополнительные группы будут вести себя так, как будто они находятся справа от обязательных аргументов.
- В случае неоднозначности код обработки аргументов отдаёт предпочтение параметрам слева (перед обязательными параметрами).
- Дополнительные группы могут содержать только позиционные параметры.
- Дополнительные группы предназначены только для устаревшего кода. Пожалуйста, не используйте дополнительные группы для нового кода.
Использование реальных конвертеров Argument Clinic вместо «устаревших конвертеров»
Для экономии времени и сведения к минимуму необходимого для выполнения первого переноса на Argument Clinic, пошаговое руководство выше предлагает использовать «устаревшие конвертеры». «Устаревшие конвертеры» — это удобство, специально разработанное для упрощения переноса существующего кода в Argument Clinic. И для ясности, их использование приемлемо при переносе кода для Python 3.4.
Однако в долгосрочной перспективе, вероятно, мы хотим, чтобы все наши блоки использовали реальный синтаксис Argument Clinic для конвертеров. Почему? По нескольким причинам:
- Правильные конвертеры гораздо проще читать и яснее отражают свои намерения.
- Существуют некоторые форматы, которые не поддерживаются в качестве «устаревших конвертеров», потому что они требуют аргументов, а синтаксис устаревших конвертеров не поддерживает указание аргументов.
- В будущем у нас может быть новая библиотека анализа аргументов, которая не ограничена тем, что
PyArg_ParseTuple()поддерживает; эта гибкость не будет доступна для параметров, использующих устаревшие конвертеры.
Поэтому, если вы не против небольших дополнительных усилий, пожалуйста, используйте обычные конвертеры вместо устаревших конвертеров.
Вкратце, синтаксис конвертеров Argument Clinic (не устаревших) похож на вызов функции Python. Однако, если у функции нет явных аргументов (все функции принимают свои значения по умолчанию), вы можете опустить скобки. Таким образом, bool и bool() представляют собой точно такие же конвертеры.
Все аргументы для конвертеров Argument Clinic — только ключевые слова. Все конвертеры Argument Clinic принимают следующие аргументы:
-
c_default -
Значение по умолчанию для этого параметра при определении в C. В частности, это будет инициализатор переменной, объявленной в «функции анализа». См. раздел о значениях по умолчанию для получения информации о способе использования. Указывается как строка.
-
annotation -
Значение аннотации для этого параметра. В настоящее время не поддерживается, потому что PEP 8 предписывает, что библиотека Python не может использовать аннотации.
Кроме того, некоторые конвертеры принимают дополнительные аргументы. Вот список этих аргументов вместе с их значениями:
-
accept -
Набор типов Python (и, возможно, псевдотипов); это ограничивает допустимый аргумент Python значениями этих типов. (Это не средство общего назначения; как правило, оно поддерживает только определенные списки типов, как показано в таблице устаревших конвертеров.)
Для принятия
None, добавьтеNoneTypeв этот набор. -
bitwise -
Поддерживается только для беззнаковых целых чисел. Значение нативного целого числа этого аргумента Python будет записано в параметр без проверки диапазона, даже для отрицательных значений.
-
converter -
Поддерживается только конвертером
object. Указывает имя C «функции-конвертера», используемой для преобразования этого объекта в нативный тип. -
encoding -
Поддерживается только для строк. Указывает кодировку, используемую при преобразовании этой строки из значения Python str (Unicode) в значение C
char *. -
subclass_of -
Поддерживается только для конвертера
object. Требуется, чтобы значение Python было подклассом типа Python, как выражено в C. -
type -
Поддерживается только для конвертеров
objectиself. Указывает тип C, который будет использоваться для объявления переменной. Значение по умолчанию —"PyObject *". -
zeroes -
Поддерживается только для строк. Если true, встраиваемые байты NUL (
'\\0') разрешены внутри значения. Длина строки будет передана в функцию impl, сразу после параметра строки, как параметр, названный<parameter_name>_length.
Обратите внимание, что не все возможные комбинации аргументов будут работать. Обычно эти аргументы реализуются специфическими PyArg_ParseTuple форматными блоками со специфическим поведением. Например, в настоящее время вы не можете вызвать unsigned_short без указания также bitwise=True. Хотя вполне разумно думать, что это сработает, эти семантики не отображаются в любом существующем формате. Поэтому 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 вручную, вы теоретически могли бы решить во время выполнения, какую строку кодировки передать в PyArg_ParseTuple(). Но теперь эта строка должна быть жестко закодирована на этапе предварительной обработки Argument Clinic. Это ограничение сделано намеренно; это значительно облегчило поддержку этой единицы формата и может позволить будущие оптимизации. Такое ограничение кажется разумным; сам CPython всегда передает статические жестко закодированные строки кодировки для параметров, чьи единицы формата начинаются с e.
Значения параметров по умолчанию
Значения параметров по умолчанию могут быть различными. В простейшем случае они могут быть строками, целыми числами или числами с плавающей запятой:
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 может иметь разные значения на разных платформах. Поэтому Argument Clinic не может просто оценить это выражение локально и жестко закодировать его в C. Поэтому она хранит значение по умолчанию таким образом, чтобы оно оценивалось во время выполнения, когда пользователь запрашивает сигнатуру функции.
В каком пространстве имен доступно выражение при его оценке? Оно оценивается в контексте модуля, из которого произошла функция встроенной функции. Таким образом, если ваш модуль имеет атрибут под названием «max_widgets», вы можете просто использовать его:
foo: Py_ssize_t = max_widgets
Если символ не найден в текущем модуле, он переходит к поиску в sys.modules. Вот как он может найти sys.maxsize, например. (Поскольку вы не знаете заранее, какие модули пользователь загрузит в свой интерпретатор, лучше ограничить себя модулями, которые предварительно загружаются самим Python.)
Оценивание значений по умолчанию только во время выполнения означает, что Argument Clinic не может вычислить правильное эквивалентное значение по умолчанию C. Поэтому вам нужно указать его явно. При использовании выражения вы также должны указать эквивалентное выражение на C, используя параметр c_default для преобразователя:
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]). - Списки/множества/словари с выражениями в виде генераторов.
- Кортежи/списки/множества/словари литералов.
Использование преобразователя возвращаемого значения
По умолчанию функция impl, которую Argument Clinic генерирует для вас, возвращает PyObject *. Но ваша функция C часто вычисляет какой-то тип C, затем преобразует его в PyObject * в последний момент. Argument Clinic обрабатывает преобразование ваших входных данных из типов Python в собственные типы C — почему бы ей не преобразовать ваше возвращаемое значение из собственного типа C в тип Python тоже?
Это делает «преобразователь возвращаемого значения». Он изменяет вашу функцию impl, чтобы она возвращала какой-то тип C, а затем добавляет код в сгенерированную (не impl) функцию для обработки преобразования этого значения в соответствующий тип PyObject *.
Синтаксис для преобразователей возвращаемого значения похож на синтаксис преобразователей параметров. Вы указываете преобразователь возвращаемого значения так, как если бы он был аннотацией возврата самой функции. Преобразователи возвращаемых значений ведут себя так же, как и преобразователи параметров; они принимают аргументы, аргументы — только ключевые, и если вы не изменяете какие-либо из аргументов по умолчанию, вы можете опустить скобки.
(Если вы используете как "as" и преобразователь возвращаемого значения для вашей функции, то "as" должен стоять перед преобразователем возвращаемого значения.)
Есть одна дополнительная проблема при использовании преобразователей возвращаемых значений: как указать, что произошла ошибка? Обычно функция возвращает допустимый (не NULL) указатель при успехе и NULL при ошибке. Но если вы используете целочисленный преобразователь возвращаемого значения, все целые числа допустимы. Как Argument Clinic может обнаружить ошибку? Решение: каждый преобразователь возвращаемого значения неявно ищет специальное значение, указывающее на ошибку. Если вы возвращаете это значение, и ошибка была установлена (PyErr_Occurred() возвращает истинное значение), тогда сгенерированный код распространит ошибку. В противном случае он закодирует возвращаемое вами значение как обычно.
В настоящее время Argument Clinic поддерживает только несколько преобразователей возвращаемого значения:
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. Не уверен, что это добавляет достаточно ясности, чтобы стоило использовать.)
Чтобы увидеть все преобразователи возвращаемого значения, которые поддерживает Argument Clinic, вместе с их параметрами (если таковые имеются), просто запустите Tools/clinic/clinic.py --converters для получения полного списка.
Клонирование существующих функций
Если у вас есть несколько похожих функций, вы можете использовать функцию «клонирования» Clinic. При клонировании существующей функции вы повторно используете:
-
ее параметры, включая
- их имена,
- их преобразователи со всеми параметрами,
- их значения по умолчанию,
- их документацию для каждого параметра,
- их тип (являются ли они только позиционными, позиционными или ключевыми, или только ключевыми), и
- ее преобразователь возвращаемого значения.
Единственное, что не копируется из исходной функции, — это ее документация; синтаксис позволяет указать новую документацию.
Вот синтаксис клонирования функции:
/*[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 и изменяет состояние выполнения Argument Clinic. Это просто: вы просто определяете блок Python.
Блок Python использует другие разделительные строки, чем блок функции Argument Clinic. Он выглядит так:
/*[python input] # python code goes here [python start generated code]*/
Весь код внутри блока Python выполняется в момент его анализа. Весь текст, записанный в stdout внутри блока, перенаправляется в «вывод» после блока.
В качестве примера, вот блок Python, который добавляет статическую целочисленную переменную в код C:
/*[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, вы можете напрямую использовать существующий преобразователь self Argument Clinic, передав желаемый тип как параметр 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]*/
Использование конвертера «определяющего класса»
Библиотека Argument Clinic облегчает получение доступа к определяющему классу метода. Это полезно для методов кучевых типов, которым необходимо получить данные уровня модуля. Используйте PyType_FromModuleAndSpec() для связывания нового кучевого типа с модулем. Теперь вы можете использовать PyType_GetModuleState() для определения класса, чтобы получить состояние модуля, например, из метода модуля.
Пример из Modules/zlibmodule.c. Сначала defining_class добавляется в входные данные Clinic:
/*[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 несовместим с методами __init__ и __new__, которые не могут использовать соглашение METH_METHOD.
Использовать defining_class с методами слотов невозможно. Чтобы получить состояние модуля из таких методов, используйте _PyType_GetModuleByDef для поиска модуля, а затем PyModule_GetState() для получения состояния модуля. Пример из метода слота setattro в Modules/_threadmodule.c:
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.
Создание пользовательского конвертера
Как мы намекнули в предыдущем разделе… вы можете написать свои собственные конвертеры! Конвертер — это просто класс Python, который наследуется от CConverter. Основное назначение пользовательского конвертера — если у вас есть параметр, использующий формат O& — для разбора этого параметра нужно вызвать PyArg_ParseTuple() «функцию-конвертер».
Имя вашего класса-конвертера должно быть *something*_converter. Если имя соответствует этому соглашению, то ваш класс-конвертер будет автоматически зарегистрирован Argument Clinic; его имя будет именем вашего класса без суффикса _converter. (Это реализуется с помощью метакласса.)
Вы не должны наследоваться от CConverter.__init__. Вместо этого вы должны написать функцию converter_init(). converter_init() всегда принимает параметр self; после этого все дополнительные параметры должны быть только ключевыми. Любые аргументы, передаваемые в конвертер в Argument Clinic, будут переданы в вашу функцию 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 -
Булево значение. Если истинно, Argument Clinic добавит
&перед именем переменной при передаче её в функцию impl. -
parse_by_reference -
Булево значение. Если истинно, Argument Clinic добавит
&перед именем переменной при передаче её в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]*/
Этот блок добавляет конвертер в Argument Clinic под именем ssize_t. Параметры, объявленные как ssize_t будут объявлены как тип Py_ssize_t и будут проанализированы с помощью формата 'O&', который вызовет функцию-конвертер ssize_t_converter . ssize_t переменные автоматически поддерживают значения по умолчанию.
Более сложные пользовательские конвертеры могут вставлять пользовательский код C для обработки инициализации и очистки. Вы можете найти больше примеров пользовательских конвертеров в дереве исходного кода CPython; найдите в файлах 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, просто не указывайте никаких аргументов.
Вы по-прежнему можете использовать конвертер self, конвертер возвращаемого значения и указать аргумент 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, при преобразовании вы можете указать любую сигнатуру для этих функций, которая вам нравится. (Если ваша функция не поддерживает ключевые слова, сгенерированная функция парсинга выбросит исключение, если получит какие-либо.)
Изменение и перенаправление вывода Clinic
Неудобно, когда вывод Clinic вклинивается в ваш обычный, вручную отредактированный код на C. К счастью, Clinic настраивается: вы можете буферизовать его вывод для печати позже (или раньше!), или писать вывод в отдельный файл. Вы также можете добавлять префикс или суффикс к каждой строке сгенерированного Clinic вывода.
Хотя изменение вывода Clinic таким образом может улучшить читаемость, это может привести к использованию типов Clinic до их определения или попытке использования кода Clinic, сгенерированного до его определения в вашем коде. Эти проблемы легко решаются путем перестановки объявлений в вашем файле или перемещения места, где находится сгенерированный Clinic код. (Вот почему по умолчанию Clinic выводит все в текущий блок; хотя многие считают, что это затрудняет чтение, это никогда не потребует перестановки вашего кода для исправления проблем с определением перед использованием.)
Начнем с определения некоторых терминов:
- поле
-
В данном контексте поле — это подраздел вывода Clinic. Например,
#defineдля структурыPyMethodDef— это поле, называемое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 файла.
-
file -
Отдельный файл «clinic», который будет автоматически создан Clinic. Выбранное имя файла —
{basename}.clinic{extension}, гдеbasenameиextensionбыли назначены выводомos.path.splitext(), выполненным на текущем файле. (Пример: назначениеfileдля_pickle.cзапишется в_pickle.clinic.c.)Важно: при использовании назначения
fileвы обязательно должны занести сгенерированный файл! -
two-pass -
Буфер, подобный
buffer. Однако буфер с двумя проходами может быть выгружен только один раз и печатает весь текст, отправленный ему во время всей обработки, даже из блоков Clinic после точки выгрузки. -
suppress -
Текст подавляется — отбрасывается.
-
Clinic определяет пять новых директив, которые позволяют переконфигурировать его вывод.
Первая новая директива — dump:
dump <destination>
Это выгружает текущее содержимое указанного назначения в вывод текущего блока и очищает его. Это работает только с назначениями buffer и two-pass.
Вторая новая директива — output. Самый базовый вид output выглядит так:
output <field> <destination>
Это сообщает Clinic выводить поле в назначение. output также поддерживает специальное мета-назначение, называемое everything, которое сообщает Clinic выводить все поля в это назначение.
У output есть ряд других функций:
output push output pop output preset <preset>
output push и output pop позволяют вам помещать и извлекать конфигурации в внутренний стек конфигурации, чтобы временно изменять конфигурацию вывода и легко восстановить предыдущую конфигурацию. Просто поместите перед изменением, чтобы сохранить текущую конфигурацию, а затем извлеките, когда захотите восстановить предыдущую конфигурацию.
output preset устанавливает вывод Clinic в одну из нескольких встроенных предопределенных конфигураций следующим образом:
-
block -
Исходная конфигурация Clinic. Записывает все сразу после блока входных данных.
Подавить
parser_prototypeиdocstring_prototype, записать все остальное вblock. -
file -
Предназначено для записи всего в файл «clinic», который он может. Затем вы
#includeэтот файл в начале вашего файла. Возможно, вам придется переставить ваш файл, чтобы это работало, хотя обычно это просто означает создание объявлений вперед для различныхtypedefиPyTypeObjectопределений.Подавить
parser_prototypeиdocstring_prototype, записатьimpl_definitionвblock, и записать все остальное вfile.Имя файла по умолчанию —
"{dirname}/clinic/{basename}.h". -
buffer -
Сохранить большую часть вывода Clinic для записи в ваш файл в конце. Для файлов 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, записываетdocstring_prototype,methoddef_define, иparser_prototypeвtwo-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 -
Записывает текст в текущий блок. Это то, что Clinic изначально делал.
-
buffer -
Простой текстовый буфер, как встроенное назначение «буфер» выше.
-
file -
Текстовый файл. Назначение файла принимает дополнительный аргумент, шаблон для создания имени файла, например:
назначение <имя> новое <тип> <шаблон_файла>
В шаблоне можно использовать три внутренних строки, которые будут заменены частями имени файла:
- {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 позволяет установить две внутренние переменные в 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) */
Это означает, что макрос всегда работает. Если функция определена, это превращается в правильную структуру, включая конечную запятую. Если функция не определена, это превращается в ничего.
Однако это создаёт одну щекотливую проблему: куда Argument Clinic должен поместить этот дополнительный код при использовании предустановки «блок» для вывода? Он не может поместить его в блок вывода, потому что это может быть деактивировано #ifdef. (В этом и заключается смысл!)
В этой ситуации Argument Clinic записывает дополнительный код в «буфер». Это может означать, что вы получите сообщение об ошибке от Argument Clinic:
Warning in file "Modules/posixmodule.c" on line 12357: Destination buffer 'buffer' not empty at end of file, emptying.
Когда это происходит, просто откройте файл, найдите блок dump buffer , который Argument Clinic добавил в ваш файл (он будет в самом низу), затем переместите его выше структуры PyMethodDef , где используется этот макрос.
Использование Argument Clinic в файлах Python
На самом деле возможно использовать Argument Clinic для предварительной обработки файлов Python. Конечно, нет смысла использовать блоки Argument Clinic, так как вывод не будет иметь смысла для интерпретатора Python. Но использование Argument Clinic для запуска блоков Python позволяет использовать Python как предварительный процессор Python!
Поскольку комментарии Python отличаются от комментариев C, блоки Argument Clinic, встроенные в файлы Python, выглядят немного иначе. Они выглядят так:
#/*[python input]
#print("def foo(): pass")
#[python start generated code]*/
def foo(): pass
#/*[python checksum:...]*/
© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/howto/clinic.html