Руководство по Argument Clinic
- author
-
Ларри Хэстингс
Аннотация
Argument Clinic — это препроцессор для файлов CPython C. Его цель — автоматизировать всю рутину, связанную с написанием кода обработки аргументов для «встроенных» функций. Этот документ показывает, как преобразовать вашу первую 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обычно сразу после блока модуля клиники:#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" в конец и использовать это имя для функции impl.
Например, если мы хотим переименовать имена функций C, сгенерированные для pickle.Pickler.dump, это будет выглядеть так:
/*[clinic input] pickle.Pickler.dump as pickler_dumper ...
Базовая функция теперь будет называться pickler_dumper(), а функция impl — 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, вложенные байты NULL (
'\\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-файла и изменяет состояние runtime 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 или подклассом thereof.
В чем смысл? Это позволяет вам переопределить тип 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]*/
Написание пользовательского преобразователя
Как мы намекнули в предыдущем разделе… вы можете написать свои собственные преобразователи! Преобразователь — это просто класс 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; выполните поиск по строке CConverter в файлах C.
Написание пользовательского преобразователя возвращаемого значения
Написание пользовательского преобразователя возвращаемого значения очень похоже на написание пользовательского преобразователя. За исключением того, что это немного проще, потому что преобразователи возвращаемых значений сами по себе намного проще.
Преобразователи возвращаемых значений должны быть подклассами 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 -
Текстовый файл. Назначение файла принимает дополнительный аргумент, шаблон для создания имени файла, например:
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 позволяет установить две внутренние переменные в 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–2022 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.9/howto/clinic.html