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

4 minute read

В Python существует несколько способов форматирования строк: старый вариант форматирования с %, форматирование с помощью метода format и использование string.Template. Кроме того, к форматированию строк можно условно отнести сложение отдельных строк в одно целое.

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

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

Синтаксис

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

In [1]: ip = '10.1.1.1'

In [2]: mask = 24

In [3]: f"IP: {ip}, mask: {mask}"
Out[3]: 'IP: 10.1.1.1, mask: 24'

Аналогичный результат с format можно получить так: "IP: {ip}, mask: {mask}".format(ip=ip, mask=mask).

Очень важное отличие f-строк от format: f-строки это выражение, которое выполняется, а не просто строка. То есть, в случае с ipython, как только мы написали выражение и нажали Enter, оно выполнилось и вместо выражений {name} и {mask} подставились значения переменных.

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

In [1]: f"IP: {ip}, mask: {mask}"
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-e6f8e01ac9c4> in <module>()
----> 1 f"IP: {ip}, mask: {mask}"

NameError: name 'ip' is not defined

Кроме подстановки значений переменных, в фигурных скобках можно писать выражения:

In [1]: octets = ['10', '1', '1', '1']

In [2]: mask = 24

In [3]: f"IP: {'.'.join(octets)}, mask: {mask}"
Out[3]: 'IP: 10.1.1.1, mask: 24'

После двоеточия в f-строках можно указывать те же значения, что и при использовании format:

In [9]: oct1, oct2, oct3, oct4 = [10, 1, 1, 1]

In [10]: print(f'''
    ...: IP address:
    ...: {oct1:<8} {oct2:<8} {oct3:<8} {oct4:<8}
    ...: {oct1:08b} {oct2:08b} {oct3:08b} {oct4:08b}''')

IP address:
10       1        1        1
00001010 00000001 00000001 00000001

Особенности использования f-строк

При использовании f-строк нельзя сначала создать шаблон, а затем его использовать, как при использовании format.

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

In [7]: ip = '10.1.1.1'

In [8]: mask = 24

In [9]: print(f"IP: {ip}, mask: {mask}")
IP: 10.1.1.1, mask: 24

Если необходимо подставить другие значения, надо создать новые переменные и снова написать f-строку:

In [11]: ip = '10.2.2.2'

In [12]: mask = 24

In [13]: ip = '10.2.2.2'

In [14]: print(f"IP: {ip}, mask: {mask}")
IP: 10.2.2.2, mask: 24

Использование f-строк в таком случае может быть не очень удобным, особенно если надо будет изменить f-строку, в таком случае вручную надо будет менять и вторую.

При использовании f-строк в циклах, f-строку надо писать в теле цикла, чтобы она “подхватывала” новые значения переменных на каждой итерации:

In [1]: ip_list = ['10.1.1.1/24', '10.2.2.2/24', '10.3.3.3/24']

In [2]: for ip_address in ip_list:
   ...:     ip, mask = ip_address.split('/')
   ...:     print(f"IP: {ip}, mask: {mask}")
   ...:
IP: 10.1.1.1, mask: 24
IP: 10.2.2.2, mask: 24
IP: 10.3.3.3, mask: 24

Примеры использования f-строк

Базовая подстановка переменных:

In [1]: intf_type = 'Gi'

In [2]: intf_name = '0/3'

In [3]: f'interface {intf_type}/{intf_name}'
Out[3]: 'interface Gi/0/3'

Выравнивание столбцами:

In [6]: topology = [['sw1', 'Gi0/1', 'r1', 'Gi0/2'],
   ...:             ['sw1', 'Gi0/2', 'r2', 'Gi0/1'],
   ...:             ['sw1', 'Gi0/3', 'r3', 'Gi0/0'],
   ...:             ['sw1', 'Gi0/5', 'sw4', 'Gi0/2']]
   ...:

In [7]: for connection in topology:
   ...:     l_device, l_port, r_device, r_port = connection
   ...:     print(f'{l_device:10} {l_port:7} {r_device:10} {r_port:7}')
   ...:
sw1        Gi0/1   r1         Gi0/2
sw1        Gi0/2   r2         Gi0/1
sw1        Gi0/3   r3         Gi0/0
sw1        Gi0/5   sw4        Gi0/2

Ширина столбцов может быть указана через переменную:

In [6]: topology = [['sw1', 'Gi0/1', 'r1', 'Gi0/2'],
   ...:             ['sw1', 'Gi0/2', 'r2', 'Gi0/1'],
   ...:             ['sw1', 'Gi0/3', 'r3', 'Gi0/0'],
   ...:             ['sw1', 'Gi0/5', 'sw4', 'Gi0/2']]
   ...:

In [7]: width = 10

In [8]: for connection in topology:
   ...:     l_device, l_port, r_device, r_port = connection
   ...:     print(f'{l_device:{width}} {l_port:{width}} {r_device:{width}} {r_port:{width}}')
   ...:
sw1        Gi0/1      r1         Gi0/2
sw1        Gi0/2      r2         Gi0/1
sw1        Gi0/3      r3         Gi0/0
sw1        Gi0/5      sw4        Gi0/2

Работа со словарями

In [1]: session_stats = {'done': 10, 'todo': 5}

In [2]: if session_stats['todo']:
   ...:     print(f"Pomodoros done: {session_stats['done']}, TODO: {session_stats['todo']}")
   ...: else:
   ...:     print(f"Good job! All {session_stats['done']} pomodoros done!")
   ...:
Pomodoros done: 10, TODO: 5

Вызов функции len внутри f-строки:

In [2]: topology = [['sw1', 'Gi0/1', 'r1', 'Gi0/2'],
   ...:             ['sw1', 'Gi0/2', 'r2', 'Gi0/1'],
   ...:             ['sw1', 'Gi0/3', 'r3', 'Gi0/0'],
   ...:             ['sw1', 'Gi0/5', 'sw4', 'Gi0/2']]
   ...:

In [3]: print(f'Количество подключений в топологии: {len(topology)}')
Количество подключений в топологии: 4

Вызов метода upper внутри f-строки:

In [1]: name = 'python'

In [2]: print(f'Zen of {name.upper()}')
Zen of PYTHON

Конвертация чисел в двоичный формат:

In [7]: ip = '10.1.1.1'

In [8]: oct1, oct2, oct3, oct4 = ip.split('.')

In [9]: print(f'{int(oct1):08b} {int(oct2):08b} {int(oct3):08b} {int(oct4):08b}')
00001010 00000001 00000001 00000001

Что использовать format или f-строки

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

In [6]: ip = [10, 1, 1, 1]

In [7]: oct1, oct2, oct3, oct4 = ip
   ...: print(f'{oct1:08b} {oct2:08b} {oct3:08b} {oct4:08b}')
   ...:
00001010 00000001 00000001 00000001

In [8]: template = "{:08b} "*4

In [9]: template.format(*ip)
Out[9]: '00001010 00000001 00000001 00000001 '

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

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

In [1]: def show_me_ip(ip, mask):
   ...:     return f"IP: {ip}, mask: {mask}"
   ...:

In [2]: show_me_ip('10.1.1.1', 24)
Out[2]: 'IP: 10.1.1.1, mask: 24'

In [3]: show_me_ip('192.16.10.192', 28)
Out[3]: 'IP: 192.16.10.192, mask: 28'

Дополнительные ресурсы

Leave a Comment