Форматування рядків тексту (англ. string) - це процес перетворення значень на рядки тексту та вставляння їх у шаблон рядка. Дзен Python стверджує, що має бути «один очевидний спосіб зробити щось у Python». Але коли йдеться про форматування рядків, усе трохи... менш дзенівське.
Може здивувати, що в Python є чотири основні способи форматування рядків, і кожен з них призначений для свого сценарію.
Частково це через довгу історію Python, а частково через такі міркування, як інтернаціоналізація чи очищення вхідних даних.
Почнімо з найновіших доповнень до інструментарію форматування рядків і поступово повернімося назад до «старого стилю» та форматування у стилі printf().
f-string
Представлені в Python 3.6, f-stringи (скорочено від «formatted strings») або інтерполяція рядкових літералів - це спосіб швидко й ефективно обчислювати (і форматувати) вирази та рядки тексту в тип str за допомогою префікса f (або F) перед лапками (_ось так: f'{object}').
f-stringи можна використовувати з усіма типами лапок, як-от одинарними (' або ") і потрійними (''' або """).
Будь-які змінні, вирази чи інші типи, розміщені всередині {}, спершу обчислюються, потім перетворюються на str, а вже потім зʼєднуються з усіма str поза фігурними дужками.
У цьому прикладі ми вставляємо два значення змінних у речення (одне str і одне float):
>>> name = 'eighth'
>>> value = 1/8
# The f-string, using the two values.
# The .2f format code truncates, so the value displays as 0.12.
>>> f'An {name} is approximately {value:.2f}.'
'An eighth is approximately 0.12.'
Обчислювані вирази можуть бути майже будь-якими.
Ось частина (з широкого діапазону) можливостей, які можна обчислити: str, числа, змінні, арифметичні вирази, умовні вирази, вбудовані типи, зрізи, функції, лямбди, включення або будь-які обʼєкти з визначеними методами __str__ чи __repr__.
Від простого до складного:
Вставляння змінної. Найпростіше використання f-stringа - розмістити змінну безпосередньо в рядку тексту.
# Assigning a variable.
>>> name = "World"
# Inserting that variable.
>>> f'Hello, {name}!'
'Hello, World!'
Вирази всередині {}. Будь-який коректний вираз Python можна обчислити всередині фігурних дужок.
Зауважмо: якщо всередині f-stringа в одинарних лапках використати подвійні лапки (або навпаки), керуючі послідовності не знадобляться:
# A dictionary of key:value pairs.
>>> waves = {'water': 1, 'light': 3, 'sound': 5}
# Inserting the whole dict.
>>> f'Wave ranks: {waves}'
"Wave ranks: {'water': 1, 'light': 3, 'sound': 5}"
# An expression can be evaluated inline:
>>> f"Tenfold the value of 'light' is {waves['light'] * 10}."
"Tenfold the value of 'light' is 30."
# A method call can also be evaluated inline:
>>> f'{"hello world!".title()} is a classic greeting.'
'Hello World! is a classic greeting.'
# An f-string can be nested inside another f-string:
>>> f"{f'hello world!'.title()} is a classic greeting."
'Hello World! is a classic greeting.'
Форматування вихідних даних. Міні-мову специфікацій формату можна використовувати, щоб керувати вирівнюванням, точністю чисел і багато чим іншим.
Специфікація формату ставиться після значення й відокремлюється символом :.
# Right-align a value to ten characters, and round it to 3 decimal places.
>>> value = 1 / 7
>>> f'One seventh is {value:10.3f}.'
'One seventh is 0.143.'
# A format specification can be set using variables as well.
>>> padding = 10
>>> precision = 3
>>> f'One seventh is {value:{padding}.{precision}f}.'
'One seventh is 0.143.'
Збираємо все разом. Змінні, вирази, виклики функцій і форматування вихідних даних:
>>> precision = 3
>>> f"{30e8 * 111_000:6.{precision}e}"
'3.330e+14'
>>> verb = 'meet'
>>> the_end = ['end', 'of', 'transmission']
>>> f'"Have a {"NICE".lower()} day, I will {verb} you after {30e8 * 111_000:6.{precision}e} light-years."{the_end}'
'"Have a nice day, I will meet you after 3.330e+14 light-years."[\'end\', \'of\', \'transmission\']'
# Did you notice the escaped single-quotes in the previous example?
# Using double quotes instead of single quotes for the f-string means the list's single-quoted strings print cleanly.
>>> f"Have a nice day. {the_end}"
"Have a nice day. ['end', 'of', 'transmission']"
Варто памʼятати про два головні обмеження.
Вирази в f-stringах не можуть бути порожніми.
До того ж до Python 3.12 вони не могли містити коментарів.
>>> f"An empty expression will error: {}"
SyntaxError: f-string: empty expression not allowed
>>> word = 'word'
>>> f"""A comment in a triple quoted f-string: {
word # I chose a nice variable
}"""
'A comment in a triple quoted f-string: word'
Інтерполяцію рядків не можна використовувати разом з API GNU gettext для інтернаціоналізації (I18N) та локалізації (L10N), тому в будь-яких сценаріях перекладу з «обгортанням рядків» радимо використовувати клас string.Template або метод str.format(), описані нижче, замість f-stringа.
Також варто памʼятати, що використання виразів усередині дужок {} в f-stringах подібне до використання eval() чи exec(), тож воно не дуже безпечне і його ніколи не слід застосовувати до даних, які вводить користувач.
str.format()
Метод str.format() замінює заповнювачі в рядку тексту значеннями, переданими як аргументи функції.
Заповнювачі позначають іменами ({price}), числами ({0} або індексами) чи навіть порожніми (позиційними) заповнювачами {}.
Наприклад:
# A named placeholder and a positional placeholder.
>>> 'My text: {placeholder_1} and {}.'.format(12, placeholder_1='named placeholder')
'My text: named placeholder and 12.'
Як і f-stringи, метод str.format() у Python підтримує цілий набір специфікаторів формату міні-мови, за допомогою яких можна вирівнювати текст, скорочувати числа з плаваючою комою тощо.
Повний шаблон специфікації формату: {[<name>][!<conversion>][:<format_specifier>]}
<name> може бути іменованим заповнювачем, числом або порожнім.!<conversion> необовʼязковий і має бути одним із трьох перетворень: !s для str(), !r для repr() або !a для ascii().
Типово використовується str().:<format_specifier> необовʼязковий і керує тим, як відображається значення.
Більше про можливі варіанти можна дізнатися тут.Приклад перетворень для літери з діакритичним знаком:
# Fills in the object at index zero, converted to a string.
>>> 'An e with an umlaut: {0!s}'.format('ë')
'An e with an umlaut: ë'
# Fills in the object at index zero, converted to a repr.
>>> 'An e with an umlaut (object representation): {0!r}'.format('ë')
"An e with an umlaut (object representation): 'ë'"
# Fills in the object at index zero, converted to ASCII.
>>> 'An e with an umlaut converted into ASCII: {0!a}'.format('ë')
"An e with an umlaut converted into ASCII: '\xeb'"
# Fills in the object in the first position,
# then fills in the object in the second position formatted as a repr.
>>> 'She said her name is not {} but {!r}.'.format('Chloe', 'Zoë')
"She said her name is not Chloe but 'Zoë'."
Приклади поширених специфікаторів формату:
# Integer and binary/hex representations of the same number.
>>> my_num = 42
>>> f"{my_num} in binary is {my_num:b}. In hex, it is {my_num:x}"
"42 in binary is 101010. In hex, it is 2a"
# Alignment: left (<), right (>), and center (^) using up to ten characters total.
>>> f"[{"left":<10}] [{"right":>10}] [{"center":^10}]"
"[left ] [ right] [ center ]"
# Float precision and scientific notation (up to three decimal places).
>>> pi = 3.141592653589793
>>> f"Fixed: {pi:.3} Scientific: {pi:.3e}"
"Fixed: 3.142 Scientific: 3.142e+00"
# Thousands separator and percentage.
>>> balance = 1000
>>> rate = 0.0225
>>> f"Balance: ${balance:,.0f} Interest rate: {rate:.1%}"
"Balance: $1,000 Interest rate: 2.2%"
# Putting it all together:
>>> items = [("Widget", 1250, 9.991), ("Gadget", 37, 24.503), ("Doohickey", 4, 149.002)]
>>> header = f"{"Item":<12} {"Qty":>6} {"Price":>9}"
>>> print(header)
Item Qty Price
>>> for name, qty, price in items:
... print(f"{name:<12} {qty:>6} {price:>9.2f}")
Widget 1250 9.99
Gadget 37 24.50
Doohickey 4 149.00
Більше прикладів наведено в кінці цієї документації.
%, або форматування у стилі printf()
Використання оператора % для форматування - найстаріший спосіб форматування рядків у Python.
Він прийшов із мови C і дозволяє будувати str з позиційних аргументів.
Цей метод витіснили f-stringи та str.format(), тому формат % і називають «старим стилем».
Його досі можна зустріти в Python 2 та/або в застарілому коді.
Хоча цей метод працює в Python 3.x, форматування через % зазвичай уникають: воно схильне до помилок, менш ефективне, має менше доступних можливостей, а будь-яка невідповідність між заповнювачем і аргументом може спричинити виняток.
Використання оператора % подібне до printf(), тому його іноді називають форматуванням printf.
# Assigning a variable.
>>> name = "Anna-conda"
# Building a string using %.
>>> "The snake's name is %s." % name
"The snake's name is Anna-conda."
У наведеному вище прикладі оператор % під час виконання підставляє замість заповнювача %s значення змінної name.
Щоб додати до рядка кілька змінних, після % треба передати кортеж, що містить по одному обʼєкту на кожен заповнювач:
# Assigning variables.
>>> name = "Billy the Kid"
>>> fruit = "grapes"
# Building a string using %.
>>> "Surprisingly, %s's favorite snack was %s." %(name, fruit)
"Surprisingly, Billy the Kid's favorite snack was grapes."
string.Template() (не плутати з t-strings з Python 3.14) - це клас із модуля string (на відміну від вбудованого типу str), який входить до стандартної бібліотеки Python, але його потрібно імпортувати.
Шаблонні рядки підтримують підстановку на основі $ і є значно простішими та менш потужними, ніж інші згадані тут варіанти.
Проте вони можуть бути дуже корисними для складної інтернаціоналізації чи очищення зовнішніх вхідних даних.
string.Template вважають безпечнішим для ненадійних даних від користувача, бо він не дозволяє обчислювати довільні вирази чи звертатися до атрибутів обʼєктів, що зменшує ризик атак із інʼєкцією форматувальних рядків.
>>> from string import Template
>>> name = "Anna-Conda"
# Creating a Template with placeholder text.
>>> template_string = Template("The snake called `$snake_name` has escaped!")
# Calling .substitute() to replace the placeholder with a value.
>>> template_string.substitute(snake_name=name)
'The snake called `Anna-Conda` has escaped!'
Більше про string.Template можна дізнатися в документації Python.
Маючи стільки варіантів і міні-мов, як вибрати те, що потрібно, коли форматуємо рядки в Python? Ось кілька коротких порад:
f-stringи - найновіші й найлегші для читання.
Якщо інтернаціоналізація не потрібна, для Python 3.6+ краще використовувати саме їх.
str.format() універсальний, потужний і сумісний як із gnu gettext, так і з більшістю версій Python.
Якщо потрібні простота, безпека та/або активна інтернаціоналізація, string.Template() допоможе зменшити ризики під час роботи з вхідними даними від користувачів і для обгортання рядків перекладу.
Оператор % загалом вважають застарілим для нового коду, хоча в сучасному Python він досі працює.
Здебільшого його варто використовувати для сумісності зі старішими кодовими базами.
Форматування через % більш схильне до помилок (і має менше функціональності), ніж інші методи, і може спричиняти проблеми з відображенням символів, що не належать до ASCII.
Якщо все ж плануємо його використовувати, варто перевірити подробиці підтримки у своєму дистрибутиві Python.
Якщо захочеться заглибитися далі, варто почати з усього про форматування і найкращих практик форматування рядків у Python.