В Python есть 3 способа форматировать строки, и один из них лучше других. Но не будем забегать наперед — о каком именно форматировании вообще речь? Каждый раз когда мы хотим поприветствовать пользователя по имени нам нужно вставить строку с именем в строку-шаблон. Большинство полезных записей в логах так же содержат значения переменных. И вот пример:
integer = 42
string = 'FORTY_TWO'
print('string number %s, or simply %d' % (string, integer))
print('string number {}, or simply {}'.format(string, integer))
print(f'string number {string}, or simply {integer}')
Первый способ, форматирование оператором %, пришел в Python еще из С — он имитирует функцию printf. Этот способ был первым в питоне, и остается единственным (из обсуждаемых в статье) в Python версии 2.5 и ниже.
Второй способ — это метод str.format, принадлежащий встроенному классу строк. Он появился с Python 3.0, и был портирован в версию 2.6. Этот метод был рекомендован как обладающий более богатым синтаксисом.
Третий способ, f-string, появился в Python версии 3.6. Как объяснено в PEP-0498, создание нового способа форматирования строк было мотивировано недостатками существующих методов, которые авторы характеризуют как подверженные ошибкам, недостаточно гибкие и не элегантные:
This PEP is driven by the desire to have a simpler way to format strings in Python. The existing ways of formatting are either error prone, inflexible, or cumbersome.
Итак, у нас есть три способа решить одну задачу. Но может это дело личного вкуса и предпочтений? Возможно, но стиль вашего кода (особенно кода в проекте с большим количеством участников) точно выиграет от единообразия. В лучшем случае стоит использовать один метод форматирования строк, тогда читать код станет проще. Но какой же метод выбрать? И есть ли разница в производительности кода?
Попробуем ответить на вопрос о производительности экспериментально:
import timeit
setup = """
integer = 42
string = 'FORTY_TWO'
""".strip()
percent_stmt ="'Number %s or simply %d' % (string, integer)"
call_stmt = "'Number {} or simply {}'.format(string, integer)"
fstr_stmt = """f'Number {string} or simply {integer}'"""
def time(stmt):
return f"{timeit.timeit(stmt, setup, number=int(1e7)):.3f}"
print(f"Timing percent formating: | {time(percent_stmt)}")
print(f"Timing call formating: | {time(call_stmt)}")
print(f"Timing f-string formating: | {time(fstr_stmt)}")
Результаты на мак-буке с Python 3.7:
Timing percent formating: | 2.025
Timing call formating: | 2.943
Timing f-string formating: | 1.348
Разница значительная. И что же теперь, запускать поиск regex на ".format" и переписывать сотни выражений? В принципе задача простая, но трудоемкая. Плюс вероятность допустить ошибку и посадить баг в до этого работающий код! Кажется, есть место для автоматизации. И действительно, существуют библиотеки способные конвертировать большинство выражений в f-strings: flynt, pyupgrade.
Flynt прост в использовании. К примеру, запустим конвертацию на исходном коде flask:
38f9d3a65222:~ ikkamens$ git clone https://github.com/pallets/flask.git
Cloning into 'flask'...
...
Resolving deltas: 100% (12203/12203), done.
38f9d3a65222:~ ikkamens$ flynt flask
Flynt run has finished. Stats:
Execution time: 0.623s
Files modified: 18
Expressions transformed: 43
Character count reduction: 241 (0.04%)
_-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_.
Please run your tests before commiting. Report bugs as github issues at: https://github.com/ikamensh/flynt
Thank you for using flynt! Fstringify more projects and recommend it to your colleagues!
_-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_._-_.
38f9d3a65222:~ ikkamens$
Так же стоит отметить возможность конвертации выражений, занимающих несколько строк: и сбор статистики о выполненных изменениях. Флаг --line_length XX определяет лимит длины строки после преобразования. Дополнительным плюсом станет сбор статистики о совершенных изменениях. Flynt позволяет вызвать pyupgrade с флагом --upgrade.
Pyupgrade включает в себя больше функционала, и может почистить ваш код от многих артефактов Python 2 — таких как наследование от object, указание имен классов в super и многое другое. Pyupgrade задуман для использования с pre-commit, утилитой для автоматической модификации кода перед коммитами.
Конвертировать лучше исходники в гите или другом контроле версий. Стоит прогнать тесты и посмотреть на изменения самому (используя git diff или среды типа PyCharm). Покуда среди нас живы те, кому не все равно, что код стал на пару символов короче, проактивная конвертация также сэкономит их время. Ведь рано или поздно кто-то начнет делать руками то, что можно сделать утилитой. F-strings работают только на Python 3.6+, но скоро это не будет проблемой так как другие версии устареют.
Стоит отметить что совсем отказаться от классического метода .format не получится. В случае когда вы используете один и тот же шаблон для создания сообщений с разными переменными в разных местах кода следует сохранить этот шаблон в переменной, и использовать её — принцип «Don't repeat yourself» куда важнее чем выигранные наносекунды от форматирования строки.
Выводы:
Мы рассмотрели три способа форматирования строк, доступные в версиях Python 3.6+, их краткую историю и сравнили их производительность. Мы также рассмотрели существующие в открытом доступе утилиты для автоматической конвертации кода к новому методу форматирования строк, и их дополнительные функции. Не забывайте о простых вещах в вашем коде, и удачи!
Автор: ikamensh