В данной статье мы рассмотрим четыре основных подхода в форматировании строк в Python, а также их сильные и слабые стороны. Вы также узнаете простое эмпирическое правило подбора лучшего подхода форматирования для строки общего назначения для вашей программы.
Содержание:
- #1 Форматирование строк “По старинке” (оператор %)
- #2 Форматирование строк “По новому” (str.format)
- #3 Интерполяция строк / f-Строки (Python 3.6+)
- #4 Шаблонные строки (Стандартная библиотека Template Strings)
- Каким методом форматирования строк стоит пользоваться?
- Подведем итоги
Помните Дзен Python, где должен быть “один очевидный способ сделать что-то в Python”? Можете почесать голову перед тем, как понять, что зачастую есть целых 4 эффективных способа выполнить форматирование строк в Python.
Есть вопросы по Python?
На нашем форуме вы можете задать любой вопрос и получить ответ от всего нашего сообщества!
Паблик VK
Одно из самых больших сообществ по Python в социальной сети ВК. Видео уроки и книги для вас!
Давайте приступим к делу, работы много! Чтобы иметь в распоряжении простой пример для эксперимента, представим, что у вас есть следующие переменные (или константы, не важно) для работы:
1 2 |
errno = 50159747054 name = 'Bob' |
Основываясь на этих переменных, вы хотите создать строку вывода, содержащую простое уведомление об ошибке:
1 |
'Hey Bob, there is a 0xbadc0ffee error!' |
Эта ошибка может немного подпортить понедельник вашему разрабу… Но мы здесь за тем, чтобы обсудить форматирование строк. Так что приступим к делу.
#1 Форматирование строк “По старинке” (оператор %)
Строки в Python содержат уникальную встроенную операцию, доступ к которой можно получить через оператор %. Это позволяет заметно упростить позиционное форматирование. Если вы когда-либо работали с функцией printf в С, вы сразу узнаете, как это работает. Вот простой пример:
1 2 3 |
print('Hello, %s' % name) # Вывод: "Hello, Bob" |
Я использую определитель формата %s в данном случае, чтобы сказать Python, где именно заменить значение имени, представленного в виде строки.
Существуют другие определители формата, которые позволяют вам контролировать формат выдачи. Например, возможно конвертировать числа в шестнадцатеричную нотацию или добавлять пробелы для создания хорошо отформатированных таблиц и отчетов. (См Python Docs: ““printf-style String Formatting”.)
Здесь, вы можете использовать определитель формата %x для конвертации значения int в строку и представить его в качестве шестнадцатеричного числа:
1 2 3 4 |
errno = 50159747054 print('%x' % errno) # Вывод: 'badc0ffee' |
“По старинке”, синтаксис форматирования строки немного меняется, если вы хотите сделать несколько замен в одной строке. Так как оператор % принимает только один аргумент, вам нужно обернуть правую часть в кортеж, вот так:
1 2 3 4 5 6 |
errno = 50159747054 name = 'Bob' print('Hey %s, there is a 0x%x error!' % (name, errno)) # 'Hey Bob, there is a 0xbadc0ffee error!' |
Также возможно сослаться на заменители переменных по имени в вашей строке формата, если вы передадите сопоставление оператору %:
1 2 3 4 5 6 7 |
print( 'Hey %(name)s, there is a 0x%(errno)x error!' % { "name": name, "errno": errno } ) # Вывод: 'Hey Bob, there is a 0xbadc0ffee error!' |
Это помогает сделать настройки вашего формата более простым процессом, а также упрощает его модификацию в будущем. Вам не нужно беспокоиться о том, что порядок, который вы передаете в значениях, совпадает с порядком, в котором значения указаны в строке формата. Конечно, недостаток — это то, что эта техника требует немного больше ручного ввода.
Я уверен, что вы думаете, почему это printf форматирование считается старым форматированием строк. Технически, оно было заменено новым подходом к форматированию в “Python 3”, которое мы сейчас и рассмотрим!
#2 Форматирование строк “По новому” (str.format)
Python 3 предоставил новый способ форматирования, который также был внесен в раннюю версию Python 2.7. Этот “новый стиль” форматирования строк избавляется от специального синтаксиса оператора % и делает синтаксис для форматирования строк более регулярным. Теперь форматирование обрабатывается вызовом .format() в объекте строки.
Вы можете использовать format(), чтобы выполнить простое позиционное форматирование, также, как мы делали это по старинке:
1 2 |
print('Hello, {}'.format(name)) # Вывод: 'Hello, Bob' |
Или, вы можете сослаться на свои подстановки переменных по имени, и использовать их в том порядке, в котором вам хочется. Это достаточно мощный способ, так как он позволяет повторно упорядочить порядок отображения без изменения переданных функции format() аргументов:
1 2 3 4 5 6 7 |
print( 'Hey {name}, there is a 0x{errno:x} error!'.format( name=name, errno=errno ) ) # Вывод: 'Hey Bob, there is a 0xbadc0ffee error!' |
Это также демонстрирует, что синтаксис формата переменной int — это шестнадцатеричная измененная строка. Теперь вам нужно передать формат spec, внеся суффикс :x. Синтаксис формата строки стал более сильным, не усложняя при этом более простые варианты использования. Не лишним будет ознакомиться с этим мини-языком форматирования строк в документации Python.
В Python 3, этот “новый стиль” форматирования строк более предпочитаем, чем форматирование с оператором %. Хотя метод по старинке и остался в стороне, он не устарел. Он все еще поддерживается последними версиями Python. Согласно этой переписке разработчиков Python и связанной с этим проблемой поисков багов у разработчиков, форматирование с оператором % будет поддерживаться еще долго.
Однако, официальная документация Python 3 не делает явных рекомендаций по использованию старого форматирования:
“Упомянутые операции форматирования демонстрируют ряд неувязок, которые могут привести к распространенным ошибкам (таким, как неспособность правильно отображать кортежи и словари). Использовать новые литералы форматирования строк или интерфейс str.format() помогает избежать этих ошибок. Эти альтернативы также предоставляют более сильные, гибкие и расширяемые подходы в форматировании текста.”
По этому я лично пытаюсь работать str.format при продвижении нового кода. Начав с Python 3.6, есть еще один способ форматирования ваших строк. Рассмотрим его в следующем разделе!
#3 Интерполяция строк / f-Строки (Python 3.6+)
Python 3.6 Добавил новый подход форматирования строк под названием форматированные строчные литералы, или “f-строки”. Этот новый способ форматирования строк позволяет вам использовать встроенные выражения Python внутрь строковых констант. Вот простой, наглядный пример:
1 2 3 4 |
name = 'Bob' print(f'Hello, {name}!') # Вывод: 'Hello, Bob!' |
Как вы видите, это добавляет префикс к константе строки с буквой “f” — следовательно, названием становится “f-strings”. Этот новый синтаксис форматирования — очень мощный. Так как вы можете вставлять произвольные выражения Python, вы можете даже проводить встроенную арифметику. Посмотрим на пример:
1 2 3 4 5 |
a = 5 b = 10 print(f'Five plus ten is {a + b} and not {2 * (a + b)}.') # Вывод: 'Five plus ten is 15 and not 30.' |
Форматированные строчные литералы — это особенность парсера Python, которая конвертирует f-строки в серию строчных констант и выражений. Затем, они соединяются и составляют итоговую строку.
Представьте, что у вас есть следующая функция greet(), которая содержит f-строку:
1 2 3 4 5 6 7 |
def greet(name, question): return f"Hello, {name}! How's it {question}?" print(greet('Bob', 'going')) # Вывод: "Hello, Bob! How's it going?" |
Когда вы разбираете функцию, и смотрите, что происходит за кулисами, вы увидите, что f-строка в функции трансформируется в нечто, похожее на следующее:
1 2 |
def greet(name, question): return "Hello, " + name + "! How's it " + question + "?" |
Настоящая имплементация проходит немного быстрее чем в примере, так как использует опкод BUILD_STRING в качестве оптимизации, однако с точки зрения функционала они одинаковы:
1 2 3 4 5 6 7 8 9 10 11 |
>>> import dis >>> dis.dis(greet) 2 0 LOAD_CONST 1 ('Hello, ') 2 LOAD_FAST 0 (name) 4 FORMAT_VALUE 0 6 LOAD_CONST 2 ("! How's it ") 8 LOAD_FAST 1 (question) 10 FORMAT_VALUE 0 12 LOAD_CONST 3 ('?') 14 BUILD_STRING 5 16 RETURN_VALUE |
Строчные литералы также поддерживают существующий синтаксис формата строк метода str.format(). Это позволяет вам решать те же проблемы с форматированием, которые мы рассматривали в двух предыдущих разделах:
1 2 3 |
print(f"Hey {name}, there's a {errno:#x} error!") # Вывод: "Hey Bob, there's a 0xbadc0ffee error!" |
Новые форматированные строчные литералы аналогичны шаблонным литералам (Template Literals) в JavaScript, которые были добавлены в ES2015. Я думаю это достаточно хорошее нововведение в Python, и я бы с радостью пользовался ими на каждодневной основе (в Python 3). Вы можете узнать больше о форматированных строчных литералах в интернете.
#4 Шаблонные строки (Стандартная библиотека Template Strings)
Рассмотрим еще один инструмент для форматирования строк в Python: template strings. Это более простой и менее мощный механизм, но в ряде случаев он может быть именно тем, что вам нужно.
Давайте посмотрим на простой пример:
1 2 3 4 5 6 |
from string import Template t = Template('Hey, $name!') print(t.substitute(name=name)) # Вывод: 'Hey, Bob!' |
Вы видите, что нам нужно импортировать класс Template из встроенного модуля Python, под названием string. Эти шаблонные строки не являются особенностью корневого языка, но они поддерживаются модулем string в стандартной библиотеке.
Другое отличие заключается в том, что шаблонные строки не позволяют форматировать спецификаторы. Учитывая это, чтобы сделать предыдущий пример с ошибкой рабочим, вам нужно вручную изменить номер ошибки int в шестнадцатеричную строку:
1 2 3 4 5 6 7 8 9 |
templ_string = 'Hey $name, there is a $error error!' print( Template(templ_string).substitute( name=name, error=hex(errno) ) ) # Вывод: 'Hey Bob, there is a 0xbadc0ffee error!' |
Это сработало отлично!
Так когда нам стоит использовать шаблонные строки в программах Python?
На мой взгляд, лучшее время для использования шаблонных строк — это когда вы обрабатываете форматированные строки, которые были созданы пользователями вашей программы. Учитывая их простоту, шаблонные строки — это безопасный выбор.
Более сложные мини-языки форматирования других техник форматирования строк может ослабить безопасность ваших программ и сделать их уязвимее. Например, строка форматирования может получить доступ к произвольным переменным в вашей программе.
Это значит, что злоумышленник может использовать форматную строку, которая (технически), может слить ключи безопасности и другую конфиденциальную информацию! Вот простой пример, подтверждающий то, как эта атака может быть использована против вашего кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# Вот ваш супер-секретный ключ: SECRET = 'this-is-a-secret' class Error: def __init__(self): pass # Злоумышленник может создать форматную строку, которая # может считать данные из общего словаря: user_input = '{error.__init__.__globals__[SECRET]}' # Это позволяет ему профильтровать конфиденциальную информацию # такую, как секретный ключ: err = Error() print(user_input.format(error=err)) # Вывод: 'this-is-a-secret' |
Видите, как гипотетический злоумышленник может извлечь нашу секретную строку, получив доступ к словарю __globals__ из вредоносной строки форматирования?
Страшно, да? Шаблонные строки закрывают этот вектор атаки. Это делает их более безопасным выбором, если вы обрабатываете строки форматирования, созданные в вводе пользователя:
1 2 3 4 5 |
print(user_input = '${error.__init__.__globals__[SECRET]}') print(Template(user_input).substitute(error=err)) ValueError: "Invalid placeholder in string: line 1, col 1" |
Каким методом форматирования строк стоит пользоваться?
Я точно понял, что наличие такого обширного выбора в том, как форматировать строки в Python, может очень запутать. Вот отличная подсказка, которая должна вам помочь:
Эта блок-схема основана на эмпирическом правиле, которое я применяю, когда пишу в Python:
Если ваши строки форматирования поддерживаются пользователями, используйте шаблонные строки (способ 4), чтобы избежать проблем с уязвимостью программы. В противном случае, воспользуйтесь литеральной интерполяцией строк / f-строками (способ 3), если вы используете Python 3.6+ и “новым способом” с str.format (способ 2), если не пользуетесь Python 3.6.
Подведем итоги
Это может быть удивительным, но существует более одного способа обработки форматирования строк в Python. Каждый метод имеет свои индивидуальные преимущества и недостатки. Использования того или иного метода форматирования строк зависит исключительно от вашей цели применения.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»