Аргументационная клиника как-никак

автор

Ларри Гастингс

Аннотация

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

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

Цели клиники «Аргумент

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

Для того чтобы 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 поставляется вместе с 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).

Для большей ясности приведем терминологию, которую мы будем использовать в клинике «Аргумент»:

  • Первая строка комментария (/*[clinic input]) является начальной строкой.

  • Последняя строка начального комментария ([clinic start generated code]*/) является конечной строкой.

  • Последняя строка (/*[clinic end generated code: checksum=...]*/) - это строка контрольной суммы.

  • Между начальной и конечной линиями находится вход.

  • Между конечной строкой и строкой контрольной суммы находится выход.

  • Весь текст вместе, от начальной строки до строки контрольной суммы включительно, является блоком. (Блок, который еще не был успешно обработан Argument Clinic, не имеет выходных данных или строки контрольной суммы, но он все равно считается блоком).

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

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

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

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

  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. Вырежьте строку docstring и вставьте ее между строками [clinic], удалив весь мусор, который делает ее правильно цитируемой строкой C. После завершения работы у вас должен остаться только текст, расположенный по левому краю, без строк шириной более 80 символов. (Аргумент Clinic сохранит отступы внутри строки документа).

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

    Образец:

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

    (В нашем примере docstring состоит только из итоговой строки, поэтому код примера не нужно менять для этого шага).

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

    Образец:

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

    Имя класса и модуля должно совпадать с тем, которое видит 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 довольно сложна; для получения дополнительной информации смотрите the section below on default values.

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

    Что такое «конвертер»? Он определяет тип переменной, используемой в языке 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 может ослабить это ограничение).

    Образец:

    /*[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, обычно сразу после блока модуля clinic:

    #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=...]*/
    {
    ...
    

    Аргумент 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;
    
        ...
    
  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() запуск вашей функции теперь должен обеспечить действительную сигнатуру!

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

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

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

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

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

  • Числовые константы (целочисленные и плавающие)

  • Строковые константы

  • True, False и None.

  • Простые символические константы типа sys.maxsize, которые должны начинаться с имени модуля

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

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

Переименование функций и переменных языка C, создаваемых Argument Clinic

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

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

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

...

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

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

/*[clinic input]
pickle.Pickler.dump

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

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

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

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

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

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

Факультативные группы

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

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

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

Примечание

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

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

/*[clinic input]

curses.window.addch

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

    ch: object
      Character to add.

    [
    attr: long
      Attributes for the character.
    ]
    /

...

Примечания:

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

  • Если нет обязательных аргументов, необязательные группы будут вести себя так, как будто они находятся справа от обязательных аргументов.

  • В случае неоднозначности код разбора аргумента отдает предпочтение параметрам слева (перед требуемыми параметрами).

  • Необязательные группы могут содержать только позиционные параметры.

  • Необязательные группы только предназначены для устаревшего кода. Пожалуйста, не используйте дополнительные группы для нового кода.

Использование настоящих конвертеров Argument Clinic вместо «устаревших конвертеров»

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

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

  • Правильные преобразователи гораздо легче читаются и более понятны по своему смыслу.

  • Существуют некоторые форматные блоки, которые не поддерживаются как «устаревшие конвертеры», поскольку они требуют аргументов, а синтаксис устаревших конвертеров не поддерживает указание аргументов.

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

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

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

Все аргументы для конвертеров Argument Clinic не содержат ключевых слов. Все конвертеры Argument Clinic принимают следующие аргументы:

c_default

Значение по умолчанию для этого параметра при определении на языке C. В частности, это будет инициализатор для переменной, объявленной в «функции разбора». Как использовать этот параметр, смотрите в the section on default values. Задается как строка.

annotation

Значение аннотации для этого параметра. В настоящее время не поддерживается, поскольку PEP 8 предписывает, что библиотека Python не может использовать аннотации.

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

accept

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

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

bitwise

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

converter

Поддерживается только конвертером object. Указывает имя конвертера C «converter function», который следует использовать для преобразования этого объекта в собственный тип.

encoding

Поддерживается только для строк. Указывает кодировку, которую следует использовать при преобразовании данной строки из значения Python str (Unicode) в значение C char *.

subclass_of

Поддерживается только для конвертера object. Требуется, чтобы значение Python было подклассом типа Python, выраженного на языке C.

type

Поддерживается только для конвертеров object и self. Указывает тип C, который будет использоваться для объявления переменной. Значение по умолчанию "PyObject *".

zeroes

Поддерживается только для строк. Если true, внутри значения допускается вставка байтов NUL ('\\0'). Длина строки будет передана в функцию impl сразу после параметра string как параметр с именем <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.

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

Значения по умолчанию для параметров могут быть любыми из ряда значений. В самом простом случае это могут быть литералы строк, 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 может иметь разные значения на разных платформах. Поэтому 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 *.

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

(Если вы используете и "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. Когда вы клонируете существующую функцию, вы используете ее повторно:

  • его параметры, включая

    • их имена,

    • их преобразователей, со всеми параметрами,

    • их значения по умолчанию,

    • их документацию по каждому параметру,

    • их вид (являются ли они только позиционными, позиционными или ключевыми, или только ключевыми), и

  • его обратный преобразователь.

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

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

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

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

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

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

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

Вызов кода Python

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

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

/*[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:...]*/

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

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

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

Как указать пользовательский тип, к которому вы хотите привести self? Если у вас есть только одна или две функции с одинаковым типом для self, вы можете напрямую использовать существующий в Argument Clinic конвертер 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]*/

Использование конвертера «определяющего класс»

Argument Clinic облегчает получение доступа к определяющему классу метода. Это полезно для методов heap type, которым необходимо получить состояние на уровне модуля. Используйте PyType_FromModuleAndSpec(), чтобы связать новый тип кучи с модулем. Теперь вы можете использовать PyType_GetModuleState() на определяющем классе для получения состояния модуля, например, из метода модуля.

Пример с Modules/zlibmodule.c. Сначала defining_class добавляется на вход клиники:

/*[clinic input]
zlib.Compress.compress

  cls: defining_class
  data: Py_buffer
    Binary data to be compressed.
  /

После запуска инструмента Argument Clinic генерируется следующая сигнатура функции:

/*[clinic start generated code]*/
static PyObject *
zlib_Compress_compress_impl(compobject *self, PyTypeObject *cls,
                            Py_buffer *data)
/*[clinic end generated code: output=6731b3f0ff357ca6 input=04d00f65ab01d260]*/

Следующий код теперь может использовать PyType_GetModuleState(cls) для получения состояния модуля:

zlibstate *state = PyType_GetModuleState(cls);

Каждый метод может иметь только один аргумент, использующий этот конвертер, и он должен появляться после self, или, если self не используется, в качестве первого аргумента. Аргумент будет иметь тип PyTypeObject *. Аргумент не будет отображаться в __text_signature__.

Конвертер defining_class не совместим с методами __init__ и __new__, которые не могут использовать соглашение METH_METHOD.

Невозможно использовать defining_class со слотовыми методами. Чтобы получить состояние модуля из таких методов, используйте _PyType_GetModuleByDef для поиска модуля, а затем PyModule_GetState() для получения состояния модуля. Пример из слотового метода setattro в Modules/_threadmodule.c:

static int
local_setattro(localobject *self, PyObject *name, PyObject *v)
{
    PyObject *module = _PyType_GetModuleByDef(Py_TYPE(self), &thread_module);
    thread_module_state *state = get_thread_state(module);
    ...
}

См. также PEP 573.

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

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

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

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

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

type

Тип C, который будет использоваться для этой переменной. type должна быть строкой Python, указывающей тип, например int. Если это тип указателя, то строка типа должна заканчиваться на ' *'.

default

Значение по умолчанию для этого параметра в формате Python. Или магическое значение unspecified, если нет значения по умолчанию.

py_default

default так, как он должен отображаться в коде Python, в виде строки. Или None, если нет значения по умолчанию.

c_default

default как это должно быть в коде на языке Си, в виде строки. Или None, если нет значения по умолчанию.

c_ignored_default

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

converter

Имя функции конвертера C в виде строки.

impl_by_reference

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

parse_by_reference

Булево значение. Если значение истинно, Argument Clinic будет добавлять & перед именем переменной при передаче ее в PyArg_ParseTuple().

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

/*[python input]

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

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

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

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

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

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

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

METH_O и METH_NOARGS

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

/*[clinic input]
meth_o_sample

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

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

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

Функции tp_new и tp_init

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

  • Имя функции, генерируемое для __new__, не заканчивается на __new__, как это было бы по умолчанию. Это просто имя класса, преобразованное в допустимый идентификатор языка Си.

  • Для этих функций не генерируется PyMethodDef #define.

  • Функции __init__ возвращают int, а не PyObject *.

  • Используйте строку docstring в качестве строки docstring класса.

  • Хотя функции __new__ и __init__ всегда должны принимать оба объекта args и kwargs, при преобразовании вы можете указать любую сигнатуру для этих функций, которая вам нравится. (Если ваша функция не поддерживает ключевые слова, то созданная функция синтаксического анализа выбросит исключение, если примет хоть одно).

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

Может быть неудобно, когда вывод Clinic перемежается с вашим обычным отредактированным вручную кодом на Си. К счастью, 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, docstring или структура methoddef), а "<b>" представляет собой тип утверждения поля. Имена полей, заканчивающиеся на "_prototype", представляют собой прямое объявление этой вещи, без фактического тела/данных этой вещи; имена полей, заканчивающиеся на "_definition", представляют фактическое определение вещи, с телом/данными этой вещи. ("methoddef" - особенное, только оно заканчивается "_define", что означает, что это препроцессорное #define).

пункт назначения

Пункт назначения - это место, куда Clinic может записать вывод. Существует пять встроенных мест назначения:

block

Назначение по умолчанию: печать в разделе вывода текущего блока клиники.

buffer

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

file

Отдельный «файл клиники», который будет создан автоматически программой 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

Исходная начальная конфигурация клиники. Записывает все сразу после блока ввода.

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

file

Предназначен для записи в «файл клиники» всего, что он может. Затем вы #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

Простой текстовый буфер, подобный встроенному назначению «buffer», описанному выше.

file

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

пункт назначения <имя> новый <тип> <шаблон файла>

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

{путь}

Полный путь к файлу, включая каталог и полное имя файла.

{dirname}

Имя каталога, в котором находится файл.

{basename}

Только имя файла, не включая каталог.

{basename_root}

Базовое имя с отрезанным расширением (все до последнего „.“, но не включая его).

{basename_extension}

Последнее „.“ и все, что после него. Если основное имя не содержит точки, это будет пустая строка.

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

two-pass

Двухпроходной буфер, подобно встроенному назначению «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, можно использовать имеющуюся функциональность контрольной суммы, чтобы убедиться, что файл не был изменен вручную, прежде чем он будет перезаписан.

Трюк с #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. (В этом весь смысл!)

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

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

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

Использование клиники аргументов в файлах 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:...]*/
Вернуться на верх