Форматирование строк Python: % vs..формат



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)


605   15  

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будет сначала вычислять to, например "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-струны для замены переменных:

>>> 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 является строкой Unicode (называется "строка" в 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'

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

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

Наверняка могут быть обходные пути, но вы все равно можете рассмотреть возможность использования метода 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, и т.д. это реализует магический метод __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'}
Это позволяет избежать записи регулярного выражения как '{type_names} [a-z]{{2}}'. Это может быть полезно, когда у вас есть два регулярных выражения, где одно используется отдельно без формата, но конкатенация обоих отформатирована.

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

s1='albha'
s2='beta'

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

#output
'albha      beta'

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

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

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

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

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

Comments

    Ничего не найдено.