06 декабря 2023

🐍📋 F-строки в Python для интерполяции и форматирования строк

Автор статей по блокчейну, криптовалюте, безопасности и общим темам
Из этой статьи вы узнаете, как использовать f-строки — инструмент для быстрой интерполяции и форматирования строк, который превосходит по эффективности и читабельности классические подходы с применением оператора % и метода .format().
🐍📋 F-строки в Python для интерполяции и форматирования строк
Данная статья является переводом. Ссылка на оригинал.

К концу этого руководства вы поймете, почему f-строки – это мощный инструмент, которым необходимо овладеть разработчику на Python.

В этом самоучителе вы узнаете, как:

  • Интерполировать значения, объекты и выражения в строки с помощью f-строк
  • Форматировать f-строки с помощью мини-языка форматирования строк Python
  • Использовать некоторые новые особенности f-строк в Python 3.12 и следующих версиях
  • Выясните, когда следует использовать классические средства интерполяции вместо f-строк

Для извлечения максимальной пользы от этого руководства, вы должны быть знакомы с типом данных строк Python. Также будет полезно иметь опыт работы с другими инструментами интерполяции строк, такими как оператор modulo (%) и метод .format().

Интерполяция и форматирование строк до Python 3.6

До появления Python 3.6 у вас было два основных инструмента для интерполяции значений, переменных и выражений внутри строковых литералов:

  1. Оператор строковой интерполяции (%) или оператор модуляции
  2. Метод str.format()

В следующих разделах мы подробно рассмотрим эти два инструмента строковой интерполяции. Вы также узнаете о возможностях форматирования строк Python, которые эти инструменты предоставляют.

Оператор модуляции (%)

Оператор modulo (%) был первым инструментом интерполяции и форматирования строк в Python и использовался в языке с самого начала. Вот как выглядит использование этого оператора на практике:

        >>> name = "Jane"

>>> "Hello, %s!" % name
'Hello, Jane!'

    

В этом примере оператор % используется для интерполяции значения переменной name в строковый литерал. Оператор интерполяции принимает два операнда:

  1. Строковый литерал, содержащий один или несколько спецификаторов преобразования
  2. Объект или объекты, которые интерполируются в строковый литерал

Спецификаторы преобразования работают как маркеры замены. В приведенном выше примере в качестве спецификатора преобразования используется комбинация %-символов. Символ % обозначает начало спецификатора, в то время как буква s является типом преобразования и указывает оператору на необходимость преобразовать вводимый объект в строку.

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

        >>> name = "Jane"
>>> age = 25

>>> "Hello, %s! You're %s years old." % (name, age)
'Hello, Jane! You're 25 years old.'

    

В этом примере в качестве правого операнда % используется кортеж со значениями. Обратите внимание, что вы использовали строку и целое число. Так как используется %-спецификатор, Python преобразует оба объекта в строки.

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

        >>> "Hello, %(name)s! You're %(age)s years old." % {"name": "Jane", "age": 25}
"Hello, Jane! You're 25 years old."

    

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

При использовании оператора % для интерполяции строк вы можете применять спецификаторы преобразования. Они предоставляют некоторые возможности форматирования строк, используя типы преобразования, флаги преобразования и некоторые символы, такие как точка . и звездочка *. Рассмотрим следующий пример:

        >>> "Balance: $%.2f" % 5425.9292
'Balance: $5425.93'

>>> print("Name: %s\nAge: %5s" % ("John", 35))
Name: John
Age:    35

    

В первом примере используется спецификатор преобразования %.2f для выражения валютных значений. Буква f указывает оператору на преобразование в число с плавающей точкой. Часть .2 определяет точность, используемая при преобразовании. Во втором примере используется %5s, чтобы выровнять показатель возраста на пять позиций вправо.

💡Примечание
Форматирование с помощью оператора modulo вдохновлено форматированием printf(), который используется в C и многих других языках программирования.

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

        >>> "The personal info is: %s" % ("John", 35)
Traceback (most recent call last):
    ...
TypeError: not all arguments converted during string formatting

    

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

        >>> "The personal info is: %s" % (("John", 35),)
"The personal info is: ('John', 35)"

    

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

Еще одной проблемой %-оператора являются его ограниченные возможности форматирования и отсутствие поддержки мини-языка форматирования строк Python, который считается мощным инструментом для форматирования строк.

🐍 Библиотека питониста
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека питониста»
🐍🎓 Библиотека собеса по Python
Подтянуть свои знания по Python вы можете на нашем телеграм-канале «Библиотека собеса по Python»
🧩🐍 Библиотека задач по Python
Интересные задачи по Python для практики можно найти на нашем телеграм-канале «Библиотека задач по Python»

Метод str.format()

Метод str.format() более эффективен по сравнению с %-оператором, так как он исправляет ряд проблем и поддерживает мини-язык форматирования строк. При использовании .format() фигурные скобки разграничивают маркеры замены:

        >>> name = "Jane"
>>> age = 25

>>> "Hello, {}! You're {} years old.".format(name, age)
"Hello, Jane! You're 25 years old."

    

Для работы метода .format() необходимо указать маркеры замены, используя фигурные скобки. Если вы используете пустые скобки, то метод интерполирует свои аргументы в целевую строку исходя из позиции.

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

        >>> "Hello, {1}! You're {0} years old.".format(age, name)
"Hello, Jane! You're 25 years old."

    

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

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

        >>> "Hello, {name}! You're {age} years old.".format(name="Jane", age=25)
"Hello, Jane! You're 25 years old."

    

В этом примере продемонстрировано как .format() интерполирует аргументы ключевых слов по их именам в целевую строку. Эта конструкция заметно улучшает читаемость кода по сравнению с предыдущим примером и примерами с использованием оператора %.

И наконец, метод .format() позволяет вам использовать словари для предоставления значений, которые необходимо интерполировать в строку:

        >>> person = {"name": "Jane", "age": 25}

>>> "Hello, {name}! You're {age} years old.".format(**person)
"Hello, Jane! You're 25 years old."

    

В этом примере мы используем словарь, который содержит данные для интерполяции. Затем с помощью оператора распаковки словаря **, мы задаем аргументы для метода .format().

Метод .format() поддерживает спецификаторы формата. Это строки, которые вы вставляете в маркеры замены для форматирования интерполируемых значений. Рассмотрим следующие примеры:

        >>> "Balance: ${:.2f}".format(5425.9292)
'Balance: $5425.93'

>>> "{:=^30}".format("Centered string")
'=======Centered string========'

    

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

Во втором примере используется спецификатор формата :=^30. В этом случае вы говорите .format() отформатировать входное значение, используя символ = в качестве заполняющего символа. Символ ^ центрирует входное значение, вставляя символы = с обеих сторон для достижения тридцати знаков.

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

В следующих разделах написано еще о нескольких примерах форматирования строк при использовании мини-языка с f-строками.

Интерполяция строк с помощью F-строк в Python

F-строки появились в Python 3.6 в PEP 498. Также называемые форматированными строковыми литералами, f-строки – это строковые литералы, в которых перед открывающейся кавычкой стоит символ f. Они могут включать в себя выражения Python, заключенные в фигурные скобки. Python заменит эти выражения на итоговые значения. Из этого следует, что f-строки превращаются в инструмент интерполяции строк.

В следующих разделах вы узнаете о f-строках и используете их при интерполяции значений, объектов и выражений в строковых литералах.

Интерполяция значений и объектов в F-строках

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

        >>> name = "Jane"
>>> age = 25

>>> f"Hello, {name}! You're {age} years old."
'Hello, Jane! You're 25 years old.'

    

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

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

Вставка выражений в F-строки

Практически любое выражение Python можно вставить в f-строку. Это позволяет делать некоторые интересные вещи. Можно сделать что-то довольно простое, к примеру, следующее:

        >>> f"{2 * 21}"
'42'

    

Когда Python запускает эту f-строку, он умножает 2 на 21 и мгновенно интерполирует полученное значение в итоговую строку.

Пример выше – довольно прост. Однако f-строки обладают более широкими возможностями. Вы можете использовать и другие выражения Python, включая вызовы функций и методов, и даже комплексные выражения или другие более сложные выражения:

        >>> name = "Jane"
>>> age = 25

>>> f"Hello, {name.upper()}! You're {age} years old."
"Hello, JANE! You're 25 years old."

>>> f"{[2**n for n in range(3, 9)]}"
'[8, 16, 32, 64, 128, 256]'

    

В первой f-строке в первый маркер замены вы вставляете вызов метода .upper(). Python вызывает метод и вставляет в итоговую строку имя в верхнем регистре. Во втором примере вы создаете f-строку, в которую вставляется метод распознавания списка. Он создает новый список, состоящий из двух значений.

Форматирование строк с помощью F-строки Python

Выражения, которые вы вставляете в f-строку, оцениваются во время выполнения программы. Затем Python форматирует результат с помощью специального подручного метода .__format__(). Этот метод поддерживает протокол форматирования строк. Этот протокол лежит в основе как метода .format(), который вы уже встречали, так и встроенной функции format():

        >>> format(5425.9292, ".2f")
'5425.93'

    

В качестве аргументов функция принимает значение и спецификатор формата. Затем она применяет спецификатор к значению и возвращает отформатированное значение. Спецификатор формата должен соответствовать правилам мини-языка форматирования строк.

Как и метод .format(), f-строки поддерживают мини-язык форматирования строк. Поэтому в f-строках вы тоже можете использовать спецификаторы формата:

        >>> balance = 5425.9292

>>> f"Balance: ${balance:.2f}"
'Balance: $5425.93'

>>> heading = "Centered string"
>>> f"{heading:=^30}"
'=======Centered string========'

    

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

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

        >>> integer = -1234567
>>> f"Comma as thousand separators: {integer:,}"
'Comma as thousand separators: -1,234,567'

>>> sep = "_"
>>> f"User's thousand separators: {integer:{sep}}"
'User's thousand separators: -1_234_567'

>>> floating_point = 1234567.9876
>>> f"Comma as thousand separators and two decimals: {floating_point:,.2f}"
'Comma as thousand separators and two decimals: 1,234,567.99'

>>> date = (9, 6, 2023)
>>> f"Date: {date[0]:02}-{date[1]:02}-{date[2]}"
'Date: 09-06-2023'

>>> from datetime import datetime
>>> date = datetime(2023, 9, 26)
>>> f"Date: {date:%m/%d/%Y}"
'Date: 09/26/2023'

    

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

Другие важные особенности F-строк

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

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

Использование строковых выражений объектов в F-строках

F-строки в Python поддерживают два флага, которые имеют особое значение в процессе интерполяции. Эти флаги тесно связаны с тем, как Python управляет строковым выражением объектов. Этими флагами являются:

Флаг Описание
!s Интерполирует строковое представление из метода .__str__()
!r Интерполирует представление строки из метода .__repr__()

Специальный метод .__str__() обычно предоставляет удобное для пользователя строковое выражение объекта, а метод .__repr__() возвращает удобное для разработчика выражение. Чтобы проиллюстрировать работу этих подручных методов, рассмотрим следующий класс:

        # person.py

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"I'm {self.name}, and I'm {self.age} years old."

    def __repr__(self):
        return f"{type(self).__name__}(name='{self.name}', age={self.age})"

    

Этот класс содержит два атрибута – .name и .age. Метод .__str__() возвращает строку, состоящую из информативного сообщения для пользователей этого класса. Это сообщение должно быть полезным скорее для конечных потребителей, чем для разработчиков.

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

Как эти рассуждения о выражении строк влияют на f-строки? При создании f-строк вы можете выбрать, какое строковое выражение использовать с помощью флагов !r и !s:

        >>> from person import Person

>>> jane = Person("Jane Doe", 25)

>>> f"{jane!s}"
"I'm Jane Doe, and I'm 25 years old."

>>> f"{jane!r}"
"Person(name='Jane Doe', age=25)"

    

В первой f-строке с помощью флага !s интерполируется строковое выражение, которое возвращает .__str__(). Во второй f-строке вы используете флаг !r для интерполяции удобного для разработчика строкового выражения вашего текущего объекта.

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

Важно заметить, что оператор % также поддерживает эквивалентные типы преобразования s и r, которые работают так же, как и флаги !s и !r в f-строках.

Самодокументирующиеся выражения для отладки

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

Для быстрой проверки можно вызвать print(), как в примере ниже:

        >>> variable = "Some mysterious value"

>>> print(f"{variable = }")
variable = 'Some mysterious value'

    

Для создания самодокументированного выражения в f-строке вы можете использовать имя переменной, за которым следует знак равенства =. Когда Python выполняет f-строку, он выстраивает строковое выражение, содержащее имя переменной, знак равенства и текущее значение переменной. Эта функция f-строки полезна для быстрых отладочных проверок в вашем коде.

Учтите, что пробелы вокруг знака равенства не обязательны, но они отражаются в результате:

        >>> print(f"{variable=}")
variable='Some mysterious value'

>>> print(f"{variable= }")
variable= 'Some mysterious value'

>>> print(f"{variable =}")
variable ='Some mysterious value'

    

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

Сравнение производительности: F-строки по сравнению с классическими инструментами

F-строки работают немного быстрее оператора modulo (%) и метода .format(). Это еще одно замечательное свойство. В приведенном ниже скрипте, при использовании модуля timeit, измеряется время выполнения, необходимое для построения строки с помощью оператора modulo, метода .format() и f-строки:

        # performance.py

import timeit

name = "Linda Smith"
age = 40
strings = {
    "Modulo operator": "'Name: %s Age: %s' % (name, age)",
    ".format() method": "'Name: {} Age: {}'.format(name, age)",
    "f_string": "f'Name: {name} Age: {age}'",
}

def run_performance_test(strings):
    max_length = len(max(strings, key=len))
    for tool, string in strings.items():
        time = timeit.timeit(
            string,
            number=1000000,
            globals=globals()
        ) * 1000
        print(f"{tool}: {time:>{max_length - len(tool) + 6}.2f} ms")

run_performance_test(strings)

    

В этом скрипте функция run_performance_test() занимается измерением времени выполнения трех различных инструментов интерполяции строк. Функция timeit.timeit() внутри цикла for запускает каждый инструмент интерполяции миллион раз и возвращает общее время выполнения.

Затем функция выводит результат на экран. Обратите внимание, как в строке f при вызове функции print() используются спецификаторы формата для удобного оформления вывода кода.

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

        $ python performance.py
Modulo operator:   90.98 ms
.format() method: 144.69 ms
f_string:          87.08 ms

    

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

Модернизация F-строк: Python 3.12 и последующие версии

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

К счастью, Python 3.12 снял эти ограничения, удалив старый парсер и предоставив новую реализацию f-строк, основанную на парсере PEG из Python 3.9. В следующих разделах вы узнаете об этих ограничениях и о том, как Python 3.12 их устранил.

Использование кавычек

Python поддерживает несколько различных типов кавычек в качестве разделителей строковых литералов. Вы можете использовать одинарные ' и двойные кавычки "". Также можно использовать тройные одинарные ''' и тройные двойные кавычки """. Все эти строковые разделители работают и для f-строк. Эта функция позволяет вставлять кавычки в f-строки. Она также позволяет вносить строковые литералы во встроенные выражения и даже создавать вложенные f-строки.

Типичным случаем использования разных кавычек в f-строке является необходимость использования апострофа или обращения к ключу словаря во встроенном выражении:

        >>> person = {"name": "Jane", "age": 25}

>>> f"Hello, {person['name']}! You're {person['age']} years old."
"Hello, Jane! You're 25 years old."

    

В этом примере имеется словарь с данными о человеке. Для определения f-строки используются двойные кавычки. Для доступа к ключу словаря используются одинарные кавычки. В сокращении "You're" одинарная кавычка используется в качестве апострофа.

Итак, где же ограничение на использование кавычек в f-строках до версии Python 3.11? Проблема заключается в том, что в f-строке нельзя повторно использовать кавычки:

        
>>> f"Hello, {person["name"]}!"
  File "<input>", line 1
    f"Hello, {person["name"]}!"
                      ^^^^
SyntaxError: f-string: unmatched '['

    

В данном примере при попытке повторного использования двойных кавычек для доступа к ключу словаря f-строка не работает, и Python выдает синтаксическую ошибку.

К счастью, новые f-строки в Python 3.12 решили эту проблему, позволив повторно использовать кавычки:

        >>> # Python 3.12

>>> person = {"name": "Jane", "age": 25}
>>> f"Hello, {person["name"]}!"
'Hello, Jane!'


    

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

Существует еще одно ограничение f-строки, тесно связанное с кавычками. В Python можно вложить ровно столько f-строк, сколько существует разделителей кавычек:

        >>> f"""{
...     f'''{
...         f"{f'{42}'}"
...     }'''
... }"""
'42'

>>> f"""{
...     f'''{
...         f"{f'{f"{42}"}'}"
...     }'''
... }"""
  File "<stdin>", line 1
    (f"{f'{f"{42}"}'}")
             ^
SyntaxError: f-string: f-string: unterminated string

    

Количество уровней вложенности в f-строке до версии Python 3.11 ограничено доступными разделителями строк, которыми являются ", ', "" и '''. Таким образом, у вас есть только четыре разделителя, которые можно использовать для разграничения уровней вложенности.

В Python 3.12 это ограничение снято, поскольку можно повторно использовать кавычки:

        >>> # Python 3.12

>>> f"{
...     f"{
...         f"{
...             f"{
...                 f"{
...                     f"Deeply nested f-string!"
...                 }"
...             }"
...         }"
...     }"
... }"
'Deeply nested f-string!'

    

До появления новой реализации f-строк не существовало формального ограничения на количество уровней вложенности. Однако тот факт, что нельзя было повторно использовать строковые кавычки, накладывал естественное ограничение на допустимые уровни вложенности в литералы f-строк. Начиная с Python 3.12, кавычки можно использовать повторно, поэтому ограничений на вложенность f-строк не существует.

Использование обратных косых черточек

Еще одним ограничением f-строк до версии 3.12 является невозможность использования символов обратной косой черты во встроенных выражениях. Рассмотрим следующий пример, в котором вы пытаетесь объединить строки с помощью символа обратной последовательности \n:

        
>>> words = ["Hello", "World!", "I", "am", "a", "Pythonista!"]

>>> f"{'\n'.join(words)}"
  File "<input>", line 1
    f"{'\n'.join(words)}"
                        ^
SyntaxError: f-string expression part cannot include a backslash

    

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

Опять-таки, новая реализация f-строк, появившаяся с Python 3.12, решает эту проблему:

        >>> # Python 3.12

>>> words = ["Hello", "World!", "I", "am", "a", "Pythonista!"]

>>> f"{'\n'.join(words)}"
'Hello\nWorld!\nI\nam\na\nPythonista!'

>>> print(f"{'\n'.join(words)}")
Hello
World!
I
am
a
Pythonista!

    

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

Написание встроенных комментариев

F-строки до версии Python 3.11 не позволяли использовать символ # во встроенных выражениях. Из-за этого нельзя было вставлять комментарии во встроенные выражения. Если попытаться это сделать, то будет выдана синтаксическая ошибка:

        >>> employee = {
...     "name": "John Doe",
...     "age": 35,
...     "job": "Python Developer",
... }

>>> f"""Storing employee's data: {
...     employee['name'].upper()  # Always uppercase name before storing
... }"""
  File "<stdin>", line 3
    }"""
        ^
SyntaxError: f-string expression part cannot include '#'


    

Когда вы используете # для ввода комментария в f-строку, вы получаете синтаксическую ошибку. К счастью, в новых f-строках Python 3.12 эта проблема также устранена:

        >>> # Python 3.12

>>> employee = {
...     "name": "John Doe",
...     "age": 35,
...     "job": "Python Developer",
... }

>>> f"Storing employee's data: {
...     employee["name"].upper()  # Always uppercase name before storing
... }"
"Storing employee's data: JOHN DOE"

    

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

Расшифровка сообщений об ошибках F-строк

Новый синтаксический анализатор PEG в Python открывает путь многим улучшениям в языке. С точки зрения пользователя, одним из наиболее ценных улучшений является появление более точных сообщений об ошибках. До версии Python 3.11 эти расширенные сообщения об ошибках не были доступны для f-строк, поскольку они не использовали синтаксический анализатор PEG. Поэтому сообщения об ошибках, связанных с f-строками, были менее конкретными и понятными.

В Python 3.12 появилась возможность исправить и эту проблему. Посмотрите на следующий пример, в котором сравнивается сообщение об ошибке для некорректной f-строки в версиях 3.11 и 3.12:

        >>> # Python 3.11
>>> f"{42 + }"
  File "<stdin>", line 1
    (42 + )
          ^
SyntaxError: f-string: invalid syntax

>>> # Python 3.12
>>> f"{42 + }"
  File "<stdin>", line 1
    f"{42 + }"
          ^
SyntaxError: f-string: expecting '=', or '!', or ':', or '}'

    

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

В Python 3.12 сообщение об ошибке более подробное. Оно сигнализирует о точном месте проблемы в затронутой строке. Кроме того, сообщение об ошибке содержит некоторые предложения, которые могут помочь решить проблему.

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

Использование классических средств форматирования строк вместо F-строк

Несмотря на то, что f-строки являются довольно интересной и популярной функцией Python, они не являются универсальным подходом. Иногда оператор modulo (%) или метод .format() обеспечивают лучшее решение. Иногда они являются единственным вариантом. Все зависит от конкретного случая их использования.

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

Интерполяция словарных значений

Интерполяция значений из словаря в строку может быть распространенной потребностью в вашем коде. Поскольку вы теперь знаете, что f-строки – это удобно, вы можете подумать об их использовании для решения этой задачи. В результате вы получите код, который будет выглядеть следующим образом:

        >>> person = {"name": "Jane Doe", "age": 25}

>>> f"Hello, {person['name']}! You're {person['age']} years old."
"Hello, Jane Doe! You're 25 years old."

    

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

Вот новая версия вашего кода:

        >>> "Hello, {name}! You're {age} years old.".format(**person)
"Hello, Jane Doe! You're 25 years old."

>>> "Hello, {name}!".format(**person)
'Hello, Jane Doe!'

    

В данном примере вместо поиска по словарю в маркерах замены используются непосредственно имена. Единственным дополнительным требованием является использование оператора распаковки словаря ** при вызове .format(). Теперь строка выглядит чище и к тому же немного короче, чем в варианте с использованием f-строки.

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

Также есть возможность использовать оператор modulo:

        >>> "Hello, %(name)s! You're %(age)s years old." % person
"Hello, Jane Doe! You're 25 years old."

>>> "Hello, %(name)s!" % person
'Hello, Jane Doe!'

    

На этот раз строка получилась еще короче. Вы используете непосредственные имена в маркерах замены, и вам не нужен оператор распаковки словаря, поскольку оператор modulo распаковывает словарь за вас. Однако некоторые могут сказать, что маркеры замены не так хорошо читаются, а оператор modulo имеет ограниченные возможности форматирования.

Так какую же версию предпочитаете вы? Поделитесь своими соображениями в комментариях!

Ленивая оценка в логировании

Протоколирование – это типичный пример тех случаев, когда не следует использовать f-строки или .format(). Модуль протоколирования лениво выполняет интерполяцию строк для оптимизации производительности в зависимости от выбранного уровня протоколирования.

К примеру, в коде может быть сотня отладочных сообщений, но только десять предупреждающих. Если вы используете f-строку или метод .format() для создания сообщений протоколирования, то Python будет интерполировать все строки независимо от выбранного уровня протоколирования.

Однако если использовать оператор % и указывать значения для интерполяции в качестве аргументов функций протоколирования, то это позволит оптимизировать весь процесс . Модуль протоколирования будет интерполировать только те строки, которые относятся к текущему и более высоким уровням протоколирования.

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

        >>> import logging
>>> msg = "This is a %s message!"

>>> logging.warning(msg, "WARNING")
WARNING:root:This is a WARNING message!

>>> logging.debug(msg, "DEBUGGING")

    

В этом примере для создания сообщения протоколирования используется синтаксис оператора modulo. Затем в качестве аргумента функциям протоколирования передается значение, которое необходимо интерполировать. Поскольку по умолчанию для протоколирования используется уровень WARNING, в журнал будут записываться только сообщения этого уровня и выше. Поэтому функция debug() не выдает никаких сообщений.

В приведенном выше вызове функции debug() интерполяция строк не происходит, поскольку используется более высокий уровень протоколирования. Однако если использовать .format(), как в приведенном ниже коде, то интерполяция будет происходить всегда:

        >>> msg = "This is a {} message!"

>>> logging.debug(msg.format("DEBUGGING")


    

Если вы вызовете debug() миллион раз внутри цикла, то Python будет усердно оценивать его аргумент, и интерполяция произойдет миллион раз. Такое поведение будет увеличивать производительность кода. Поэтому модуль протоколирования выполняет интерполяцию лениво.

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

SQL-запросы к базе данных

Использование любого инструмента интерполяции строк является плохой идеей при создании SQL-запросов с динамическими параметрами. В этом ситуации средства интерполяции провоцируют атаки типа SQL-инъекции.

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

        import psycopg2

connection = psycopg2.connect(
    database="db",
    user="user",
    password="password"
)
cursor = connection.cursor()

role = "admin"

query_modulo = "SELECT * FROM users WHERE role = '%s'" % role
query_format = "SELECT * FROM users WHERE role = '{role}'".format(role=role)
query_f_string = f"SELECT * FROM users WHERE role = '{role}'"

cursor.execute(query_modulo)
cursor.execute(query_format)
cursor.execute(query_f_string)

    

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

💡Примечание
Код в приведенном примере не выполняется из-за отсутствия библиотеки psycopg2 и предположения об определенной структуре и настройке базы данных. Это лишь наглядный пример кода.

Чтобы избежать риска SQL-инъекции, можно использовать синтаксис оператора % для создания шаблона запроса, а затем предоставить параметр запроса в качестве второго аргумента метода .execute() в виде кортежа или списка:

        query_template = "SELECT * FROM users WHERE role = %s"

cursor.execute(query_template, (role,))

    

В этом примере для создания шаблона запроса используется синтаксис оператора %. Затем в качестве независимого аргумента .execute() указываются параметры. В этом случае при выполнении запроса система баз данных будет использовать указанный тип и значение роли. Такая практика обеспечивает защиту от SQL-инъекций.

💡Примечание
Синтаксис оператора modulo следует использовать только в строковом литерале, представляющем шаблон запроса. Не следует использовать оператор и фактическую последовательность параметров для создания конечного запроса. Пусть .execute() сделает всю работу и создаст окончательный запрос более безопасным способом.

Проще говоря, следует избегать использования каких-либо инструментов строковой интерполяции для создания динамических запросов. Вместо этого используйте синтаксис оператора % для создания шаблона запроса и последовательно передавайте параметры запроса в .execute().

Интернационализация и локализация

Если необходимо обеспечить интернационализацию и локализацию в Python-проекте, следует использовать метод .format():

        >>> greeting_template = "{greeting} Pythonista!"

>>> greeting_en = "Good Morning!"
>>> greeting_es = "¡Buenos días!"
>>> greeting_fr = "Bonjour!"

>>> for greeting in (greeting_en, greeting_es, greeting_fr):
...     print(greeting_template.format(greeting=greeting))
...
Good Morning! Pythonista!
¡Buenos días! Pythonista!
Bonjour! Pythonista!

    

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

Автоматическое преобразование старых строк в F-строки

Если вы работаете над переносом устаревшей кодовой базы на современный Python и одной из ваших целей является преобразование всех строк в f-строки, то вы можете воспользоваться проектом flynt. Этот инструмент позволяет быстро преобразовать классические строки в f-строки.

Чтобы использовать flynt, необходимо сначала установить его с помощью pip:

        $ python -m pip install flynt
    

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

        # sample.py

name = "Jane"
age = 25

print("Hello, %s! You're %s years old." % (name, age))

    

Если вы хотите обновить этот файл и начать использовать f-строки вместо оператора %, то можно просто выполнить следующую команду:

        $ flynt sample.py
Running flynt v.1.0.1
Using config file at .../pyproject.toml

Flynt run has finished. Stats:

Execution time:                            0.002s
Files checked:                             1
Files modified:                            1
Character count reduction:                 78 (98.73%)

Per expression type:
Old style (`%`) expressions attempted:     1/2 (50.0%)
No `.format(...)` calls attempted.
No concatenations attempted.
No static string joins attempted.
F-string expressions created:              1
…

    

Эта команда указывает flynt обновить содержимое файла sample.py посредством замены строк, использующих оператор % и метод .format(), на эквивалентные f-строки. Обратите внимание, что эта команда модифицирует ваши файлы на месте. Таким образом, после выполнения команды sample.py будет выглядеть примерно так:

        # sample.py

name = "Jane"
age = 25

print(f"Hello, {name}! You're {age} years old.")


    

Это здорово, не правда ли? Вы также можете запустить flynt для всего каталога, содержащего большую кодовую базу Python. Программа просканирует каждый файл и преобразует старые строки в f-строки. Таким образом, инструмент может быть полезен, если вы модернизируете свою кодовую базу.

Ключевые моменты

Ниже приведены общие вопросы, обобщающие самые важные понятия, изученные в этом руководстве. Эти вопросы можно использовать для проверки, повторения и закрепления полученных знаний. После каждого вопроса находится ответ, спрятанный в раскрывающемся разделе. Щелкните на переключателе Показать/скрыть, чтобы открыть его. Но сначала попробуйте предложить свой вариант ответа. Готовы ли вы?

Что такое f-строки в Python?
Как написать f-строку в Python?
В чем преимущества f-строк по сравнению с классическими инструментами?

Источники

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ