Spec-Zone.ru › Python 3.8

Руководство по 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»). Но мы упростим этот обзор, чтобы вы могли узнать.

Давайте приступим!

END_OF_DOCUMENT_MARKER
  1. Убедитесь, что вы работаете с недавно обновленной версией CPython trunk.
  2. Найдите встроенную функцию Python, которая вызывает либо PyArg_ParseTuple(), либо PyArg_ParseTupleAndKeywords() и которая ещё не была преобразована для работы с Argument Clinic. В моём примере я использую _pickle.Pickler.dump().
  3. Если вызов функции PyArg_Parse использует любой из следующих форматов:

    O&
    O!
    es
    es#
    et
    et#
    

    или если в нём есть несколько вызовов PyArg_ParseTuple(), вам следует выбрать другую функцию. Argument Clinic поддерживает все эти сценарии. Но это продвинутые темы — давайте сделаем что-то более простое для вашей первой функции.

    Также, если функция содержит несколько вызовов PyArg_ParseTuple() или PyArg_ParseTupleAndKeywords(), где она поддерживает разные типы для одного и того же аргумента, или если функция использует что-то помимо функций PyArg_Parse для парсинга аргументов, то она, вероятно, не подходит для преобразования в Argument Clinic. Argument Clinic не поддерживает универсальные функции или полиморфные параметры.

  4. Добавьте следующий шаблон над функцией, создавая наш блок:

    /*[clinic input]
    [clinic start generated code]*/
    
  5. Вырежьте строку документации и вставьте её между строками [clinic], удалив весь мусор, который делает её правильно оформленной строкой C-строки. Когда вы закончите, у вас должна остаться только текст, отступленный слева, без строк шире 80 символов. (Argument Clinic сохранит отступы внутри строки документации.)

    Если в исходной строке документации первая строка выглядела как подпись функции, удалите её. (Строке документации больше не нужен — при использовании help() для вашей встроенной функции в будущем, первая строка будет автоматически построена на основе подписи функции.)

    Пример:

    /*[clinic input]
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  6. Если ваша строка документации не содержит строки «резюме», Argument Clinic пожалуется. Давайте убедимся, что она есть. Строка «резюме» должна быть абзацем, состоящим из одной строки длиной 80 символов в начале строки документации.

    (Наша примерная строка документации состоит только из строки резюме, поэтому код примера не должен меняться для этого шага.)

  7. Над строкой документации введите имя функции, за которым следует пустая строка. Это должно быть имя функции Python и полный путь к функции — она должна начинаться с имени модуля, включать любые подмодули, и если функция является методом класса, она должна включать имя класса тоже.

    Пример:

    /*[clinic input]
    _pickle.Pickler.dump
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  8. Если это первый раз, когда модуль или класс используется с 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]*/
    
  9. Объявите каждый из параметров функции. Каждый параметр должен получить свою собственную строку. Все строки параметров должны быть отступом от имени функции и строки документации.

    Общая форма этих строк параметров следующая:

    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]*/
    
  10. Если ваша функция имеет | в строке формата, означая, что некоторые параметры имеют значения по умолчанию, вы можете проигнорировать её. Argument Clinic определяет, какие параметры являются необязательными, на основе того, есть ли у них значения по умолчанию или нет.

    Если ваша функция имеет $ в строке формата, означая, что она принимает только ключевые аргументы, укажите * в отдельной строке перед первым ключевым аргументом, отступленный так же, как строки параметров.

    (_pickle.Pickler.dump ни то, ни другое не содержит, поэтому наш пример не меняется.)

  11. Если существующая функция 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]*/
    
  12. Полезно написать строку документации для каждого параметра. Но строки документации для параметров необязательны; вы можете пропустить этот шаг, если хотите.

    Вот как добавить строку документации для параметра. Первая строка документации для параметра должна быть с ещё большим отступом, чем определение параметра. Левый край этой первой строки задаёт левый край для всей строки документации параметра; весь текст, который вы напишите, будет смещён влево на это количество. Вы можете написать столько текста, сколько захотите, на нескольких строках, если хотите.

    Пример:

    /*[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]*/
    
  13. Сохраните и закройте файл, затем запустите 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"
    
  14. Проверьте, что код анализа аргументов, сгенерированный 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 снова, пока они не станут такими же.

  15. Обратите внимание, что последней строкой вывода является объявление вашей функции «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;
    
        ...
    
  1. Вспомните макрос с структурой PyMethodDef для этой функции? Найдите существующую структуру PyMethodDef для этой функции и замените её ссылкой на макрос. (Если встроенная функция находится на уровне модуля, это, вероятно, будет где-то в конце файла; если встроенная функция — метод класса, это, вероятно, будет ниже, но относительно близко к реализации.)

    Обратите внимание, что тело макроса содержит запятую в конце. Поэтому, когда вы замените существующую статическую структуру PyMethodDef макросом, не добавляйте запятую в конец.

    Пример:

    static struct PyMethodDef Pickler_methods[] = {
        __PICKLE_PICKLER_DUMP_METHODDEF
        __PICKLE_PICKLER_CLEAR_MEMO_METHODDEF
        {NULL, NULL}                /* sentinel */
    };
    
  2. Скомпилируйте и затем запустите соответствующие части набора регрессионных тестов. Это изменение не должно вводить новые предупреждения или ошибки во время компиляции, и поведение 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, этот параметр будет установлен в ноль, если эта группа не использовалась, и в ненулевое значение, если эта группа использовалась. (Под использованием или неиспользованием я имею в виду, передавались ли в этом вызове аргументы для этих параметров.)
  • Если нет обязательных аргументов, необязательные группы будут вести себя так, как если бы они были справа от обязательных аргументов.
  • В случае неоднозначности код разбора аргументов отдаёт предпочтение параметрам слева (перед обязательными параметрами).
  • Необязательные группы могут содержать только позиционные параметры.
  • Необязательные группы только предназначены для устаревшего кода. Пожалуйста, не используйте необязательные группы для нового кода.
END_OF_DOCUMENT_MARKER

Использование реальных конвертеров 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. В левой колонке находится устаревший конвертер, а в правой — текст, которым его нужно заменить.

'B'

unsigned_char(bitwise=True)

'b'

unsigned_char

'c'

char

'C'

int(accept={str})

'd'

double

'D'

Py_complex

'es'

str(encoding='name_of_encoding')

'es#'

str(encoding='name_of_encoding', zeroes=True)

'et'

str(encoding='name_of_encoding', accept={bytes, bytearray, str})

'et#'

str(encoding='name_of_encoding', accept={bytes, bytearray, str}, zeroes=True)

'f'

float

'h'

short

'H'

unsigned_short(bitwise=True)

'i'

int

'I'

unsigned_int(bitwise=True)

'k'

unsigned_long(bitwise=True)

'K'

unsigned_long_long(bitwise=True)

'l'

long

'L'

long long

'n'

Py_ssize_t

'O'

object

'O!'

object(subclass_of='&PySomething_Type')

'O&'

object(converter='name_of_c_function')

'p'

bool

'S'

PyBytesObject

's'

str

's#'

str(zeroes=True)

's*'

Py_buffer(accept={buffer, str})

'U'

unicode

'u'

Py_UNICODE

'u#'

Py_UNICODE(zeroes=True)

'w*'

Py_buffer(accept={rwbuffer})

'Y'

PyByteArrayObject

'y'

str(accept={bytes})

'y#'

str(accept={robuffer}, zeroes=True)

'y*'

Py_buffer

'Z'

Py_UNICODE(accept={str, NoneType})

'Z#'

Py_UNICODE(accept={str, NoneType}, zeroes=True)

'z'

str(accept={str, NoneType})

'z#'

str(accept={str, NoneType}, zeroes=True)

'z*'

Py_buffer(accept={buffer, str, NoneType})

В качестве примера, вот наш пример pickle.Pickler.dump с использованием правильного конвертера:

/*[clinic input]
pickle.Pickler.dump

    obj: object
        The object to be pickled.
    /

Write a pickled representation of obj to the open file.
[clinic start generated code]*/

Одно из преимуществ реальных конвертеров заключается в том, что они более гибкие, чем устаревшие конвертеры. Например, конвертер unsigned_int (и все конвертеры unsigned_) могут быть указаны без bitwise=True. Их поведение по умолчанию выполняет проверку диапазона значений, и они не будут принимать отрицательные числа. Вы не можете сделать это с устаревшим конвертером!

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

Булево значение. Если true, Argument Clinic добавит & перед именем переменной при передаче её в функцию impl.

parse_by_reference

Булево значение. Если true, 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.8/howto/clinic.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API