Более пристальный взгляд на то, как работают f-строки Python


ФотоFancycrave

ПКП 498 представил новый механизм форматирования строк, известный как Литеральная интерполяция строк или чаще как F-строки (из-за ведущего символа f, предшествующего строковому литералу). F-строки обеспечивают краткий и удобный способ встраивания выражений Python в строковые литералы для форматирования:

Мы можем выполнять функции внутри f-строк:

Фа-струны быстрые! Гораздо быстрее, чем %-форматирование и **str.format() ** — два наиболее часто используемых механизма форматирования строк:

Почему фа-струны такие быстрые и как они на самом деле работают?ПКП 498 дает подсказку:

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

Ключевым моментом здесь является то, что f-строка на самом деле является выражением, оцениваемым во время выполнения, а не постоянным значением. . По сути, это означает, что выражения внутри f-строк оцениваются точно так же, как и любые другие выражения Python в той области, в которой они появляются. Компилятор CPython выполняет тяжелую работу на этапе синтаксического анализа, чтобы разделить f-строку на строковые литералы и выражения для генерации соответствующий Абстрактное синтаксическое дерево (АСТ):

Мы используем аст модуль для просмотра абстрактного синтаксического дерева, связанного с простым выражением a + b внутри и снаружи f-строки. Мы видим, что выражение a + b в f-строке f{a + b} анализируется в простую старую двоичную операцию так же, как и вне f-строки.

Мы даже можем видеть на уровне байт-кода, что выражения f-строки оцениваются так же, как и любые другие выражения Python:

add_two функция просто суммирует локальные переменные а и б и возвращает результаты. Функция add_two_fstring делает то же самое, но сложение происходит внутри f-строки. Кроме FORMAT_VALUE инструкция в дизассемблированном байт-коде функция add_two_fstring (эта инструкция существует потому, что, в конце концов, f-строка должна преобразовать результаты вложенного выражения), инструкции байт-кода для оценки a + b внутри и снаружи f-строки одинаковы.

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

Почему ул.формат() намного медленнее, чем f-струны? Ответ станет ясен, если мы посмотрим на дизассемблированный байт-код функции, использующей str.format():

Из дизассемблированного байткода сразу выскакивают две инструкции байткода: LOAD_ATTR и CALL_FUNCTION. Когда мы используем ул.формат(), формат функция сначала должна быть просмотрена в глобальной области видимости. Это делается через LOAD_ATTR инструкция байт-кода. Поиск глобальной переменной на самом деле не дешевая операция и включает в себя ряд шагов (взгляните на один из моих предыдущих почта о том, как работает поиск атрибутов, если вам интересно). Однажды формат находится, операция двоичного сложения (BINARY_ADD) вызывается для суммирования переменных а и б. Наконец формат функция выполняется через CALL_FUNCTION инструкция байт-кода и возвращаются строковые результаты. Вызов функции в python стоит недешево и имеет значительные накладные расходы. Когда используешь ул.формат(), дополнительное время, проведенное в LOAD_ATTR и CALL_FUNCTION это то, что способствует тому, что str.format() намного медленнее, чем f-строки.

Как насчет %-нить форматирование? Мы видели, что это быстрее, чем str.format(), но все же медленнее, чем f-строки. Опять же, давайте посмотрим на дизассемблированный байт-код для функции, использующей форматирование %-строки для подсказок:

Сразу же мы не видим LOAD_ATTR и CALL_FUNCTION инструкции байт-кода — поэтому форматирование %-строки позволяет избежать накладных расходов на поиск глобальных атрибутов и вызов функций Python. Это объясняет, почему он быстрее, чем str.format(). Но почему форматирование %-строки все еще медленнее, чем f-строки? Одно потенциальное место, где форматирование %-строки может занять дополнительное время, находится в BINARY_MODULO инструкция байт-кода . Я не делал тщательного профилирования BINARY_MODULO bytecode, но глядя на исходный код CPython, мы можем понять, почему при вызове ДВОЙНОЙ_МОДУЛЬ:

Из приведенного выше фрагмента исходного кода Python C мы видим, что BINARY_MODULO операция перегружена. Каждый раз, когда он вызывается, он должен проверять тип своих операндов (строки 7-13 в приведенном выше фрагменте кода), чтобы определить, являются ли операнды строковыми объектами или нет. Если да, то оператор по модулю выполняет операции форматирования строки. В противном случае он вычисляет обычный модуль (возвращает остаток от деления первого аргумента на второй). Несмотря на небольшой размер, эта проверка типов сопряжена с накладными расходами, которых можно избежать с помощью f-строки.

Надеюсь, этот пост помог пролить свет на то, почему f-строки выделяются из толпы, когда дело доходит до форматирования строк. F-строки быстрые, простые в использовании, практичные и приводят к гораздо более чистому коду. Используй их!

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *