it-swarm.com.ru

Форматирование строки 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)
    
1237
NorthIsUp

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

888
Claudiu

То, что оператор по модулю (%) не может сделать, афаик:

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

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

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

который избегает делать форматирование, если регистратор фактически не регистрирует что-то.

132
Wooble

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

98
Colonel Panic

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

55
BrainStorm

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

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

51
rslnx

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

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

28
balu

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

25
lcltj

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

14
David Sanders

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

'{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}}'. Это может быть полезно, когда у вас есть два регулярных выражения, одно из которых используется без формата, но объединение обоих форматируется.

8
Jorge Leitão

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

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

4
Sylvan LE DEUNFF

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

s1='albha'
s2='beta'

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

#output
'albha      beta'
2
Roushan

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 __== '__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
-----
1
Felix Martinez

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

Пример:

>>> '{{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}'
>>> 
1
U9-Forward

Строго видно, что мы действительно далеки от первоначальной темы, но почему бы и нет? 

При использовании модуля gettext для предоставления, например, локализованный графический интерфейс, старые и новые строки стиля - единственный путь; F-строки не могут быть использованы там. ИМХО новый стиль - лучший выбор для этого случая. SO есть вопрос по этому вопросу здесь .

0
jake77