Spec-Zone.ru › Python 3.10

Руководство по использованию Argument Clinic

author

Larry Hastings

Аннотация

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

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

Цели Argument Clinic

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

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

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

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

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

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

Основные концепции и использование

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

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

Argument Clinic будет сканировать файл в поисках строк, которые выглядят точно так же:

/*[clinic input]

Найдя одну, он прочитает все до строки, которая выглядит точно так же:

[clinic start generated code]*/

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

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

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

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

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

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

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

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

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

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

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

    O&
    O!
    es
    es#
    et
    et#
    

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

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

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

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

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

    Пример:

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

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

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

    Пример:

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

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

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

    Пример:

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

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

    name_of_parameter: converter
    

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

    name_of_parameter: converter = default_value
    

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

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

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

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

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

    Пример:

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

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

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

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

    Чтобы пометить все параметры как позиционные только в Argument Clinic, добавьте / в отдельной строке после последнего параметра, с тем же отступом, что и строки параметров.

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

    Пример:

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

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

    Пример:

    /*[clinic input]
    module _pickle
    class _pickle.Pickler "PicklerObject *" "&Pickler_Type"
    [clinic start generated code]*/
    
    /*[clinic input]
    _pickle.Pickler.dump
    
        obj: 'O'
            The object to be pickled.
        /
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
  13. Сохраните и закройте файл, затем запустите Tools/clinic/clinic.py на нём. Если всё получилось, ваш блок теперь имеет вывод, и был сгенерирован файл .c.h! Откройте файл в вашем текстовом редакторе, чтобы увидеть:

    /*[clinic input]
    _pickle.Pickler.dump
    
        obj: 'O'
            The object to be pickled.
        /
    
    Write a pickled representation of obj to the open file.
    [clinic start generated code]*/
    
    static PyObject *
    _pickle_Pickler_dump(PicklerObject *self, PyObject *obj)
    /*[clinic end generated code: output=87ecad1261e02ac7 input=552eb1c0f52260d9]*/
    

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

    Для повышения читабельности большая часть «склейки» кода сгенерирована в файл .c.h. Вам нужно будет включить его в ваш исходный файл .c, обычно сразу после блока модуля 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=...]*/
    {
    ...
    

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

    Пример:

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

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

    Пример:

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

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

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

Дополнительные темы

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

Символьные значения по умолчанию

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

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

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

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

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

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

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

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

...

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

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

/*[clinic input]
pickle.Pickler.dump

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

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

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

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

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

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

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

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

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

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

Примечание

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

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

/*[clinic input]

curses.window.addch

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

    ch: object
      Character to add.

    [
    attr: long
      Attributes for the character.
    ]
    /

...

Примечания:

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

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

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

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

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

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

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

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

c_default

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

annotation

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

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

accept

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

Для принятия None, добавьте NoneType в этот набор.

bitwise

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

converter

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

encoding

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

subclass_of

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

type

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

zeroes

Поддерживается только для строк. Если true, встраиваемые байты NUL ('\\0') разрешены внутри значения. Длина строки будет передана в функцию impl, сразу после параметра строки, как параметр, названный <parameter_name>_length.

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

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

'B'

unsigned_char(bitwise=True)

'b'

unsigned_char

'c'

char

'C'

int(accept={str})

'd'

double

'D'

Py_complex

'es'

str(encoding='name_of_encoding')

'es#'

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

'et'

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

'et#'

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

'f'

float

'h'

short

'H'

unsigned_short(bitwise=True)

'i'

int

'I'

unsigned_int(bitwise=True)

'k'

unsigned_long(bitwise=True)

'K'

unsigned_long_long(bitwise=True)

'l'

long

'L'

long long

'n'

Py_ssize_t

'O'

object

'O!'

object(subclass_of='&PySomething_Type')

'O&'

object(converter='name_of_c_function')

'p'

bool

'S'

PyBytesObject

's'

str

's#'

str(zeroes=True)

's*'

Py_buffer(accept={buffer, str})

'U'

unicode

'u'

Py_UNICODE

'u#'

Py_UNICODE(zeroes=True)

'w*'

Py_buffer(accept={rwbuffer})

'Y'

PyByteArrayObject

'y'

str(accept={bytes})

'y#'

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

'y*'

Py_buffer

'Z'

Py_UNICODE(accept={str, NoneType})

'Z#'

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

'z'

str(accept={str, NoneType})

'z#'

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

'z*'

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

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

/*[clinic input]
pickle.Pickler.dump

    obj: object
        The object to be pickled.
    /

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

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

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

Py_buffer

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

Расширенные преобразователи

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

foo: Py_ssize_t = sys.maxsize - 1

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

В каком пространстве имен доступно выражение при его оценке? Оно оценивается в контексте модуля, из которого произошла функция встроенной функции. Таким образом, если ваш модуль имеет атрибут под названием «max_widgets», вы можете просто использовать его:

foo: Py_ssize_t = max_widgets

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

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

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

Другая сложность: Argument Clinic не может заранее узнать, является ли выражение, которое вы предоставили, допустимым. Она анализирует его, чтобы убедиться, что оно выглядит законным, но не может действительно знать. Вы должны быть очень осторожны при использовании выражений для указания значений, которые гарантированно будут допустимыми во время выполнения!

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

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

Использование преобразователя возвращаемого значения

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

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

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

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

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

В настоящее время Argument Clinic поддерживает только несколько преобразователей возвращаемого значения:

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

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

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

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

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

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

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

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

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

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

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

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

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

Извините, нет синтаксиса для частичного клонирования функции или клонирования функции, а затем ее изменения. Клонирование — это все или ничего.

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

Вызов кода Python

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

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

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

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

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

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

Использование «преобразователя self»

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

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

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

/*[clinic input]

_pickle.Pickler.dump

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

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

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

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

/*[clinic input]

_pickle.Pickler.dump

  self: PicklerObject
  obj: object
  /

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

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

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

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

/*[clinic input]
zlib.Compress.compress

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

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

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

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

zlibstate *state = PyType_GetModuleState(cls);

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

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

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

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

См. также PEP 573.

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

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

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

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

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

type

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

default

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

py_default

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

c_default

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

c_ignored_default

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

converter

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

impl_by_reference

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

parse_by_reference

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

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

/*[python input]

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

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

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

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

Создание пользовательского конвертера возвращаемого значения

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

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

METH_O и METH_NOARGS

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

/*[clinic input]
meth_o_sample

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

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

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

Функции tp_new и tp_init

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

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

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

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

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

Начнем с определения некоторых терминов:

поле

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

docstring_prototype
docstring_definition
methoddef_define
impl_prototype
parser_prototype
parser_definition
impl_definition

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

назначение

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

block

По умолчанию: выводится в секции вывода текущего блока Clinic.

buffer

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

file

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

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

two-pass

Буфер, подобный buffer. Однако буфер с двумя проходами может быть выгружен только один раз и печатает весь текст, отправленный ему во время всей обработки, даже из блоков Clinic после точки выгрузки.

suppress

Текст подавляется — отбрасывается.

Clinic определяет пять новых директив, которые позволяют переконфигурировать его вывод.

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

dump <destination>

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

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

output <field> <destination>

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

У output есть ряд других функций:

output push
output pop
output preset <preset>

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

output preset устанавливает вывод Clinic в одну из нескольких встроенных предопределенных конфигураций следующим образом:

block

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

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

file

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

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

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

buffer

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

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

two-pass

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

Подавляет impl_prototype, записывает impl_definition в block, записывает docstring_prototype, methoddef_define, и parser_prototype в two-pass, и записывает все остальное в buffer.

partial-buffer

Аналогично предопределению buffer, но записывает больше вещей в block, записывая только очень большие куски сгенерированного кода в buffer. Это полностью устраняет проблему определения перед использованием в buffer, с небольшой ценой незначительно большего содержимого вывода блока. Выгрузите buffer в конце, как и при использовании предопределения buffer.

Подавляет impl_prototype, записывает docstring_definition и parser_definition в buffer, записывает все остальное в block.

Третья новая директива — destination:

destination <name> <command> [...]

Это выполняет операцию над назначением, названным name.

Есть две определенные подкоманды: new и clear.

Подкоманда new работает так:

destination <name> new <type>

Это создает новое назначение с именем <name> и типом <type>.

Существует пять типов назначений:

suppress

Отбрасывает текст.

block

Записывает текст в текущий блок. Это то, что Clinic изначально делал.

buffer

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

file

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

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

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

{path}

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

{dirname}

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

{basename}

Просто имя файла без директории.

{basename_root}

Имя файла без расширения (все до последней точки, но не включая её).

{basename_extension}

Последняя точка и все после неё. Если в имени файла нет точки, это будет пустая строка.

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

two-pass

Буфер с двумя проходами, как встроенное назначение «двухпроходный» выше.

Подкоманда clear работает так:

destination <name> clear

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

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

set line_prefix "string"
set line_suffix "string"

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

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

{block comment start}

Превращается в строку /*, последовательность текста начального комментария для файлов C.

{block comment end}

Превращается в строку */, последовательность текста конечного комментария для файлов C.

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

preserve

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

Уловка #ifdef

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

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

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

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

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

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

Затем удалите эти три строки из структуры PyMethodDef, заменив их на макрос, сгенерированный Argument Clinic:

MODULE_FUNCTIONNAME_METHODDEF

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

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

Вот где Argument Clinic становится очень умным. Он на самом деле обнаруживает, что блок Argument Clinic может быть деактивирован #ifdef. Когда это происходит, он генерирует немного дополнительного кода, который выглядит так:

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

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

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

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

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

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

Использование Argument Clinic в файлах Python

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

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

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

© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/howto/clinic.html

Spec-Zone.ru

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