HOWTO по Клинике Cпоров

автор:Ларри Гастингс

Аннотация

Клиника Споров — это препроцессор для C файлов CPython. Её цель — автоматизировать создание шаблона, связанного с написанием кода парсинга аргументов для «встроенных» программ. В этом документе рассказывается, как преобразовать вашу первую C функцию для работы с Клиникой Споров, а затем представлены некоторые дополнительные темы по использованию Клиники Споров.

В настоящее время Клиника Споров считается внутренней для CPython. Её использование не поддерживается для файлов за пределами CPython, и не даётся никаких гарантий относительно обратной совместимости для будущих версий. Другими словами: если вы поддерживаете внешнее C расширение для CPython, вы можете поэкспериментировать с Клиникой Споров в собственном коде. Но версия Клиники Споров, которая поставляется со следующей версией CPython может быть полностью несовместима и разрушает весь ваш код.

Цели Клиники Споров

Основная цель Клиники Споров — взять на себя ответственность за весь код парсинга аргументов внутри CPython. Это означает, что когда вы конвертируете функцию для работы с Клиникой Споров, эта функция больше не должна выполнять какой-либо собственный синтаксический анализ аргументов, — сгенерированный код Клиники Споров, должен быть для вас «черным ящиком», где CPython вызывается в вверху, и ваш код вызывается внизу, а PyObject *args (и, возможно, PyObject *kwargs) волшебным образом преобразуется в нужные переменные и типы C.

Чтобы Клиника Споров могла выполнить свою основную задачу, она должна быть простой в использовании. В настоящее время работа с библиотекой синтаксического анализа аргументов CPython является рутинной работой, требующей сохранения избыточной информации в удивительном количестве мест. Когда вы используете Клинику Споров, вам не нужно повторяться.

Очевидно, что никто не захочет использовать Клинику Споров, если она не решает их проблему, без создания собственных новых проблем. Поэтому крайне важно, чтобы Клиника Споров сгенерировала правильный код. Было бы неплохо, если бы код тоже был быстрее, но, по крайней мере, он не должен приводить к серьёзному снижению скорости. (В конце концов, Клиника Споров должна сделать возможное значительное ускорение, мы могли бы переписать её генератором кода для создания индивидуального кода синтаксического анализа аргументов, вместо того, чтобы вызывать универсальную библиотеку парсинга аргументов CPython. Это сделало бы возможным парсинг аргументов максимально быстрым!)

Кроме того, Клиника Споров должна быть достаточно гибкой, чтобы работать с любым подходом к парсингу аргументов. В Python существуют функции с очень странным поведением при синтаксическом анализе; цель Клиники Споров — поддерживать их всех.

Наконец, первоначальной мотивацией для Клиники Споров было обеспечение «сигнатур» интроспекции для встроенных функций CPython. Раньше функции запросов самоанализа вызывали исключение, если вы передали встроенный. С Клиникой Споров это в прошлом!

При работе с Клиникой Споров следует помнить об одной идее: чем больше информации вы передадите ей, тем лучше она сможет выполнять свою работу. По общему признанию, сейчас Клиника Споров относительно проста. Но по мере развития она будет становиться всё более изощрённой, и она должна уметь делать много интересных и умных вещей со всей информацией, которую вы ей предоставляете.

Основные понятия и использование

Клиника Споров поставляется с CPython; вы найдете её в Tools/clinic/clinic.py. Если вы запустите этот сценарий, указав файл C в качестве аргумента :

$ python3 Tools/clinic/clinic.py foo.c

Клиника Споров просканирует файл в поисках строк, которые выглядят следующим образом:

/*[clinic input]

Когда она её находит, она считывает все до строки, которая выглядит примерно так:

[clinic start generated code]*/

Всё, что находится между этими двумя строками, предоставляются для Клиники Споров. Все эти строки, включая начальную и конечную строки комментариев, вместе называются «блоком» Клиники Споров.

Когда Клиника Споров распарсит один из этих блоков, она генерирует выходные данные. Этот вывод перезаписывается в файл C сразу после блока, за ним следует комментарий, содержащий контрольную сумму. Блок Клиники Споров теперь выглядит так:

/*[clinic input]
... clinic input goes here ...
[clinic start generated code]*/
... clinic output goes here ...
/*[clinic end generated code: checksum=...]*/

Если вы запустите Клинику Споров для того же файла во второй раз, Клиника Споров отбросит старый вывод и запишет новый вывод с новой строкой контрольной суммы. Однако, если вход не изменился, выход тоже не изменится.

Вы никогда не должны изменять выходную часть блока Клиники Споров. Вместо этого изменяйте ввод, пока он не даст желаемый результат. (Это цель контрольной суммы — определить, изменил ли кто-то вывод, поскольку эти изменения будут потеряны в следующий раз, когда Клиника Споров запишет новый вывод.)

Для ясности, далее приведена терминология, которую мы будем использовать в Клинике Споров:

  • Первая строка комментария (/*[clinic input]) — это стартовая строка.
  • Последняя строка начального комментария ([clinic start generated code]*/) — это конечная строка.
  • Последняя строка (/*[clinic end generated code: checksum=...]*/) — это строка с контрольной суммой.
  • Между начальной и конечной строками находится ввод.
  • Между конечной строкой и строкой контрольной суммы находится вывод.
  • Весь текст в совокупности, от начальной строки до строки контрольной суммы включительно, представляет собой блок. (Блок, который не был успешно обработан Клиникой Споров, ещё не имеет вывода или строки контрольной суммы, но по-прежнему считается блоком.)

Преобразование первой функции

Лучший способ понять, как работает Клиника Споров — это преобразовать функцию для работы с ней. Итак, вот минимум шагов, которые вам нужно выполнить, чтобы преобразовать функцию для работы с Клиникой Споров. Обратите внимание, что для кода, который вы планируете регистрировать в CPython, вам действительно следует пойти дальше по преобразованию, используя некоторые из расширенных концепций, которые вы увидите позже в документе (например, «обратные конвертеры» и «самоконвертеры»). Но мы сделаем это просто для данного HOWTO, чтобы вы могли учиться.

Давайте погрузимся!

  1. Убедитесь, что вы работаете с недавно обновленной веткой CPython.

  2. Найдите встроенный Python, который вызывает PyArg_ParseTuple() или PyArg_ParseTupleAndKeywords() и ещё не был преобразован для работы с Клиникой Споров. В моем примере я использую _pickle.Pickler.dump().

  3. Если при вызове функции PyArg_Parse используются любые из следующих единиц формата :

    O&
    O!
    es
    es#
    et
    et#
    

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

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

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

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

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

    Пример:

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

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

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

    Пример:

    /*[clinic input]
    _pickle.Pickler.dump
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  8. Если это первый раз, когда модуль или класс использовались с Клиникой Споров в C файле, вы должны объявить модуль и/или класс. Правильная гигиена Клиники Споров предпочитает объявлять их в отдельном блоке где-то в верхней части файла C, точно так же, как файлы include и static идут вверху. (В нашем примере кода мы просто покажем два блока рядом друг с другом.)

    Имя класса и модуля должно совпадать с тем, которое видит Python. Проверьте имя, определенное в PyModuleDef или PyTypeObject соответственно.

    Когда вы объявляете класс, вы также должны указать два аспекта его типа в C: объявление типа, которое вы бы использовали для указателя на экземпляр этого класса, и указатель на PyTypeObject для этого класса.

    Пример:

    /*[clinic input]
    module _pickle
    class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    [clinic start generated code]*/
    
    /*[clinic input]
    _pickle.Pickler.dump
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  9. Объявите каждый из параметров функции. У каждого параметра должна быть своя строка. Все строки параметров должны иметь отступ от имени функции и строки документации.

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

    name_of_parameter: converter = default_value
    

    Если параметр содержит значение по умолчанию, добавьте его после преобразователя :

    name_of_parameter: converter = default_value
    

    Поддержка «значений по умолчанию» в Клинике Споров довольно сложна; пожалуйста см. раздел ниже о значениях по умолчанию для получения дополнительной информации.

    Добавьте пустую строку под параметрами.

    Что такое «конвертер»? Он устанавливает как тип переменной, используемой в C, так и метод преобразования значения Python в значение C во время выполнения. Сейчас вы собираетесь использовать так называемый «устаревший конвертер» — удобный синтаксис, призванный упростить перенос старого кода в Клинику Споров.

    Для каждого параметра скопируйте «единицу формата» для этого параметра из аргумента формата PyArg_Parse() и укажите его в качестве преобразователя в виде строки в кавычках. («Единица формата» — это формальное имя подстроки, состоящей из одного-трёх символов, в параметре format, который сообщает функции синтаксического анализа аргументов, каков тип переменной и как ее преобразовать. Подробнее о единицах формата см. Анализ аргументов и сборка значений. )

    Для единиц формата с несколькими символами, таких как z#, используйте всю строку из двух или трёх символов.

    Пример:

     /*[clinic input]
     module _pickle
     class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
     [clinic start generated code]*/
    
     /*[clinic input]
     _pickle.Pickler.dump
    
        obj: 'O'
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  10. Если в строке формата вашей функции указано |, что означает, что некоторые параметры имеют значения по умолчанию, вы можете игнорировать это. Клиника Споров определяет, какие параметры являются необязательными, в зависимости от того, имеют ли они значения по умолчанию.

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

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

  11. Если существующая функция C вызывает PyArg_ParseTuple() (в отличие от PyArg_ParseTupleAndKeywords()), то все её аргументы являются только позиционными.

    Чтобы пометить все параметры как позиционные в Клинике Споров, добавьте / в отдельную строку после последнего параметра с таким же отступом, как и строки параметров.

    В настоящее время это всё или ничего; либо все параметры являются позиционными, либо ни один из них не является. (В будущем Клиника Споров может ослабить это ограничение.)

    Пример:

    /*[clinic input]
    module _pickle
    class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    [clinic start generated code]*/
    
    /*[clinic input]
    _pickle.Pickler.dump
    
        obj: 'O'
        /
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  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]*/
    

    Очевидно, что если Клиника Споров не выдала никаких выходных данных, это потому, что она обнаружила ошибку во входных данных. Продолжайте исправлять ошибки и повторяйте попытки, пока Клиника Споров не обработает ваш файл без жалоб.

    Для удобства чтения большая часть связующего кода была сгенерирована в файл .c.h. Вам нужно будет включить это в исходный файл .c, обычно сразу после блока клинического модуля:

    #include "clinic/_pickle.c.h"
    
  14. Ещё раз проверьте, что созданный Клиникой Споров код для синтаксического анализа аргументов выглядит в основном так же, как и существующий код.

    Во-первых, убедитесь, что в обоих местах используется одна и та же функция анализа аргументов. Существующий код должен вызывать PyArg_ParseTuple() или PyArg_ParseTupleAndKeywords(); убедитесь, что код, сгенерированный Клиникой Споров, вызывает точно такую же функцию.

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

    (Клиника Споров всегда генерирует свои строки формата с :, за которым следует имя функции. Если строка формата существующего кода заканчивается на ;, чтобы облегчить использование, это изменение безвредно — не беспокойтесь об этом.)

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

    В-четвертых, внутри выходной части блока вы найдете макрос препроцессора, определяющий соответствующую статическую структуру PyMethodDef для этой встроенной функции:

    #define __PICKLE_PICKLER_DUMP_METHODDEF    \
    {"dump", (PyCFunction)__pickle_Pickler_dump, METH_O, __pickle_Pickler_dump__doc__},
    

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

    Если какие-либо из этих элементов как-нибудь отличаются, настройте спецификацию функции Клиники Споров и повторно запустите Tools/clinic/clinic.py, пока они не станут одинаковыми.

  15. Обратите внимание, что последняя строка вывода — это объявление вашей функции «impl». Вот где идёт встроенная реализация. Удалите существующий прототип функции, которую вы изменяете, но оставьте открывающую фигурную скобку. Теперь удалите код синтаксического анализа аргументов и объявления всех переменных, в которые она выгружает аргументы. Обратите внимание, что аргументы Python теперь являются аргументами функции impl; если реализация использовала разные имена для этих переменных, исправьте это.

    Повторим ещё раз, просто потому, что это немного странно. Теперь ваш код должен выглядеть так:

    static return_type
    your_function_impl(...)
    /*[clinic end generated code: checksum=...]*/
    {
    ...
    

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

    Пример:

    /*[clinic input]
    module _pickle
    class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    [clinic start generated code]*/
    /*[clinic end generated code: checksum=da39a3ee5e6b4b0d3255bfef95601890afd80709]*/
    
    /*[clinic input]
    _pickle.Pickler.dump
    
        obj: 'O'
            The object to be pickled.
        /
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
    PyDoc_STRVAR(__pickle_Pickler_dump__doc__,
    "Write a pickled representation of obj to the open file.\n"
    "\n"
    ...
    static PyObject *
    _pickle_Pickler_dump_impl(PicklerObject *self, PyObject *obj)
    /*[clinic end generated code: checksum=3bd30745bf206a48f8b576a1da3d90f55a0a4187]*/
    {
        /* Check whether the Pickler was initialized correctly (issue3664).
           Developers often forget to call __init__() in their subclasses, which
           would trigger a segfault without this check. */
        if (self->write == NULL) {
            PyErr_Format(PicklingError,
                         "Pickler.__init__() was not called by %s.__init__()",
                         Py_TYPE(self)->tp_name);
            return NULL;
        }
    
        if (_Pickler_ClearBuffer(self) < 0)
            return NULL;
    
        ...
    
  16. Помните макрос со структурой PyMethodDef для этой функции? Найдите существующую структуру PyMethodDef для этой функции и замените её ссылкой на макрос. (Если встроенная функция находится в области видимости модуля, это, вероятно, будет очень близко к концу файла; если встроенная функция является методом класса, это, вероятно, будет ниже, но относительно близко к реализации.)

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

    Пример:

    static struct PyMethodDef Pickler_methods[] = {
        __PICKLE_PICKLER_DUMP_METHODDEF
        __PICKLE_PICKLER_CLEAR_MEMO_METHODDEF
        {NULL, NULL}                /* sentinel */
    };
    
  17. Скомпилируйте, а затем запустите соответствующие части набора регрессионных тестов. Это изменение не должно приводить к появлению каких-либо новых предупреждений или ошибок во время компиляции, а также не должно быть видимых извне изменений в поведении Python.

    Ну, за исключением одного отличия: inspect.signature(), запущенный в вашей функции, теперь должен предоставить правильную сигнатуру!

    Поздравляем, вы перенесли свою первую функцию для работы с Клиникой Споров!

Продвинутые темы

Теперь, когда у вас есть некоторый опыт работы с Клиникой Споров, пришло время для некоторых продвинутых тем.

Символические значения по умолчанию

Значение по умолчанию, которое вы указываете для параметра, не может быть произвольным выражением. В настоящее время явно поддерживаются :

  • Числовые константы (целые числа и числа с плавающей запятой)
  • Строковые константы
  • True, False и None
  • Простые символьные константы, такие как sys.maxsize, которые должны начинаться с имени модуля

Если вам интересно, это реализовано в from_builtin() в файле Lib/inspect.py.

(В будущем может потребоваться ещё более сложная разработка, чтобы можно было использовать полные выражения, такие как CONSTANT - 1.)

Переименование функций и переменных C, созданных Клиникой Споров

Клиника Споров автоматически называет функции, которые она генерирует для вас. Иногда это может вызвать проблему, если сгенерированное имя конфликтует с именем существующей функции C. Есть простое решение: переопределить имена, используемые для функций C. Просто добавьте ключевое слово "as" в строку объявления функции, а затем имя функции, которую вы хотите использовать. Клиника Споров будет использовать это имя функции для базовой (сгенерированной) функции, затем добавит "_impl" в конец и будет использовать это имя для имени функции impl.

Например, если бы мы хотели переименовать имена функций C, сгенерированные для pickle.Pickler.dump, это выглядело бы так:

/*[clinic input]
pickle.Pickler.dump as pickler_dumper

...

Базовая функция теперь будет называться pickler_dumper(), а функция impl теперь будет называться pickler_dumper_impl().

Точно так же у вас может возникнуть проблема, когда вы хотите дать параметру конкретное имя Python, но это имя может быть неудобным в C. Клиника Споров позволяет вам давать параметру разные имена в Python и в C, используя тот же синтаксис "as":

/*[clinic input]
pickle.Pickler.dump

    obj: object
    file as file_obj: object
    protocol: object = NULL
    *
    fix_imports: bool = True

Здесь имя, используемое в Python (в сигнатуре и массиве keywords), будет file, а переменная C будет иметь имя file_obj.

Вы также можете использовать его для переименования параметра self !

Преобразование функций с помощью PyArg_UnpackTuple

Чтобы преобразовать функцию, анализирующую свои аргументы, с помощью PyArg_UnpackTuple(), просто запишите все аргументы, указав каждый как object. Вы можете указать аргумент type для приведения типа соответствующим образом. Все аргументы должны быть помечены как позиционные (добавьте / в отдельную строку после последнего аргумента).

В настоящее время сгенерированный код будет использовать PyArg_ParseTuple(), но это скоро изменится.

Дополнительные группы

У некоторых устаревших функций сложный подход к синтаксическому анализу своих аргументов: они подсчитывают количество позиционных аргументов, а затем используют оператор switch для вызова одного из нескольких различных вызовов PyArg_ParseTuple() в зависимости от количества позиционных аргументов. (Эти функции не могут принимать аргументы, состоящие только из ключевых слов.) этот подход использовался для имитации необязательных аргументов ещё до создания PyArg_ParseTupleAndKeywords().

Хотя функции, использующие этот подход, часто можно преобразовать для использования PyArg_ParseTupleAndKeywords(), дополнительных аргументов и значений по умолчанию, это не всегда возможно. У некоторых из этих устаревших функций есть поведение, которое PyArg_ParseTupleAndKeywords() напрямую не поддерживает. Наиболее очевидным примером является встроенная функция range(), у которой есть необязательный аргумент слева от обязательного аргумента! Другой пример — curses.window.addch(), у которого есть группа из двух аргументов, которые всегда должны указываться вместе. (Аргументы называются x и y; если вы вызываете функцию, передающуюся в x, вы также должны передать y; и если вы не передадите x, вы также не сможете передать y.)

В любом случае целью Клиники Споров является поддержка синтаксического анализа аргументов для всех существующих встроенных команд CPython без изменения их семантики. Поэтому Клиника Споров поддерживает этот альтернативный подход к синтаксическому анализу с использованием так называемого необязательных групп. Необязательные группы — это группы аргументов, которые должны передаваться вместе. Они могут быть слева или справа от требуемых аргументов. Их можно использовать только с позиционными параметрами.

Примечание

Дополнительные группы предназначены только для использования при преобразовании функций, которые выполняют несколько вызовов, в PyArg_ParseTuple()! Функции, использующие любой другой подход к синтаксическому анализу аргументов, почти никогда не следует преобразовывать в Клинике Споров с использованием дополнительных групп. Функции, использующие необязательные группы, в настоящее время не могут иметь точных сигнатур в Python, потому что Python просто не понимает эту концепцию. По возможности избегайте использования дополнительных групп.

Чтобы указать необязательную группу, добавьте [ в отдельную строку перед параметрами, которые вы хотите сгруппировать вместе, и ] в отдельной строке после этих параметров. В качестве примера, вот как curses.window.addch использует необязательные группы, чтобы сделать первые два параметра и последний параметр необязательными:

/*[clinic input]

curses.window.addch

    [
    x: int
      X-coordinate.
    y: int
      Y-coordinate.
    ]

    ch: object
      Character to add.

    [
    attr: long
      Attributes for the character.
    ]
    /

...

Примечание:

  • Для каждой необязательной группы в функцию impl, представляющую группу, будет передан один дополнительный параметр. У параметра будет тип int с именем group_{direction}_{number}, где {direction} — это либо right, либо left, в зависимости от того, находится ли группа до или после требуемых параметров, а {number} — это монотонно возрастающее число (начиная с 1), указывающее, как далеко группа находится от обязательные параметры. При вызове impl этот параметр будет установлен в ноль, если эта группа не использовалась, и в ненулевое значение, если эта группа использовалась. (Под используемым или неиспользованным я подразумеваю, получили ли параметры аргументы в этом вызове.)
  • Если нет обязательных аргументов, необязательные группы будут вести себя так, как если бы они находились справа от требуемых аргументов.
  • В случае неоднозначности код разбора аргументов отдает предпочтение параметрам слева (перед обязательными параметрами).
  • Дополнительные группы могут содержать только позиционные параметры.
  • Дополнительные группы предназначены только для устаревшего кода. Пожалуйста, не используйте необязательные группы для нового кода.

Использование реальных конвертеров Клиники Споров вместо «устаревших конвертеров»

Чтобы сэкономить время и свести к минимуму объем обучения, который вам нужно изучить для достижения первого порта в Клинике Споров, в приведённом выше пошаговом руководстве предлагается использовать «устаревшие конвертеры». «Устаревшие конвертеры» — это удобство, специально разработанное для облегчения переноса существующего кода в Клинике Споров. И чтобы было ясно, их использование допустимо при переносе кода на Python 3.4.

Однако в долгосрочной перспективе мы, вероятно, захотим, чтобы все наши блоки использовали реальный синтаксис Клиники Споров для конвертеров. Почему? Пара причин:

  • Правильные конвертеры намного легче читать и понятнее по своему назначению.
  • Существуют некоторые единицы формата, которые не поддерживаются как «устаревшие конвертеры», поскольку они требуют аргументов, а устаревший синтаксис преобразователя не поддерживает указание аргументов.
  • В будущем у нас может появиться новая библиотека парсинга аргументов, которая не ограничивается тем, что поддерживает PyArg_ParseTuple(); эта гибкость не будет доступна для параметров, использующих устаревшие конвертеры.

Поэтому, если вы не против немного дополнительных усилий, воспользуйтесь обычными конвертерами вместо устаревших конвертеров.

В двух словах, синтаксис конвертеров Клиники Споров (не устаревших) выглядит как вызов Python функции. Однако, если у функции нет явных аргументов (все функции принимают значения по умолчанию), вы можете опустить круглые скобки. Таким образом, bool и bool() — это абсолютно одинаковые конвертеры.

Все аргументы конвертеров Клиники Споров только ключевые. Все конвертеры Клиники Споров принимают следующие аргументы:

c_default
Значение по умолчанию для этого параметра, если оно определено в C. В частности, это будет инициализатор для переменной, объявленной в «функции парсинга». См. раздел значений по умолчанию, чтобы узнать, как это использовать. Указывается в виде строки.
annotation
Значение аннотации для этого параметра. В настоящее время не поддерживается, поскольку PEP 8 требует, чтобы библиотека Python не могла использовать аннотации.

Кроме того, некоторые конвертеры принимают дополнительные аргументы. Вот список этих аргументов с указанием их значений:

accept

Набор типов Python (и, возможно, псевдотипов); это ограничивает допустимый аргумент Python значениями этих типов. (Это средство не общего назначения; как правило, оно поддерживает только определенные списки типов, как показано в устаревшей таблице преобразователя.)

Чтобы принять None, добавьте в набор NoneType.

bitwise
Поддерживается только для целых чисел без знака. Собственное целочисленное значение этого аргумента Python будет записано в параметр без какой-либо проверки диапазона, даже для отрицательных значений.
converter
Поддерживается только конвертером object. Задаёт имя C «функции конвертера», которое будет использоваться для преобразования объекта в собственный тип.
encoding
Поддерживается только для строк. Задаёт кодировку для использования при преобразовании этой строки из значения Python str (Юникод) в значение C char *.
subclass_of
Поддерживается только для конвертера object. Требует, чтобы значение Python было подклассом типа Python, как выражено в C.
type
Поддерживается только для конвертеров object и self. Задает тип C, который будет использоваться для объявления переменной. Значение по умолчанию — "PyObject *".
zeroes
Поддерживается только для строк. Если истинно, внутри значения разрешены встроенные байты NUL ('\\0'). Длина строки будет передана в функцию impl сразу после параметра строки как параметр с именем <parameter_name>_length.

Обратите внимание, что не все возможные комбинации аргументов будут работать. Обычно эти аргументы реализуются конкретным PyArg_ParseTuple единиц формата с определенным поведением. Например, в настоящее время вы не можете вызвать unsigned_short, не указав также bitwise=True. Хотя вполне разумно думать, что это сработает, эта семантика не сопоставляется ни с одной существующей единицей формата. Так что Клиника Споров не поддерживает это. (Или, по крайней мере, пока.)

Ниже приведена таблица, в которой показано преобразование устаревших конвертеров в настоящие конвертеры Клиники Споров. Слева находится устаревший конвертер, справа текст, которым вы его заменили.

'B' unsigned_char(bitwise=True)
'b' unsigned_char
'c' char
'C' int(accept={str})
'd' double
'D' Py_complex
'es' str(encoding='name_of_encoding')
'es#' str(encoding='name_of_encoding', zeroes=True)
'et' str(encoding='name_of_encoding', accept={bytes, bytearray, str})
'et#' str(encoding='name_of_encoding', accept={bytes, bytearray, str}, zeroes=True)
'f' float
'h' short
'H' unsigned_short(bitwise=True)
'i' int
'I' unsigned_int(bitwise=True)
'k' unsigned_long(bitwise=True)
'K' unsigned_long_long(bitwise=True)
'l' long
'L' long long
'n' Py_ssize_t
'O' object
'O!' object(subclass_of='&PySomething_Type')
'O&' object(converter='name_of_c_function')
'p' bool
'S' PyBytesObject
's' str
's#' str(zeroes=True)
's*' Py_buffer(accept={buffer, str})
'U' unicode
'u' Py_UNICODE
'u#' Py_UNICODE(zeroes=True)
'w*' Py_buffer(accept={rwbuffer})
'Y' PyByteArrayObject
'y' str(accept={bytes})
'y#' str(accept={robuffer}, zeroes=True)
'y*' Py_buffer
'Z' Py_UNICODE(accept={str, NoneType})
'Z#' Py_UNICODE(accept={str, NoneType}, zeroes=True)
'z' str(accept={str, NoneType})
'z#' str(accept={str, NoneType}, zeroes=True)
'z*' Py_buffer(accept={buffer, str, NoneType})

В качестве примера приведём наш образец pickle.Pickler.dump с использованием соответствующего конвертера:

/*[clinic input]
pickle.Pickler.dump

    obj: object
        The object to be pickled.
    /

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

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

Клиника Споров покажет вам все доступные конвертеры. Для каждого конвертера он покажет вам все параметры, которые он принимает, а также значение по умолчанию для каждого параметра. Просто запустите Tools/clinic/clinic.py --converters, чтобы увидеть полный список.

Py_buffer

При использовании конвертера Py_buffer (или устаревших конвертеров 's*', 'w*', '*y' или 'z*') вы не должны вызывать в предоставленном буфере. Клиника Споров генерирует код, который делает это за вас (в функции синтаксического анализа).

Расширенные конвертеры

Помните те единицы формата, которые вы пропустили в первый раз, потому что они были продвинутыми? Вот как с этим справиться.

Хитрость в том, что все эти единицы формата принимают аргументы — либо функции преобразования, либо типы, либо строки, определяющие кодировку. (Но «устаревшие конвертеры» не поддерживают аргументы. Вот почему мы пропустили их для вашей первой функции.) Аргумент, который вы указали для единицы формата, теперь является аргументом для преобразователя; этот аргумент либо converter (для O&), либо subclass_of (для O!), либо encoding (для всех единиц формата, начинающихся с e).

При использовании subclass_of вы также можете использовать другой настраиваемый аргумент для object(): type, который позволяет вам установить тип, фактически используемый для параметра. Например, если вы хотите убедиться, что объект является подклассом PyUnicode_Type, вы, вероятно, захотите использовать конвертер object(type='PyUnicodeObject *', subclass_of='&PyUnicode_Type').

Одна из возможных проблем с использованием Клиники Споров: она снижает возможную гибкость для единиц формата, начиная с e. При написании вызова PyArg_Parse вручную, вы теоретически можете решить во время выполнения, какую строку кодирования передать в PyArg_ParseTuple(). Но теперь эта строка должна быть жестко закодирована во время предварительной обработки аргументов. Это ограничение является преднамеренным; это значительно упростило поддержку этого блока формата и может позволить провести оптимизацию в будущем. Это ограничение не кажется необоснованным; сам CPython всегда передаёт статические жёстко закодированные строки кодирования для параметров, единицы формата которых начинаются с e.

Значения параметров по умолчанию

Значения по умолчанию для параметров могут быть любыми из ряда значений. В простейшем случае это могут быть строковые литералы, литералы типа int или float :

foo: str = "abc"
bar: int = 123
bat: float = 45.6

Они также могут использовать любые встроенные константы Python :

yep:  bool = True
nope: bool = False
nada: object = None

Также имеется специальная поддержка значения по умолчанию NULL и простых выражений, описанных в следующих разделах.

Значение по умолчанию NULL

Для строковых и объектных параметров вы можете установить их None, чтобы указать, что по умолчанию нет. Однако это означает, что переменная C будет инициализирована как Py_None. Для удобства существует специальное значение NULL именно по этой причине: с точки зрения Python оно ведёт себя как значение по умолчанию None, но переменная C инициализируется с помощью NULL.

Выражения, указанные как значения по умолчанию

Значение по умолчанию для параметра может быть больше, чем просто буквальное значение. Это может быть целое выражение с использованием математических операторов и поиска атрибутов объектов. Однако эта поддержка не совсем простая из-за некоторой неочевидной семантики.

Рассмотрим следующий пример:

foo: Py_ssize_t = sys.maxsize - 1

sys.maxsize может содержать разные значения на разных платформах. Поэтому Клиника Споров не может просто вычислить это выражение локально и жёстко запрограммировать его на C. Поэтому она сохраняет значение по умолчанию таким образом, чтобы оно было вычислено во время выполнения, когда пользователь запрашивает сигнатуру функции.

Какое пространство имён доступно при вычислении выражения? Оно вычисляется в контексте модуля, из которого пришла встроенная команда. Итак, если в вашем модуле есть атрибут под названием «max_widgets», вы можете просто использовать его :

foo: Py_ssize_t = max_widgets

Если символ не найден в текущем модуле, он не выполняет поиск в sys.modules. Вот как он может найти sys.maxsize например. (Поскольку вы заранее не знаете, какие модули пользователь загрузит в свой интерпретатор, лучше ограничиться модулями, которые предварительно загружены самим Python.)

Вычисление значений по умолчанию только во время выполнения означает, что Клиника Споров не может вычислить правильное эквивалентное значение по умолчанию C. Так что вам нужно сказать это прямо. При использовании выражения необходимо также указать эквивалентное выражение на языке C, используя параметр c_default для преобразователя :

foo: Py_ssize_t(c_default="PY_SSIZE_T_MAX - 1") = sys.maxsize - 1

Еще одна сложность: Клиника Споров не может знать заранее, действительно ли предоставленное вами выражение. Он анализирует его, чтобы убедиться, что оно выглядит законным, но не может фактически знать. Вы должны быть очень осторожны при использовании выражений для указания значений, которые гарантированно будут действительны во время выполнения!

Наконец, поскольку выражения должны быть представлены как статические значения C, существует множество ограничений на допустимые выражения. Вот список функций Python, которые вам не разрешено использовать:

  • Вызов функций.
  • Встроенные операторы if (3 if foo else 5).
  • Автоматическая последовательность распаковки (*[1, 2, 3]).
  • Список/множество/словарь и выражений генератора.
  • Кортеж/список/множество/словарь литералов.

Использование возвратного конвертера

По умолчанию функция impl, которую генерирует для вас Клиника Споров, возвращает PyObject *. Но ваша функция C часто вычисляет некоторый тип C, а затем в последний момент преобразует его в PyObject *. Клиника Споров выполняет преобразование ваших входных данных из типов Python в родные типы C — почему бы не преобразовать возвращаемое вами значение из родного типа C в тип Python?

Вот что делает «конвертер возврата». Он изменяет вашу функцию impl, чтобы она возвращала некоторый тип C, а затем добавляет код к сгенерированной (не-impl) функции для обработки преобразования этого значения в соответствующий PyObject *.

Синтаксис конвертеров возврата аналогичен синтаксису конвертеров параметров. Вы указываете конвертер возврата, как если бы это была аннотация возврата к самой функции. Преобразователи возврата ведут себя так же, как конвертеры параметров; они принимают аргументы, все аргументы состоят только из ключевых слов, и если вы не меняете ни один из аргументов по умолчанию, вы можете пропустить круглые скобки.

(Если вы используете как "as", так и конвертером возврата для своей функции, "as" должен стоять перед конвертером возврата.)

Есть ещё одно осложнение при использовании конвертеров возврата: как вы указываете на возникновение ошибки? Обычно функция возвращает действительный указатель (не NULL) в случае успеха и NULL в случае ошибки. Но если вы используете конвертер возврата целых чисел, все числа действительны. Как Клиника Споров обнаруживает ошибку? Её решение: каждый конвертер возврата неявно ищет специальное значение, указывающее на ошибку. Если вы возвращаете это значение и была установлена ошибка (PyErr_Occurred() возвращает истинное значение), то сгенерированный код распространит ошибку. В противном случае он будет кодировать возвращаемое вами значение как обычно.)

В настоящее время Клиника Споров поддерживает только несколько обратных конвертеров :

bool
int
unsigned int
long
unsigned int
size_t
Py_ssize_t
float
double
DecodeFSDefault

Ни один из них не принимает параметров. Для первых трёх вернёт -1, чтобы указать на ошибку. Для DecodeFSDefault тип возврата — const char *; вернуть указатель NULL, чтобы указать на ошибку.

(Существует также экспериментальный конвертер NoneType, который позволяет возвращать Py_None в случае успеха или NULL в случае неудачи, не увеличивая счетчик ссылок на Py_None. Я не уверен, что это добавляет достаточно ясности, чтобы его стоило использовать.)

Чтобы увидеть все поддерживаемые Клиникой Споров конвертеры возврата, а также их параметры (если есть), просто запустите Tools/clinic/clinic.py --converters для получения полного списка.

Клонирование существующих функций

Если у вас есть несколько похожих функций, вы можете использовать функцию «клонирования» Клиники. Когда вы клонируете существующую функцию, вы повторно используете :

  • Её параметры, в том числе;
    • Её имена;
    • Её конвертеры, со всеми параметрами;
    • Значения по умолчанию;
    • Её строки документации для каждого параметра;
    • Её вид (независимо от того, являются ли они только позиционными, позиционными или ключевыми, или только ключевыми), и;
  • Её конвертер возврата.

Единственное, что не скопировано из исходной функции — это её строка документации; синтаксис позволяет указать новую строку документации.

Синтаксис для клонирования функции:

/*[clinic input]
module.class.new_function [as c_basename] = module.class.existing_function

Docstring for new_function goes here.
[clinic start generated code]*/

(Функции могут быть в разных модулях или классах. Я написал module.class в примере, чтобы проиллюстрировать, что вы должны использовать полный путь к обеим функциям.)

К сожалению, нет синтаксиса для частичного клонирования функции или клонирования функции с последующим её изменением. Клонирование — это предложение по принципу «всё или ничего».

Кроме того, функция, из которой вы клонируете, должна быть предварительно определена в текущем файле.

Вызов кода Python

Остальные сложные темы требуют от вас написания кода Python, который находится внутри вашего файла C и изменяет состояние выполнения Клиники Споров. Это просто: вы просто определяете блок Python.

В блоке Python используются другие разделительные строки, чем в функциональном блоке Клиники Споров. Это выглядит так:

/*[python input]
# python code goes here
[python start generated code]*/

Весь код внутри блока Python выполняется в момент его анализа. Весь текст, записанный в стандартный вывод внутри блока, перенаправляется на «вывод» после блока.

В качестве примера приведем блок Python, который добавляет статическую целочисленную переменную в код C:

/*[python input]
print('static int __ignored_unused_variable__ = 0;')
[python start generated code]*/
static int __ignored_unused_variable__ = 0;
/*[python checksum:...]*/

Использование «самоконвертера»

Клиника Споров автоматически добавляет для вас параметр self, используя конвертер по умолчанию. Он автоматически устанавливает type этого параметра на «указатель на экземпляр», который вы указали при объявлении типа. Однако вы можете переопределить конвертер Клиники Споров и указать его самостоятельно. Просто добавьте свой собственный параметр self в качестве первого параметра в блок и убедитесь, что его преобразователь является экземпляром self_converter или его подклассом.

В чём смысл? Это позволяет вам переопределить тип self или дать ему другое имя по умолчанию.

Как указать пользовательский тип, в который нужно преобразовать self? Если у вас есть только одна или две функции с одним и тем же типом для self, вы можете напрямую использовать существующий преобразователь self от Клиники Споров, передав тип, который вы хотите использовать, в качестве параметра type:

/*[clinic input]

_pickle.Pickler.dump

  self: self(type="PicklerObject *")
  obj: object
  /

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

С другой стороны, если у вас есть много функций, которые будут использовать один и тот же тип для self, лучше всего создать свой собственный преобразователь, создав подкласс self_converter, но перезаписав поле type:

/*[python input]
class PicklerObject_converter(self_converter):
    type = "PicklerObject *"
[python start generated code]*/

/*[clinic input]

_pickle.Pickler.dump

  self: PicklerObject
  obj: object
  /

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

Написание собственного конвертера

Как мы намекали в предыдущем разделе … вы можете писать свои собственные конвертеры! Конвертер — это просто класс Python, унаследованный от CConverter. Основное назначение настраиваемого конвертера — если у вас есть параметр, использующий единицу формата O& этого параметра, означает вызов «функции конвертера» PyArg_ParseTuple().

Ваш класс конвертера должен называться *something*_converter. Если имя соответствует этому соглашению, тогда ваш класс конвертера будет автоматически зарегистрирован в Клинике Споров; его имя будет именем вашего класса с удаленным суффиксом _converter. (Это достигается с помощью метакласса.)

Вы не должны создавать подклассы CConverter.__init__. Вместо этого вы должны написать функцию converter_init(). converter_init() всегда принимает параметр self; после этого все дополнительные параметры должны будут содержать только ключевые. Любые аргументы, переданные конвертеру в Клинике Споров, будут переданы на ваш converter_init().

Есть несколько дополнительных полей CConverter, которые вы можете указать в своем подклассе. Вот текущий список :

type
Тип C, используемый для этой переменной. type должен быть строкой Python, определяющей тип, например int. Если это тип указателя, строка типа должна заканчиваться на ' *'.
default
Значение Python по умолчанию для этого параметра в виде значения Python. Или магическое значение unspecified, если нет значения по умолчанию.
py_default
default, как он должен отображаться в коде Python в виде строки. Или None, если нет по умолчанию.
c_default
default, как он должен отображаться в коде C, в виде строки. Или None, если нет по умолчанию.
c_ignored_default
Значение по умолчанию, используемое для инициализации переменной C, когда нет значения по умолчанию, но не указание значения по умолчанию может привести к предупреждению о «неинициализированной переменной». Это может легко произойти при использовании групп опций — хотя правильно написанный код на самом деле никогда не будет использовать это значение, переменная действительно передается в impl, и компилятор C будет жаловаться на «использование» неинициализированного значения. Это значение всегда должно быть непустой строкой.
converter
Имя функции конвертера C в виде строки.
impl_by_reference
Логическое значение. Если истинно, Клиника Споров добавит & перед именем переменной при передаче её в функцию impl.
parse_by_reference
Логическое значение. Если истинно, Клиника Споров добавит & перед именем переменной при передаче в PyArg_ParseTuple().

Вот простейший пример специального конвертера из Modules/zlibmodule.c:

/*[python input]

class ssize_t_converter(CConverter):
    type = 'Py_ssize_t'
    converter = 'ssize_t_converter'

[python start generated code]*/
/*[python end generated code: output=da39a3ee5e6b4b0d input=35521e4e733823c7]*/

Этот блок добавляет в Клинику Споров конвертер с именем ssize_t. Параметры, объявленные как ssize_t, будут объявлены как тип Py_ssize_t и будут проанализированы модулем формата 'O&', который вызовет функцию конвертера ssize_t_converter. Переменные ssize_t автоматически поддерживают значения по умолчанию.

Более сложные пользовательские конвертеры могут вставлять собственный код C для обработки инициализации и очистки. Вы можете увидеть больше примеров пользовательских конвертеров в дереве исходных текстов CPython; grep’ните файлы C для строки CConverter.

Написание собственного конвертера возврата

Написание специального конвертера возврата очень похоже на написание специального конвертера. За исключением того, что это несколько проще, потому что обратные конвертеры сами по себе намного проще.

У возвратных конвертеров должен быть подкласс CReturnConverter. Примеров настраиваемых конвертеров возврата пока нет, потому что они ещё не получили широкого распространения. Если вы хотите написать свой собственный конвертер возврата, прочтите Tools/clinic/clinic.py, особенно реализацию CReturnConverter и всех его подклассов.

METH_O и METH_NOARGS

Чтобы преобразовать функцию с использованием METH_O, убедитесь, что единственный аргумент функции использует конвертер object, и пометьте аргументы как позиционные:

/*[clinic input]
meth_o_sample

     argument: object
     /
[clinic start generated code]*/

Чтобы преобразовать функцию с использованием METH_NOARGS, просто не указывайте никаких аргументов.

Вы по-прежнему можете использовать собственный преобразователь, преобразователь возврата и указать аргумент type для преобразователя объектов для METH_O.

Функции tp_new и tp_init

Вы можете преобразовывать функции tp_new и tp_init. Просто назовите их __new__ или __init__ соответственно. Примечания:

  • Имя функции, созданное для __new__, не оканчивается на __new__, как это было бы по умолчанию. Это просто имя класса, преобразованное в действительный идентификатор C.
  • Для этих функций генерируется номер PyMethodDef #define.
  • Функции __init__ возвращают int, а не PyObject *.
  • Используйте строку документации в качестве строки документации класса.
  • Хотя функции __new__ и __init__ всегда должны принимать объекты args и kwargs, при преобразовании вы можете указать любую сигнатуру для этих функций, которая вам нравится. (Если ваша функция не поддерживает ключевые аргументы, сгенерированная функция парсера вызовет исключение, если она получит что-либо другое.)

Изменение и перенаправление вывода Клиники

Может быть неудобно, если выходные данные Клиники перемежаются с обычным редактируемым вручную кодом C. К счастью, Клиника настраивается: вы можете буферизовать его вывод для печати позже (или раньше!) или записать его вывод в отдельный файл. Вы также можете добавить префикс или суффикс к каждой строке сгенерированного вывода Клиники.

Хотя изменение вывода Клиники таким образом может быть благом для читабельности, это может привести к тому, что код Клиники будет использовать типы до того, как они будут определены, или ваш код попытается использовать код, сгенерированный Клиникой, до того, как он будет определён. Эти проблемы можно легко решить, переставив объявления в файле или переместив сгенерированный код Клиники. (Вот почему по умолчанию Клиника выводит всё в текущий блок; хотя многие считают, что это затрудняет читаемость, никогда не потребуется перекомпоновывать код, чтобы исправить проблемы, связанные с определением перед использованием.)

Начнем с определения терминологии:

field

Поле в данном контексте является подразделом вывода Клиники. Например, #define для структуры PyMethodDef — это поле с именем methoddef_define. В Клинике есть семь различных полей, которые можно выводить для каждого определения функции:

docstring_prototype
docstring_definition
methoddef_define
impl_prototype
parser_prototype
parser_definition
impl_definition

Все имена имеют форму "<a>_<b>", где "<a>" — представленный семантический объект (функция синтаксического анализа, функция impl, строка документации или структура methoddef), а "<b>" представляет собой тип оператора. Имена полей, заканчивающиеся на "_prototype", представляют собой предварительные объявления этого объекта без фактического тела/данных объекта; имена полей, оканчивающиеся на "_definition", представляют фактическое определение объекта с телом/данными объекта. ("methoddef" является особенным, это единственный, который заканчивается на "_define", что означает, что это препроцессор #define.)

destination

Пункт назначения — это место, куда Клиника может записывать выходные данные. Есть пять встроенных пунктов назначения :

block
Назначение по умолчанию: печатается в разделе вывода текущего блока Клиники.
buffer
Текстовый буфер, в котором вы можете сохранить текст на будущее. Отправленный здесь текст добавляется в конец любого существующего текста. Когда Клиника завершает обработку файла, в буфере остается какой-либо текст. Это ошибка.
file

Отдельный «файл Клиники», который будет автоматически создан Клиникой. Имя файла, выбранное для файла, {basename}.clinic{extension}, где basename и extension были назначены выходным данным из os.path.splitext(), запущенного в текущем файле. (Пример: место назначения file для _pickle.c будет записано в _pickle.clinic.c.)

Важно: когда используется file пункт назначения, вы должны проверить в сгенерированный файл!

two-pass
Буфер типа buffer. Однако двухпроходный буфер можно сбросить только один раз, и он распечатывает весь текст, отправленный в него во время всей обработки, даже из блоков Клиники после точки сброса.
suppress
Текст подавляется — выбрасывается.

Клиника определяет пять новых директив, которые позволяют вам перенастроить его вывод.

Первая новая директива — dump :

dump <destination>

Она выгружает текущее содержимое указанного адресата в вывод текущего блока и очищает его. Это работает только с адресами buffer и two-pass.

Вторая новая директива — output. Самая основная форма output выглядит так:

output <field> <destination>

output также поддерживает специальный метаадрес, называемый everything, который сообщает Клинике о необходимости вывода всех полей в это место назначения.

output имеет ряд других функций

output push
output pop
output preset <preset>

output push и output pop позволяют отправлять и извлекать конфигурации во внутреннем стеке конфигурации, чтобы вы могли временно изменить конфигурацию вывода, а затем легко восстановить предыдущую конфигурацию. Просто нажмите перед изменением, чтобы сохранить текущую конфигурацию, затем нажмите, когда вы хотите восстановить предыдущую конфигурацию.

output preset устанавливает для вывода Клиникой одну из нескольких встроенных предустановленных конфигураций, а именно:

block

Оригинальная стартовая конфигурация Клиники. Пишет все сразу после блока ввода.

Подавить parser_prototype и docstring_prototype, все остальное записать в block.

file

Предназначен для записи в «файл Клиники» всего, что может. Затем вы #include этот файл в верхней части файла. Возможно, вам придется изменить порядок файла, чтобы эта работа работала, хотя обычно это означает просто создание предварительных объявлений для различных определений typedef и PyTypeObject.

Подавить parser_prototype и docstring_prototype, записать impl_definition в block, а всё остальное записать в file.

Имя файла по умолчанию "{dirname}/clinic/{basename}.h".

buffer

Сохранить большую часть вывода из Клиники, чтобы записать в файл ближе к концу. Для файлов Python, реализующих модули или встроенные типы, рекомендуется выгружать буфер непосредственно над статическими структурами для вашего модуля или встроенного типа; обычно это очень близко к концу. Использование buffer может потребовать даже большего редактирования, чем file, если ваш файл имеет статические массивы PyMethodDef, определенные в середине файла.

Подавить parser_prototype, impl_prototype, и docstring_prototype, записать impl_definition в block, а всё остальное записать в file.

two-pass

Подобен предустановке buffer, но записывает предварительные объявления в буфер two-pass, а определения в buffer. Это похоже на предустановку buffer, но может потребовать меньшего редактирования, чем buffer. Сброcbnm буфер two-pass в верхней части файла и сбросbnm buffer ближе к концу, как если бы вы использовали пресет buffer.

Подавить impl_prototype, записать impl_definition в block, write docstring_prototype, methoddef_define, и parser_prototype to 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
Записывает текст в текущий блок. Это то, чем изначально занималась Клиника.
buffer
Простой текстовый буфер, подобный встроенному назначению «buffer» выше.
file

Текстовый файл. Место назначения файла принимает дополнительный аргумент, шаблон для создания имени файла, например:

destination <name> new <type> <file_template>

Шаблон может использовать внутри три строки, которые будут заменены битами имени файла :

{path}
Полный путь к файлу, включая каталог и полное имя файла.
{dirname}
Имя каталога, в котором находится файл.
{basename}
Просто имя файла, не включая каталог.
{basename_root}
Имя базы с обрезанным расширением (все до последнего „.“, но не включая его).
{basename_extension}
Последний „.“ и все после него. Если базовое имя не содержит точки, это будет пустая строка.

Если в имени файла нет точек, {basename} и {filename} совпадают, а {extension} пусто. «{basename} {extension}» всегда в точности совпадает с «{filename}». «

two-pass
Двухпроходный буфер, подобный «двухпроходному» встроенному назначению выше.

Подкоманда clear работает следующим образом:

destination <name> clear

Она удаляет весь накопленный текст до этого момента в месте назначения. (Я не знаю, для чего вам это нужно, но я подумал, может быть, это будет полезно, пока кто-то экспериментирует.)

Четвертая новая директива — set :

set line_prefix "string"
set line_suffix "string"

set позволяет вам установить две внутренние переменные в Клинике. line_prefix — это строка, которая будет добавляться к каждой строке вывода Клиники; line_suffix — это строка, которая будет добавляться к каждой строке вывода Клиники.

Оба они поддерживают две строки формата:

{block comment start}
Преобразуется в строку /*, текстовую последовательность начала-комментария для файлов C.
{block comment end}
Преобразуется в строку */, текстовую последовательность конечного комментария для файлов C.

Последняя новая директива, которую вам не нужно использовать напрямую, называется preserve :

preserve

Она используется внутри Клиники при выгрузке вывода в файлы file; упаковка её в блок Клиники позволяет Клинике использовать свои существующие функции контрольной суммы, чтобы гарантировать, что файл не был изменён вручную, прежде чем он будет перезаписан.

Уловка #ifdef

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

#ifdef HAVE_FUNCTIONNAME
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */

И тогда в структуре PyMethodDef внизу существующий код будет таким :

#ifdef HAVE_FUNCTIONNAME
{'functionname', ... },
#endif /* HAVE_FUNCTIONNAME */

В этом сценарии вы должны заключить тело вашей функции impl внутри #ifdef, вот так:

#ifdef HAVE_FUNCTIONNAME
/*[clinic input]
module.functionname
...
[clinic start generated code]*/
static module_functionname(...)
{
...
}
#endif /* HAVE_FUNCTIONNAME */

Затем удалит три строки из структуры PyMethodDef, заменив их макросом Клиники Споров, созданным :

MODULE_FUNCTIONNAME_METHODDEF

(Настоящее имя этого макроса можно найти внутри сгенерированного кода. Или вы можете рассчитать его самостоятельно: это имя вашей функции, как определено в первой строке вашего блока, но с точками, замененными на подчеркивания, в верхнем регистре и добавлением "_METHODDEF" в конец.)

Возможно, вам интересно: а что, если HAVE_FUNCTIONNAME не определен? Макрос MODULE_FUNCTIONNAME_METHODDEF тоже не будет определён!

Он фактически обнаруживает, что блок Клиники Споров может быть деактивирован #ifdef. Когда это происходит, он генерирует небольшой дополнительный код, который выглядит следующим образом:

#ifndef MODULE_FUNCTIONNAME_METHODDEF
    #define MODULE_FUNCTIONNAME_METHODDEF
#endif /* !defined(MODULE_FUNCTIONNAME_METHODDEF) */

Это означает, что макрос работает всегда. Если функция определена, он превращается в правильную структуру, включая конечную запятую. Если функция не определена, превращается в ничто.

Однако это вызывает одну щекотливую проблему: куда Клиника Споров должна поместить этот дополнительный код при использовании предустановки вывода «блока»? Она не может войти в выходной блок, потому что он может быть деактивирован #ifdef. (В этом весь смысл!)

В этой ситуации Клиника Споров записывает дополнительный код в «буферное» место назначения. Это может означать, что вы получили жалобу от Клиники Споров:

Warning in file "Modules/posixmodule.c" on line 12357:
Destination buffer 'buffer' not empty at end of file, emptying.

Когда это произойдет, просто откройте свой файл, найдите блок dump buffer, который Клиника Споров добавила в ваш файл (он будет в самом низу), а затем переместите его над структурой PyMethodDef, в которой используется этот макрос.

Использование Клиники Споров в файлах Python

Фактически можно использовать Клинику Споров для предварительной обработки файлов Python. Конечно, нет смысла использовать блоки Клиники Споров, поскольку вывод не будет иметь никакого смысла для интерпретатора Python. Но использование Клиники Споров для запуска блоков Python позволяет использовать Python в качестве препроцессора Python!

Поскольку комментарии Python отличаются от комментариев C, блоки Клиники Споров, встроенные в файлы Python, выглядят немного иначе. Они выглядят так:

#/*[python input]
#print("def foo(): pass")
#[python start generated code]*/
def foo(): pass
#/*[python checksum:...]*/