Фото Автора Fancycrave
PEP 498 представил новый механизм форматирования строк, известный как Интерполяция буквенных строк или чаще как F-строки (из-за ведущего символа f, предшествующего строковому литералу) . F-строки предоставляют краткий и удобный способ встраивания выражений python в строковые литералы для форматирования:
Мы можем выполнять функции внутри f-строк:
F-струны быстры! Намного быстрее, чем %-форматирование и **str.format() **— два наиболее часто используемых механизма форматирования строк:
Почему f-строки так быстры и как они на самом деле работают? PEP 498 дает ключ к разгадке:
F-строки предоставляют способ встраивания выражений в строковые литералы с использованием минимального синтаксиса. Следует отметить, что f-строка на самом деле является выражением, вычисляемым во время выполнения, а не постоянным значением. В исходном коде Python строка f-это литеральная строка с префиксом “f”, которая содержит выражения внутри фигурных скобок. Выражения заменяются их значениями.
Ключевым моментом здесь является то, что f-строка на самом деле является выражением, вычисляемым во время выполнения, а не постоянным значением . По сути, это означает, что выражения внутри f-строк вычисляются так же, как и любые другие выражения python в пределах области, в которой они появляются. Компилятор CPython выполняет тяжелую работу на этапе синтаксического анализа, чтобы разделить f-строку на строковые литералы и выражения для создания соответствующего Абстрактного синтаксического дерева (AST):
Мы используем модуль ast для просмотра абстрактного синтаксического дерева, связанного с простым выражением a + b
внутри и вне строки f. Мы можем видеть, что выражение a + b
внутри строки f f{a + b}
анализируется в простую старую двоичную операцию так же, как и вне строки f.
Мы даже можем видеть на уровне байт-кода, что выражения f-string вычисляются так же, как и любые другие выражения python:
Функция add_two просто суммирует локальные переменные a и b и возвращает результаты. Функция add_two_string делает то же самое, но добавление происходит в f-строке. Помимо инструкции FORMAT_VALUE в разобранном байт-коде функции add_two_fstring (эта инструкция существует, потому что, в конце концов, f-строка должна строчить результаты заключенного выражения), инструкции байт-кода для вычисления a + b
внутри и вне f-строки одинаковы.
Обработка f-строк просто разбивается на вычисление выражения (как и любого другого выражения python), заключенного в фигурные скобки, а затем комбинирует его со строковой литеральной частью f-строки, чтобы вернуть значение конечной строки. Дополнительная обработка во время выполнения не требуется . Это делает f-строки довольно быстрыми и эффективными.
Почему str.format() намного медленнее, чем f-строки? Ответ становится ясным, как только мы посмотрим на разобранный байт-код для функции, использующей формат str.():
Из разобранного байт-кода сразу же выскакивают инструкции по байт-коду: LOAD_ATTR и CALL_FUNCTION . Когда мы используем str.format() , сначала необходимо найти функцию format в глобальной области видимости. Это делается с помощью инструкции LOAD_ATTR байт-кода. Поиск глобальных переменных на самом деле не является дешевой операцией и включает в себя ряд шагов(взгляните на один из моих предыдущих постов о том, как работает поиск атрибутов, если вам интересно). Как только функция format найдена, операция двоичного добавления ( BINARY_ADD) вызывается для суммирования переменных a и b. Наконец, функция format выполняется с помощью инструкции CALL_FUNCTION байт-кода и возвращаются строковые результаты. Вызов функций в python стоит недешево и имеет значительные накладные расходы. При использовании str.format () дополнительное время, проведенное в LOAD_ATTR и CALL_FUNCTION , способствует тому, что str.format() работает намного медленнее, чем f-строки.
Как насчет %-string форматирования? Мы видели, что это быстрее, чем str.format (), но все же медленнее, чем f-строки. Опять же, давайте рассмотрим разобранный байт-код для функции, использующей % – строковое форматирование для подсказок:
Сразу же мы не видим инструкций LOAD_ATTR и CALL_FUNCTION байт — кода-поэтому форматирование % – строки позволяет избежать накладных расходов на поиск глобальных атрибутов и вызов функций python. Это объясняет, почему он работает быстрее, чем str.format(). Но почему форматирование %-строк все еще медленнее, чем f-строки? Одно потенциальное место, где %-строковое форматирование может тратить дополнительное время, находится в инструкции BINARY_MODULO байт-код . Я не провел тщательного профилирования инструкции BINARY_MODULO байт-кода, но, глядя на исходный код CPython, мы можем понять, почему при вызове BINARY_MODULO может возникнуть небольшая накладная нагрузка:
Из приведенного выше фрагмента исходного кода python C мы видим, что операция BINARY_MODULO перегружена. Каждый раз, когда он вызывается, ему необходимо проверить тип своих операндов (строка 7 -13 в приведенном выше фрагменте кода), чтобы определить, являются ли операнды строковыми объектами или нет. Если это так, то оператор по модулю выполняет операции форматирования строк. В противном случае он вычисляет обычное по модулю(возвращает остаток от деления первого аргумента от второго). Хотя эта проверка типов невелика, она сопряжена с накладными расходами, которых избегают f-строки.
Надеюсь, этот пост помог пролить некоторый свет на то, почему f-строки выделяются из толпы, когда дело доходит до форматирования строк. F-строки быстры, просты в использовании, практичны и приводят к гораздо более чистому коду. Используйте их!