str.format() — метод форматирования строк

Слово форматирование произошло от слова форма, т.е. форматирование строк – это приведение строк к той форме, которая вам нужна. Для удовлетворения таких нужд, как раз и нужен метод format().


Шаблоны и вставки

Метод format() принимает произвольное количество аргументов и выполняет их подстановку в указанных местах строки, относительно которой он вызван:

>>> s = 'aaa_{0}_ccc_{1}_eee'
>>> 
>>> s.format('BBB', 'DDD')     #  эквивалентно 'aaa_{0}_ccc_{1}_eee'.format('BBB', 'DDD')
'aaa_BBB_ccc_DDD_eee'

Давайте по пунктам разберем как работает это пример:

  • строка s выступает в роли шаблона;
  • фигурными скобками мы обозначили те места, в которых необходимо выполнить вставку;
  • номера в фигурных скобках – это индексы позиционных аргументов метода format().

Словосочетание "позиционные аргументы" означает, что каждый из этих аргументов можно указать по номеру той позиции, в которой он расположен внутри круглых скобок метода – его индексу. Например, в команде s.format('X', 'Y', 'Z') у аргумента 'X' индекс равен \(0\), У 'Y' — \(1\) и 'Z' — \(2\):

>>> s = '-->{0}-->{1}-->{2}'
>>> s.format('X', 'Y', 'Z')
'-->X-->Y-->Z'
>>> 
>>> s = '-->{0}-->{0}-->{0}'
>>> s.format('X', 'Y', 'Z')
'-->X-->X-->X'
>>> 
>>> s = '-->{2}-->{0}-->{1}'
>>> s.format('X', 'Y', 'Z')
'-->Z-->X-->Y'

Помимо позиционных аргументов, данный метод может принимать именованные аргументы:

>>> s = '{nums}____{spaces}_____{smile}'
>>> 
>>> s.format(smile = ':-D', nums = '1234', spaces = '    ')
'1234____    _____:-D'

Обратите внимание на то, как указаны именованные аргументы, например smile = ':-D', значением данного аргумента является строка ':-D', а с помощью оператора "=" выполняется его сопоставление с именем smile.

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

>>> s = '{x} / {y} = {z}'
>>> s.format(x = '8', y = '2', z = '4')
'8 / 2 = 4'
>>> 
>>> s = '{z} / {y} = {x}'
>>> s.format(z = '4', x = '8', y = '2')
'4 / 2 = 8'

В общем случае, аргументы могут быть как именованными, так и позиционными:

>>> s = '{x}; {0}; {y}; {1}'
>>> 
>>> s.format('A', 'B', x = 1, y = 2)
'1; A; 2; B'

Здесь нужно обратить внимание на два нюанса: первый – позиционные аргументы, должны следовать перед именованными; второй – аргументами могут быть данные любого типа:

>>> s = '{int}; {float}; {complex}'
>>> 
>>> s.format(int = 2, float = 2e-5, complex = 2+0.2j)
'2; 2e-05; (2+0.2j)'
>>> 
>>> 
>>> a = list(range(3))
>>> b = dict([[1,'a'],[2,'b']])
>>> c = set('aabbcc')
>>> 
>>> s = '{list}; {dict}; {set}'
>>> 
>>> s.format(list = a, dict = b, set = c)
"[0, 1, 2]; {1: 'a', 2: 'b'}; {'a', 'b', 'c'}"

Мало того, что мы можем подставлять списки и словари, мы еще и можем обращаться к элементам, которые в них расположены.

Мы знаем, что к символам строк и элементам списков (а так же кортежей) можно обращаться по индексу. Допустим мы передаем методу два позиционных аргумента: строку 'XYZ' и список [34, 45, 56]. Тогда, мы можем выполнить подстановку не только подставить их целиком, но и выполнить подстановку их отдельного содержимого:

>>> s = 'sym: {0}; num: {1}'        #  Вот так мы выполним подстановку
>>> s.format('XYZ', [34, 45, 56])  #  и строки и списка целиком
'sym: XYZ; num: [34, 45, 56]'
>>> 
>>> 
>>> s = 'sym: {0[0]}; num: {1[2]}'        #  Вот так мы выполним подстановку
>>> s.format('XYZ', [34, 45, 56])        #  символа 'X' и числа '56'
'sym: X; num: 56'
>>> 
>>> 
>>> s = 'sym: {0[2]}; num: {1[1]}'        #  А так мы подставим
>>> s.format('XYZ', [34, 45, 56])        #  символ 'Z' и число '45'
'sym: Z; num: 45'

Сам шаблон 'sym: {0[2]}; num:{1[1]}' выглядит довольно странно, но работает он, до банального просто: сначала интерпретаор натыкается на "указатель" {0[2]}, сначала выполняется подстановка аргумента с позицией \(0\), т.е. строки 'XYZ'. Если вместо цифры ноль в указателе {0[2]} подставить 'XYZ', то получится 'XYZ'[2], а это выражение, как не трудно догадаться, вернет символ 'Z'. С "указателем" {1[1]} цепочка будет следующей {1[1]} --> [34, 45, 56][1] --> 45.

Уверен, вы теперь легко догадаетесь, как будет работать этот же механизм если мы передадим те же аргументы, но именованными:

>>> s = 'sym: {coord[2]}; num: {value[1]}'
>>> s.format(coord = 'XYZ', value = [34, 45, 56])
'sym: Z; num: 45'

А как все это будет работать, если аргументы будут, как позиционными, так и именованными одновременно? Правильно, вот так:

>>> s = 'sym: {0[2]}; num: {value[1]}'
>>> s.format('XYZ', value = [34, 45, 56])
'sym: Z; num: 45'

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

>>> s = 'sym: {0[x]}; num: {value[1]}'
>>> s.format({'x': 'X', 'y': 'Y'}, value = [34, 45, 56])
'sym: X; num: 45'
>>> 
>>> 
>>> s = 'sym: {coord[y]}; num: {value[a]}'
>>> s.format(value = {'a': 34, 'b': 45, 'c': 56},
...          coord = {'x': 'X', 'y': 'Y'})
'sym: Y; num: 34'

Кстати, обратите внимание, на то как мы указываем ключи словарей. Ключи являются строками, но мы не указываем их как строковые литералы! По хорошему, если бы некоторому элементу словаря соответствовал ключь-строка, то мы бы обратились к нему вот так:

>>> coord = {'x': 'X', 'y': 'Y'}
>>> coord['x']
'X'

То есть, именованные аргументы хоть и можно воспринимать как словарь, но это не совсем словарь. Точно так же, позиционные аргументы – это не совсем кортеж, хотя бы потому что к ним нельзя обращаться по отрицательному смещению (отрицательному индексу):

>>> s = '{0[-1]} = 3, {1[-2]} = 11'
>>> s.format(coord, sym)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: string indices must be integers
>>> 
>>> s = '{coord[-1]} = 3, {sym[-2]} = 11'
>>> s.format(coord = 'XYZ', sym = 'abc')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: string indices must be integers

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

>>> s = '{0} = 3, {1} = 11'
>>> s.format(coord[-1], sym[-2])
'Z = 3, b = 11'

Учитывая, такой "буквальный" механизм подстановки, мы можем предположить: "А нельзя ли через эти указатели обращаться к методам, подставляемых элементов?". Ответ – "Нет". Мы можем обращаться только к атрибутам объектов. Теоретически мы можем обратиться к атрибутам, вот так:

>>> s = 'small: {0.lower}; big: {1.upper}'
>>> s.format('XYZ', 'abc')
'small: <built-in method lower of str object at 0xb70567c0>; big: <built-in method upper of str object at 0xb711b7a0>'

Никакой ошибки не возникло, но и результатов метода мы тоже не увидели. Если необходимо обратиться к методам, то лучше это сделать при передаче аргументов:

>>> s = 'small: {0}; big: {1}'
>>> s.format('XYZ'.lower(), 'abc'.upper())
'small: xyz; big: ABC'

А вот к атрибутам мы можем спокойно обращаться прямо из указателей. Давайте проверим это используя модуль sys:

>>> import sys
>>> 
>>> sys.platform     #  Вот так узнаем свою операционку
'linux'
>>> sys.version      #  А так версию дистрибутива Python
'3.5.4 |Anaconda, Inc.| (default, Dec  7 2017, 12:26:40) \n[GCC 7.2.0]'
>>> 
>>> 
>>> s = 'Я юзаю Python {0.version}\nна компе с {0.platform}'
>>> 
>>> print(s.format(sys))
Я юзаю Python 3.5.4 |Anaconda, Inc.| (default, Dec  7 2017, 12:26:40) 
[GCC 7.2.0]
на компе с linux

Точно так же мы можем обратиться к методам именованных атрибутов:

>>> s = 'Я юзаю Python {system.version}\
... \nна компе с {system.platform}'
>>> 
>>> print(s.format(system = sys))
Я юзаю Python 3.5.4 |Anaconda, Inc.| (default, Dec  7 2017, 12:26:40) 
[GCC 7.2.0]
на компе с linux

А если вспомнить, что аргументами могут быть как списки, так и словари, то к атрибутам их внутренних элементов можно обращаться, указав их эти самые атрибуты, после индекса или ключа необходимого элемента. Если атрибут – список, то обращаемся вот так:

>>> s = 'Я юзаю Python {0[0].version}\
... \nна компе с {0[0].platform} и очень этим {0[1]}'
>>> 
>>> print(s.format([sys, 'ДОВОЛЕН.']))
Я юзаю Python 3.5.4 |Anaconda, Inc.| (default, Dec  7 2017, 12:26:40) 
[GCC 7.2.0]
на компе с linux и очень этим ДОВОЛЕН.

Если атрибут – то вот так:

>>> s = 'Я юзаю Python {0[system].version}\
... \nна компе с {0[system].platform} и очень этим {0[status]}'
>>> 
>>> print(s.format({'system': sys, 'status': 'ДОВОЛЕН'}))
Я юзаю Python 3.5.4 |Anaconda, Inc.| (default, Dec  7 2017, 12:26:40) 
[GCC 7.2.0]
на компе с linux и очень этим ДОВОЛЕН

И так, подъитожим. С помощью фигурных скобок мы можем указать в строке-шаблоне то место, в которое необходимо что-то вставить. Такие фигурные скобки мы назвали "указателями". Внутри фигурных скобок мы указываем идентификатор, указывающий, что именно мы хотим вставить. Как вы могли заметить, различных вариантов записи этих самых идентификаторов довольно много:

  • {1} — указываем позиционный аргумент по его позиции;
  • {arg_name} — указываем именованный аргумент по его имени;
  • {1[0]} — указываем позиционный аргумент, который является списком, по его позиции, и обращаемся к находящемуся в нем элементу по его индексу;
  • {arg_name[0]} — именованный аргумент – это список и мы обращаемся к его внутреннему элементу по индексу;
  • {1[key]} — позиционный аргумент – это словарь и мы указываем ключ необходимого элемента;
  • {arg_name[key]} — теперь именованным аргументом является словарь и к необходимому элементу мы так же обращаемся по его ключу;

Все вышеперечисленное является указателями, а все что находится внутри "{}" является идентификатором. Плюс ко всему, за каждым идентификатором, через точку "." может следовать имя атрибута, например вот так: {arg_name[key].name_atr}.

В общем, метод format() довольно гибок. Причем гибок настолько, что я вполне могу что-то забыть, или, даже о чем-то не подозревать. В общем, если вы в чем-то не уверены или возникают какие-то смутные сомнения, то лучше поэкспериментируйте в интерактивном режиме интерпретатора.


Форматирование

Указателями мы обозначаем место для вставки, а с помощью идентификатора, указываем что именно хотим вставить. Однако, помимо вставки элементов, мы можем выполнять их предварительное форматирование. Для этого необходимо воспользоваться выражением вида:

"{" [field_name] ["!" conversion] [":" format_spec] "}"

В этом выражении идентификатор обозначен, как field_name. Кстати, квадратными скобками "[]" (еще называют брекетами) обозначают необязательные параметры, так что внутри указателей "{}", по идее, можно вообще ничего не указывать. Однако, если все аргументы метода являются позиционными, то их номера позиций будут автоматически раставлены в пустые указатели:

>>> s = '{}{}{}'    #  эквивалентно s = '{0}{1}{2}'
>>> s.format(1, 2, 3)
'123'
>>> s.format(1, 2, 3, 4)
'123'

Далее, после восклицательного знака может следовать, а может и не следовать conversionflag – это флаг, позволяющий указать, какую из встроенных функций нужно применить к значению подставляемого элемента: rrepr(); sstr(); aascii(). Придумать какую-то практическую пользу от использования этих флагов я не могу, разве что вы захотите принудительно форматировать значения как строки. В общем, поехали дальше.

Сразу, после идентификатора, через двоеточие, может следовать format_spec с помощью которого и выполняется форматирование подставляемого значения. Синтаксис format_spec выглядит довольно замысловато:

[[fill]align][sign][⌗][0][width][.precision][typecode]

По сути, это целый мини-язык, который позволяет точно описывать параметры форматирования. Каждый из этих параметров может принимать следующие значения:

  • fill — один символ, выступающий в роли заполнителя;
  • align — позволяет с помощью символов "<", ">", "=" и "^" указать выравнивание вставляемого значения;
  • sign — с помощью символов "+", "-" и " " управляет отображением знака;
  • width — задает ширину, подставляемого значения;
  • precision — определяет точность числового, подставляемого значения;
  • type — определяет тип, подставляемого значения, с помощью символов: "b", "c", "d", "e", "E", "f", "F", "g", "G", "n", "o", "s", "x", "X", "%".

Если значение align указано правильно, то ему может предшествовать символ заполнителя fill, который по понятным причинам, не может быть символом "{" или "}", а по умолчанию является символом пробела. Работает все довольно просто:

>>> '|{:<20}| - выравниваем по левому краю'.format('XX')
'|XX                  | - выравниваем по левому краю'
>>> 
>>> '|{:>20}| - выравниваем по правому краю'.format('XX')
'|                  XX| - выравниваем по правому краю'
>>> 
>>> '|{:^20}| - выравниваем по центру'.format('XX')
'|         XX         | - выравниваем по центру'
>>> 
>>> '|{:_^20}| - выравниваем по центру с заполнителем'.format('XX')
'|_________XX_________| - выравниваем по центру с заполнителем'

Значение "=" параметра align применяется только к числовым значениям и добавляет указанное количество пробелов (по умолчанию) или указанных символов слева от числа

>>> '|{:=20}| - добавляем пробелы'.format(1.234)
'|               1.234| - добавляем пробелы'
>>> 
>>> '|{:0=20}| - добавляем нули'.format(1.234)
'|0000000000000001.234| - добавляем нули'
>>> 
>>> '|{:$=20}| - добавляем произвольный символ'.format(1.234)
'|$$$$$$$$$$$$$$$1.234| - добавляем произвольный символ'

Но особенность действия "=" заключается в том что заполнение происходит между знаком числа и его значением. Это можно продемонстрировать с помощью параметра sign.Если мы хотим указывать как положительные так и отрицательные знаки, то мы должны указать значение "+":

>>> '|{:0=+20}|'.format(1.234)
'|+000000000000001.234|'
>>> '|{:0=+20}|'.format(-1.234)
'|-000000000000001.234|'

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

>>> '|{:0=-20}|'.format(1.234)
'|0000000000000001.234|'
>>> '|{:0=-20}|'.format(-1.234)
'|-000000000000001.234|'

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

>>> '|{:0= 20}|'.format(1.234)
'| 000000000000001.234|'
>>> '|{:0= 20}|'.format(-1.234)
'|-000000000000001.234|'

Параметры [fill], [align], [sign] и [width]

В предыдущих примерах мы уже начали использовать четыре параметра format_spec, это: fill, align, sign и width. С непривычки, все это кажется неочевидным, но давайте выполним еще раз какой-нибудь пример:

>>> '|{:0=+20}|'.format(1.234)
'|+000000000000001.234|'

В указателе мы не указали идентификатор, потому что у нас всего один аргумент и он позиционный, а в этом случае интерпретатор сам расставит необходимые номера позиций, т.е. указатель {:0=+20} эквивалентен указателю {0:0=+20}.

Самое интересное начинается за двоеточием. Мы указали параметр fill, который имеет значение "0". Сразу за ним следует знак "=" — это параметр align. Далее следует знак "+" — это параметр sign. И только теперь мы указываем ширину поля для вставки с помощью параметра width, и эта ширина равна \(20\) символам.

Однако, в синтаксисе [[fill]align][sign][⌗][0][width][.precision][typecode], между параметрами sign и width присутствуют еще два необязательных параметра, которые мы так и не использовали, это [⌗] и [0]. давайте разберемся с ними немного подробнее.

Параметры [⌗] и [0]

Наличие параметра [⌗] заставляет интерпретатор использовать альтернативную форму для преобразования чисел. Числа могут быть следующих типов: int, float и complex, причем для каждого типа существует отдельная альтернативная форма представления. Для целых чисел представленых в двоичном, восьмеричном или шестнадцатеричном представлении будут добавлены соответствующие префиксы:

>>> '|{:b}|'.format(255)
'|11111111|'
>>> '|{:#b}|'.format(255)
'|0b11111111|'
>>> 
>>> '|{:o}|'.format(255)
'|377|'
>>> '|{:#o}|'.format(255)
'|0o377|'
>>> 
>>> '|{:x}|'.format(255)
'|ff|'
>>> '|{:#x}|'.format(255)
'|0xff|'

Для чисел типа float и complex параметр [⌗] гарантирует, что в результате всегда будет десятичная точка:

>>> '|{}|'.format(1.)
'|1.0|'
>>> '|{:#}|'.format(1.)
'|1.0|'
>>> 
>>> '|{}|'.format(1+1j)
'|(1+1j)|'
>>> '|{:#}|'.format(1+1j)
'|(1.+1.j)|'

Если указан тип числа "g" или "G", то конечные нули удаляться не будут

>>> '|{:g}|'.format(1.12300)
'|1.123|'
>>> '|{:#g}|'.format(1.1230000)
'|1.12300|'

Параметр [0] перед параметром width применяется только к числовым значениям и указывает, на то что заполнение до нужной длины должно выполняться нулями:

>>> '{:20}'.format(333)
'                 333'
>>> 
>>> '{:020}'.format(333)    # эквивалентно {:0>20}
'00000000000000000333'

Параметр [precision]

Параметр [precision] позволяет указать необходимую точность подставляемого значения:

>>> '{:}'.format(1.23456789)
'1.23456789'
>>> 
>>> '{:.3}'.format(1.23456789)
'1.23'
>>> 
>>> '{:.4}'.format(1.23456789)
'1.235'

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

>>> '{:20.4}'.format(1.23456789)
'               1.235'
>>> 
>>> '{:0=+20.4}'.format(1.23456789)
'+000000000000001.235'

Если число отворматировано с помощью флагов f или F, то [precision] указывает сколько знаков должно находиться после десятичной точки:

>>> '{:.7}'.format(123456.123456789)
'123456.1'
>>> 
>>> '{:.7f}'.format(123456.123456789)
'123456.1234568'

Для нечисловых типов данный параметр указывает максимальный размер поля для вставки:

>>> '{:.10}'.format('abcdefg')
'abcdefg'
>>> '{:.5}'.format('abcdefg')
'abcde'
>>> '{:.2}'.format('abcdefg')
'ab'

Указание точности для чисел типа int недопустимо:

>>> '{:.2}'.format(123)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Precision not allowed in integer format specifier

Параметр typecode

Параметр typecode позволяет с помощью кодового символа, указать тип подставляемых данных и то как они должны быть представлены.

Для строк доступно всего два кода:

  • "s" — строковый формат, установлен по умолчанию и может быть опущен;
  • код не указан — эквивалентно s;

Для целых чисел доступны следующие коды:

  • "b" — двоичный формат, преобразующий число в двоичную систему счисления;
  • "c" — символьный фомат, преобразующий число в соответствующий ему символ Юникода;
  • "d" — десятичный формат, преобразующий число в двоичную систему счисления;
  • "o" — восьмеричный формат, преобразующий число в восьмеричную систему счисления;
  • "x" — шестнадцатеричный формат, преобразующий число в шестнадцатеричную систему счисления, с использованием строчных букв для обозначения цифр больших \(9\);
  • "X" — аналогичен x, только с использованием заглавных букв для обозначения цифр больших \(9\);
  • "n" — аналогичен "d", но применяет к числам символ разделителя разрядов тысяч, причем для разделителя используется настройка текущей локали;
  • "код не указан" — эквивалентно "d";
>>> s = '''Коды формата для целых чисел:
... код "b": до {0}, после {0:b}
... код "c": до {0}, после {0:c}
... код "d": до {0}, после {0:d}
... код "o": до {0}, после {0:o}
... код "x": до {0}, после {0:x}
... код "X": до {0}, после {0:X}
... код "n": до {0}, после {0:n}
... код не указан: до {0}, после {0:}'''
>>> 
>>> 
>>> print(s.format(10696))
Коды формата для целых чисел:
код "b": до 10696, после 10100111001000
код "c": до 10696, после ⧈
код "d": до 10696, после 10696
код "o": до 10696, после 24710
код "x": до 10696, после 29c8
код "X": до 10696, после 29C8
код "n": до 10696, после 10696
код не указан: до 10696, после 10696

Целые числа так же могут быть представлены в вещественном формате, для этого достаточно указать соответствующий код из приведенной ниже таблицы (кроме "n"). При этом используется функция float() для соответствующего промежуточного преобразования.

Коды для вещественных чисел:

  • "e" — экспоненциальная форма (научная нотация), показатель степени обозначается символом "e", а точность равна 6 знакам после запятой;
  • "E" — аналогично "e", только для обозначения степени используется символ "E";
  • "f" — число с плавающей точкой, точность 6 знаков после запятой;
  • "F" — аналогичен "f", только для обозначения inf и nan используется верхний регистр символов;
  • "g" — общий формат. Если точность \(p\) задана вручную и \(p \geqslant 1\), то это округляет число до \(p\) значащих цифр, а затем форматирует число как "f" или "e", в зависимости от его величины;
  • "G" — аналогичен "g", но переключается или на "E" или на "F" в зависимости от величины числа;
  • "n" — аналогичен "g", но использует символ разделителя для разрядов тысяч, предусмотренный настройкой локали;
  • "%" — формат процента, умнажает число на \(100\) с последующим переключением на код "f" и отбражением знака процента в конце;
  • "код не указан" — аналогичен "g", для отображения конкретного значения используется максимальная точность. После десятичной точки всегда присутствует хотя бы одна цифра.
>>> '{:e}'.format(3.14**10)
'9.317437e+04'
>>> 
>>> '{:E}'.format(3.14**10)
'9.317437E+04'
>>> '{0:f}, {1:f}, {2:f}'.format(3.14**10, float('nan'), float('inf'))
'93174.373387, nan, inf'
>>> 
>>> '{0:F}, {1:F}, {2:F}'.format(3.14**10, float('nan'), float('inf'))
'93174.373387, NAN, INF'
>>> '{:n}'.format(3.14**10)
'93174.4'
>>> '{:%}'.format(25)
'2500.000000%'
>>> 
>>> '{:%}'.format(0.25)
'25.000000%'
>>> 
>>> '{:%}'.format(48/92)
'52.173913%'

Разделители разрядов

Спецификаторы , (доступен в Python с версии 3.1.) и _ (доступен в Python с версии 3.6.) используются для разделителя разрядов тысяч.

>>> 2**40
1099511627776
>>> 
>>> '{:,}'.format(2**40)
'1,099,511,627,776'

Но для разделителей, используемых в конкретных локалях, лучше пользоваться кодом n:

>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
'en_US.UTF-8'
>>> 
>>> '{:n}'.format(2**16/3)
'21,845.3'

Спецификатор _ разделяет разряды тысяч знаком подчеркивания в числах с плавающей запятой и целых чисел с кодом d:

>>> '{:_}'.format(2**16/3)
'21_845.333333333332'
>>> 
>>> '{:_d}'.format(2**30)
'1_073_741_824'

Для целых чисел с кодом b, o, x или X разделители будут вставлены через каждые \(4\) цифры, но для чисел с другими кодами будет вызвано исключение:

>>> '{:_b}'.format(7**17)
'1101_0011_1001_0011_1000_0011_0010_0110_0110_1110_1000_0111'
>>> 
>>> '{:_o}'.format(7**17)
'6471_1603_1146_7207'
>>> 
>>> '{:_x}'.format(7**17)
'd393_8326_6e87'
>>> 
>>> '{:_X}'.format(7**17)
'D393_8326_6E87'