Как всем известно, в питоне есть четыре способа форматирования строк:
string.Template
сишный стиль
f-строки
str.format()
Первый, string.Template
, я последний раз видел в проектах на версии питона не то 2.4, не то 2.5. Очень давно.
Сишный стиль, конечно же, самый няшный. К тому же в питоне можно давать подстановкам имена и после % указывать dict
вместо tuple
или одиночного значения, если оно не tuple. Этот стиль используется в модуле logging
— см. описание класса LogRecord
: The main information passed in is in msg and args, which are combined using str(msg) % args to create the message field of the record. — вы наверняка везде видели рекомендации использовать отложенное форматирование log.debug('%s, %s', 'hello', 'world)
вместо log.debug('%s, %s' % ('hello', 'world))
. Но можно и так: log.debug('%(greet)s, %(me)s', dict(greet='hello', me='world))
f-строки это не совсем данные, это часть кода и для их обработки задействуется компилятор. Но они сделаны по образу и подобию str.format()
, который описан в PEP 3101 – Advanced String Formatting.
Грамматики str.format() и f-строк очень похожи, но для str.format()
она неполная и реализация парсера слегка упрощённая — в сишном модуле _string
видим простое сканирование строки и подсчёт открывающих и закрывающих скобок. Поэтому можем наблюдать вот такое интересное отличие:
class MyObject:
def __format__(self, format_spec):
print(format_spec)
return "Here we go."
print(f"{MyObject():Format spec with {{ braces }} }")
выдаёт ошибку
NameError: name 'braces' is not defined
Потому что { braces }
интерпретируется как set
, состоящий из одного элемента braces
, который мы не определяли. Но str.format()
отрабатывает без ошибок:
print('{my_obj:Format spec with {{ braces }} }'.format(my_obj=MyObject()))
Format spec with { braces }
Here we go.
Потому что двойные скобки интерпретируются как экранирование одиночных. Можно считать это фичей.
Итак, format_spec
допускает рекурсию, но она ограничивается двойкой:
x=1
y=2
z=3
print('x={x:{y:{z}}}'.format_map(locals()))
ValueError: Max string recursion exceeded
Такая неглубокая рекурсия служит исключительно ради гибкости задания параметров форматирования, но для людей с креативной фантазией это несомненно серьёзное ограничение.
Как видим, format_spec
может содержать что угодно, но стандартное форматирование реализовано в классе Formatter
модуля string
. Однако ничто не мешает написать свой formatter. Зачем? — Ну, например, чтобы найти имена всех подстановок в строке:
import string
import _string
class SubstCollector(string.Formatter):
def format(self, format_string, /, *args, **kwargs):
'''
Return names of all substitutions in format_string.
'''
self._substs = set()
self.vformat(format_string, args, kwargs)
return self._substs
def get_field(self, field_name, args, kwargs):
'''
Collect substitution names
i.e. `arg_name` according to
https://docs.python.org/3/library/string.html#formatspec.
'''
first, rest = _string.formatter_field_name_split(field_name)
self._substs.add(first)
return '', first
def convert_field(self, value, conversion):
'''
Do nothing.
'''
return ''
def format_field(self, value, format_spec):
'''
Do nothing.
'''
return ''
print(SubstCollector().format(
'a={a[s].d:Format spec with b={b.c[d]} and {{braces}}} plus {e}'
))
{'a', 'e', 'b'}
Или сделать форматирование с поддержкой отступов. Или, что гораздо прикольнее, не ограничивать рекурсию и замутить свой мини-язык. Но это я всецело оставляю на ваше усмотрение.
igrishaev
"There should be one-- and preferably only one --obvious way to do it." (c)
ValeryIvanov
f-строки и есть тот самый obvious way. Конкуренты проигрывают по всем возможным пунктам настолько, что не использовать f-строки глупо.
igrishaev
Я писал на Питоне много лет, и одно могу сказать точно: нет легче способа разжечь срач, чем спросить о форматировании строк.
Я даже троллил коллег: использовал сишный оператор `"foo %s bar %d" % ('kek', 1)` и наблюдал километры срача между участниками.
ValeryIvanov
Раньше такие срачи имели место быть, но после появления f-строк есть резон использовать другие варианты?
Минусов у f-строк не много. Например, они не подходят для динамического форматирования(с заранее неизвестным форматом строки и, возможно, набором переменных), но это редкий случай использования(само собой, речь идёт о простых строках, где jinja это слишком).
И нельзя отметить, что автодополнение и подсветка ошибок вашей среды разработки, будет прекрасно работать для f-строк.
igrishaev
Да я не оспариваю преимуществ f-строк. Я пошутил, что тезис из Дзена конфликтует с зоопарком форматирования. И когда у вас команда из 10 человек, каждый с такой же уверенностью высказывает другое мнение => срач.
Andrey_Solomatin
pyupgrade и ruff умеют конвертировать другие форматы в f-строки.
Вообще использование линтеров и форматеров убирает достаточно много срачей.
pomponchik
f-строки не поддерживают динамическое форматирование осознанно. f-строка позволяет выполнять произвольный код, и возможность подставлять туда шаблоны создает очень большое окно для уязвимостей.
Однако эта возможность может быть реализована в сторонних библиотеках f-строк, например в моей.
vagon333
Хм, как насчет tab vs. spaces?
bebugz
У них есть одна проблема, если надо переводить проект на Питоне на другие языки, то babel их не поддерживает и придётся везде их менять на format()
ValeryIvanov
Действительно. Сначала хотел написать, мол это babel такой плохой, но прочитав это issue понял, что:
Интересно, в других языках дела с переводами интерполируемых строк обстоят также или есть какое-то решение, которое разработчики питона почему-то проигнорировали.