Форматирование строк Python: % vs..формат
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? Например, если мой уровень ведения журнала установлен на высокий, буду ли я по-прежнему получать удар за выполнение следующей операции
%? А если так, то есть есть ли способ избежать этого?
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будет сначала вычислять 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.357107877731Python 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