Форматирование строки:% против .format

Python 2.6 представил метод str.format() с немного отличающимся синтаксисом от существующего оператора %. Что лучше и для каких ситуаций?

  • Следующий метод использует каждый метод и имеет тот же результат, поэтому в чем разница?

    #!/usr/bin/python
    sub1 = "python string!"
    sub2 = "an arg"
    
    a = "i am a %s" % sub1
    b = "i am a {0}".format(sub1)
    
    c = "with %(kwarg)s!" % {'kwarg':sub2}
    d = "with {kwarg}!".format(kwarg=sub2)
    
    print a    # "i am a python string!"
    print b    # "i am a python string!"
    print c    # "with an arg!"
    print d    # "with an arg!"
    
  • Кроме того, когда форматирование строк происходит в Python? Например, если мой уровень ведения журнала установлен на HIGH, я все равно сделаю удар для выполнения следующей операции %? И если да, есть ли способ избежать этого?

    log.debug("some debug info: %s" % some_info)
    

Ответ 1

Чтобы ответить на ваш первый вопрос... .format просто кажется более сложным во многих отношениях. Досадная вещь о % также заключается в том, как она может принимать переменную или кортеж. Вы могли бы подумать, что следующее всегда будет работать:

"hi there %s" % name

но если name окажется (1, 2, 3), он выкинет TypeError. Чтобы гарантировать, что он всегда печатает, вам нужно будет сделать

"hi there %s" % (name,)   # supply the single argument as a single-item tuple

который просто уродлив. .format не имеет этих проблем. Кроме того, во втором примере, который вы указали, пример .format выглядит более чистым.

Почему бы вам не использовать его?

  • не зная об этом (я, прежде чем читать это)
  • должен быть совместим с Python 2.5

Чтобы ответить на ваш второй вопрос, форматирование строки происходит одновременно с любой другой операцией - при вычислении выражения форматирования строки. И Python, не являясь ленивым языком, оценивает выражения перед вызовом функций, поэтому в вашем примере log.debug выражение "some debug info: %s"%some_info сначала оценивается, например, "some debug info: roflcopters are active", то эта строка будет передана в log.debug().

Ответ 2

Что-то, что не может выполнить оператор modulo (%), afaik:

tu = (12,45,22222,103,6)
print '{0} {2} {1} {2} {3} {2} {4} {2}'.format(*tu)

результат

12 22222 45 22222 103 22222 6 22222

Очень полезно.

Другая точка: format(), являющаяся функцией, может использоваться как аргумент в других функциях:

li = [12,45,78,784,2,69,1254,4785,984]
print map('the number is {}'.format,li)   

print

from datetime import datetime,timedelta

once_upon_a_time = datetime(2010, 7, 1, 12, 0, 0)
delta = timedelta(days=13, hours=8,  minutes=20)

gen =(once_upon_a_time +x*delta for x in xrange(20))

print '\n'.join(map('{:%Y-%m-%d %H:%M:%S}'.format, gen))

Результаты в:

['the number is 12', 'the number is 45', 'the number is 78', 'the number is 784', 'the number is 2', 'the number is 69', 'the number is 1254', 'the number is 4785', 'the number is 984']

2010-07-01 12:00:00
2010-07-14 20:20:00
2010-07-28 04:40:00
2010-08-10 13:00:00
2010-08-23 21:20:00
2010-09-06 05:40:00
2010-09-19 14:00:00
2010-10-02 22:20:00
2010-10-16 06:40:00
2010-10-29 15:00:00
2010-11-11 23:20:00
2010-11-25 07:40:00
2010-12-08 16:00:00
2010-12-22 00:20:00
2011-01-04 08:40:00
2011-01-17 17:00:00
2011-01-31 01:20:00
2011-02-13 09:40:00
2011-02-26 18:00:00
2011-03-12 02:20:00

Ответ 3

Предполагая, что вы используете модуль Python logging, вы можете передать аргументы форматирования строки в качестве аргументов в метод .debug(), а не самостоятельно форматировать:

log.debug("some debug info: %s", some_info)

который позволяет избежать форматирования, если регистратор фактически ничего не записывает.

Ответ 4

Начиная с Python 3.6 (2016) вы можете использовать f-строки для замены переменных:

>>> origin = "London"
>>> destination = "Paris"
>>> f"from {origin} to {destination}"
'from London to Paris'

Обратите внимание на f" префикс. Если вы попробуете это в Python 3.5 или более ранней версии, вы получите SyntaxError.

См. Https://docs.python.org/3.6/reference/lexical_analysis.html#f-strings

Ответ 5

PEP 3101 предлагает заменить оператора % новым расширенным форматированием строк в Python 3, где он будет использоваться по умолчанию.

Ответ 6

Но будьте осторожны, только сейчас я обнаружил одну проблему при попытке заменить все % на .format в существующем коде: '{}'.format(unicode_string) будет пытаться кодировать unicode_string и, вероятно, не удастся.

Посмотрите на этот журнал интерактивных сеансов Python:

Python 2.7.2 (default, Aug 27 2012, 19:52:55) 
[GCC 4.1.2 20080704 (Red Hat 4.1.2-48)] on linux2
; s='й'
; u=u'й'
; s
'\xd0\xb9'
; u
u'\u0439'

s - это просто строка (называемая "байтовый массив" в Python3), а u - строка Unicode (называемая "string" в Python3):

; '%s' % s
'\xd0\xb9'
; '%s' % u
u'\u0439'

Когда вы укажете объект Unicode в качестве параметра для оператора %, он выдает строку Unicode, даже если исходная строка не была Unicode:

; '{}'.format(s)
'\xd0\xb9'
; '{}'.format(u)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'latin-1' codec can't encode character u'\u0439' in position 0: ordinal not in range(256)

но функция .format поднимет "UnicodeEncodeError":

; u'{}'.format(s)
u'\xd0\xb9'
; u'{}'.format(u)
u'\u0439'

и он будет работать с аргументом Unicode штрафом, только если исходная строка была Unicode.

; '{}'.format(u'i')
'i'

или строка аргумента может быть преобразована в строку (так называемый "байтовый массив" )

Ответ 7

Еще одно преимущество .format (чего я не вижу в ответах): он может принимать свойства объекта.

In [12]: class A(object):
   ....:     def __init__(self, x, y):
   ....:         self.x = x
   ....:         self.y = y
   ....:         

In [13]: a = A(2,3)

In [14]: 'x is {0.x}, y is {0.y}'.format(a)
Out[14]: 'x is 2, y is 3'

Или, как аргумент ключевого слова:

In [15]: 'x is {a.x}, y is {a.y}'.format(a=a)
Out[15]: 'x is 2, y is 3'

Это невозможно с %, насколько я могу судить.

Ответ 8

Как я обнаружил сегодня, старый способ форматирования строк через % не поддерживает Decimal, модуль Python для десятичной неподвижной точки и арифметики с плавающей запятой, из коробки.

Пример (с использованием Python 3.3.5):

#!/usr/bin/env python3

from decimal import *

getcontext().prec = 50
d = Decimal('3.12375239e-24') # no magic number, I rather produced it by banging my head on my keyboard

print('%.50f' % d)
print('{0:.50f}'.format(d))

Вывод:

0,00000000000000000000000312375239000000009907464850 0,00000000000000000000000312375239000000000000000000

Разумеется, это могут быть проблемы, но вы все равно можете использовать метод format() сразу.

Ответ 9

% дает лучшую производительность, чем format из моего теста.

Тестовый код:

Python 2.7.2:

import timeit
print 'format:', timeit.timeit("'{}{}{}'.format(1, 1.23, 'hello')")
print '%:', timeit.timeit("'%s%s%s' % (1, 1.23, 'hello')")

Результат:

> format: 0.470329046249
> %: 0.357107877731

Python 3.5.2

import timeit
print('format:', timeit.timeit("'{}{}{}'.format(1, 1.23, 'hello')"))
print('%:', timeit.timeit("'%s%s%s' % (1, 1.23, 'hello')"))

Результат

> format: 0.5864730989560485
> %: 0.013593495357781649

Это выглядит в Python2, разница невелика, тогда как в Python3 % намного быстрее, чем format.

Спасибо @Chris Cogdon за пример кода.

Изменить 1:

В июле 2019 года снова протестирован на Python 3.7.2.

Результат:

> format: 0.86600608
> %: 0.630180146

Разницы не так много. Я думаю, что Python постепенно улучшается.

Изменить 2:

После того, как кто-то упомянул f-строку Python 3 в комментарии, я провел тест для следующего кода в Python 3.7.2:

import timeit
print('format:', timeit.timeit("'{}{}{}'.format(1, 1.23, 'hello')"))
print('%:', timeit.timeit("'%s%s%s' % (1, 1.23, 'hello')"))
print('f-string:', timeit.timeit("f'{1}{1.23}{\"hello\"}'"))

Результат:

format: 0.8331376779999999
%: 0.6314778750000001
f-string: 0.766649943

Кажется, что f-строка все еще медленнее, чем %, но лучше, чем format.

Ответ 10

Если ваш python> = 3.6, форматированный литерал F-string - ваш новый друг.

Это более простая, чистая и лучшая производительность.

In [1]: params=['Hello', 'adam', 42]

In [2]: %timeit "%s %s, the answer to everything is %d."%(params[0],params[1],params[2])
448 ns ± 1.48 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [3]: %timeit "{} {}, the answer to everything is {}.".format(*params)
449 ns ± 1.42 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [4]: %timeit f"{params[0]} {params[1]}, the answer to everything is {params[2]}."
12.7 ns ± 0.0129 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

Ответ 11

В качестве побочного примечания вам не нужно принимать удар производительности, чтобы использовать новое форматирование стиля с протоколированием. Вы можете передать любой объект в logging.debug, logging.info и т.д., Который реализует магический метод __str__. Когда модуль протоколирования решил, что он должен излучать ваш объект сообщения (независимо от того, что он есть), он вызывает str(message_object) перед этим. Таким образом, вы можете сделать что-то вроде этого:

import logging


class NewStyleLogMessage(object):
    def __init__(self, message, *args, **kwargs):
        self.message = message
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        args = (i() if callable(i) else i for i in self.args)
        kwargs = dict((k, v() if callable(v) else v) for k, v in self.kwargs.items())

        return self.message.format(*args, **kwargs)

N = NewStyleLogMessage

# Neither one of these messages are formatted (or calculated) until they're
# needed

# Emits "Lazily formatted log entry: 123 foo" in log
logging.debug(N('Lazily formatted log entry: {0} {keyword}', 123, keyword='foo'))


def expensive_func():
    # Do something that takes a long time...
    return 'foo'

# Emits "Expensive log entry: foo" in log
logging.debug(N('Expensive log entry: {keyword}', keyword=expensive_func))

Все это описано в документации Python 3 (https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles). Однако он будет работать и с Python 2.6 (https://docs.python.org/2.6/library/logging.html#using-arbitrary-objects-as-messages).

Одним из преимуществ использования этого метода, помимо того, что он является агностиком в стиле форматирования, является то, что он допускает ленивые значения, например. функции expensive_func выше. Это дает более элегантную альтернативу советам, приведенным в документах Python: https://docs.python.org/2.6/library/logging.html#optimization.

Ответ 12

Одна ситуация, когда % может помочь, - это когда вы форматируете выражения регулярных выражений. Например,

'{type_names} [a-z]{2}'.format(type_names='triangle|square')

вызывает IndexError. В этой ситуации вы можете использовать:

'%(type_names)s [a-z]{2}' % {'type_names': 'triangle|square'}

Это позволяет избежать написания регулярного выражения как '{type_names} [a-z]{{2}}'. Это может быть полезно, когда у вас есть два регулярных выражения, где один используется без формата, но конкатенация обоих файлов отформатирована.

Ответ 13

Я бы добавил, что начиная с версии 3.6 мы можем использовать fstrings, как показано ниже

foo = "john"
bar = "smith"
print(f"My name is {foo} {bar}")

Что дает

Меня зовут Джон Смит

Все преобразуется в строки

mylist = ["foo", "bar"]
print(f"mylist = {mylist}")

Результат:

mylist = ['foo', 'bar']

вы можете передавать функцию, как в других форматах.

print(f'Hello, here is the date : {time.strftime("%d/%m/%Y")}')

Предоставление, например

Привет, вот дата: 16/04/2018

Ответ 14

Для версии python> = 3.6 (см. PEP 498)

s1='albha'
s2='beta'

f'{s1}{s2:>10}'

#output
'albha      beta'

Ответ 15

Но одно дело в том, что если у вас есть вложенные фигурные скобки, они не будут работать в формате, но % будет работать.

Пример:

>>> '{{0}, {1}}'.format(1,2)
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    '{{0}, {1}}'.format(1,2)
ValueError: Single '}' encountered in format string
>>> '{%s, %s}'%(1,2)
'{1, 2}'
>>> 

Ответ 16

Python 3.6.7 сравнительный:

#!/usr/bin/env python
import timeit

def time_it(fn):
    """
    Measure time of execution of a function
    """
    def wrapper(*args, **kwargs):
        t0 = timeit.default_timer()
        fn(*args, **kwargs)
        t1 = timeit.default_timer()
        print("{0:.10f} seconds".format(t1 - t0))
    return wrapper


@time_it
def new_new_format(s):
    print("new_new_format:", f"{s[0]} {s[1]} {s[2]} {s[3]} {s[4]}")


@time_it
def new_format(s):
    print("new_format:", "{0} {1} {2} {3} {4}".format(*s))


@time_it
def old_format(s):
    print("old_format:", "%s %s %s %s %s" % s)


def main():
    samples = (("uno", "dos", "tres", "cuatro", "cinco"), (1,2,3,4,5), (1.1, 2.1, 3.1, 4.1, 5.1), ("uno", 2, 3.14, "cuatro", 5.5),) 
    for s in samples:
        new_new_format(s)
        new_format(s)
        old_format(s)
        print("-----")


if __name__ == '__main__':
    main()

Выход:

new_new_format: uno dos tres cuatro cinco
0.0000170280 seconds
new_format: uno dos tres cuatro cinco
0.0000046750 seconds
old_format: uno dos tres cuatro cinco
0.0000034820 seconds
-----
new_new_format: 1 2 3 4 5
0.0000043980 seconds
new_format: 1 2 3 4 5
0.0000062590 seconds
old_format: 1 2 3 4 5
0.0000041730 seconds
-----
new_new_format: 1.1 2.1 3.1 4.1 5.1
0.0000092650 seconds
new_format: 1.1 2.1 3.1 4.1 5.1
0.0000055340 seconds
old_format: 1.1 2.1 3.1 4.1 5.1
0.0000052130 seconds
-----
new_new_format: uno 2 3.14 cuatro 5.5
0.0000053380 seconds
new_format: uno 2 3.14 cuatro 5.5
0.0000047570 seconds
old_format: uno 2 3.14 cuatro 5.5
0.0000045320 seconds
-----