В Python строку форматирования: % против.формат

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

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

    #!/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!"
    
  2. кроме того, когда происходит форматирование строк в Python? Например, если мой уровень ведения журнала установлен на высокий, я все равно получу удар для выполнения после % операции? И если да, то есть ли способ избежать этого?

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

15 ответов


чтобы ответить на ваш первый вопрос... .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().


то, что оператор по модулю ( % ) не может сделать, 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

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

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

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


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

>>> 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


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


но, пожалуйста, будьте осторожны, только что я обнаружил одну проблему при попытке заменить все % С .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 является строкой Юникода (называемой "строкой" в 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 (чего я не вижу в ответах): он может принимать свойства объекта.

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'

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


как я обнаружил сегодня, старый способ форматирования строки через % не поддерживает 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() способ сразу.


% дает лучшую производительность, чем 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 за образец кода.


в качестве примечания, вам не нужно принимать удар производительности, чтобы использовать новое форматирование стиля с журналом. Вы можете передать любой объект в logging.debug, logging.info, etc. это реализует __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.


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

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

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

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

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


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

s1='albha'
s2='beta'

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

#output
'albha      beta'

Если ваш 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)

Я бы добавил, что начиная с версии 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


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

пример:

>>> '{{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}'
>>>