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


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? Например, если мой уровень ведения журнала установлен на HIGH, я все равно сделаю удар для выполнения следующих %операция? И если да, есть ли способ избежать этого?

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

1167


источник


Ответы:


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


843



То, что оператор 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

280



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

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

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


124



Начиная с версии 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


82



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


54



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

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


51



Еще одно преимущество .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'

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


33



As I discovered today, the old way of formatting strings via % doesn't support Decimal, Python's module for decimal fixed point and floating point arithmetic, out of the box.

Example (using 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))

Output:

0.00000000000000000000000312375239000000009907464850 0.00000000000000000000000312375239000000000000000000

There surely might be work-arounds but you still might consider using the format() method right away.


27



% gives better performance than format from my test.

Test code:

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')")

Result:

> 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')"))

Result

> format: 0.5864730989560485
> %: 0.013593495357781649

It looks in Python2, the difference is small whereas in Python3, % is much faster than format.

Thanks @Chris Cogdon for the sample code.


19



As a side note, you don't have to take a performance hit to use new style formatting with logging. You can pass any object to logging.debug, logging.info, etc. that implements the __str__ magic method. When the logging module has decided that it must emit your message object (whatever it is), it calls str(message_object) before doing so. So you could do something like this:

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

This is all described in the Python 3 documentation (https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles). However, it will work with Python 2.6 as well (https://docs.python.org/2.6/library/logging.html#using-arbitrary-objects-as-messages).

One of the advantages of using this technique, other than the fact that it's formatting-style agnostic, is that it allows for lazy values e.g. the function expensive_func above. This provides a more elegant alternative to the advice being given in the Python docs here: https://docs.python.org/2.6/library/logging.html#optimization.


14



One situation where % may help is when you are formatting regex expressions. For example,

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

raises IndexError. In this situation, you can use:

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

This avoids writing the regex as '{type_names} [a-z]{{2}}'. This can be useful when you have two regexes, where one is used alone without format, but the concatenation of both is formatted.


8