web-dev-qa-db-pt.com

Formatação de string do Python:% vs. .format

O Python 2.6 introduziu o método str.format() com uma sintaxe ligeiramente diferente do operador % existente. Qual é melhor e para que situações?

  1. O seguinte usa cada método e tem o mesmo resultado, então qual é a diferença?

    #!/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. Além disso, quando ocorre a formatação de strings em Python? Por exemplo, se o meu nível de registro estiver definido como ALTO, ainda serei atingido pela execução da seguinte operação %? E se assim for, há uma maneira de evitar isso?

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

Para responder sua primeira pergunta ... .format parece mais sofisticado de várias maneiras. Uma coisa chata sobre o % é também como ele pode pegar uma variável ou um Tuple. Você acha que o seguinte sempre funcionaria:

"hi there %s" % name

ainda assim, se name for (1, 2, 3), ele lançará uma TypeError. Para garantir que sempre imprime, você precisa fazer

"hi there %s" % (name,)   # supply the single argument as a single-item Tuple

que é apenas feio. .format não tem esses problemas. Também no segundo exemplo que você deu, o exemplo .format é muito mais limpo.

Por que você não usaria isso? 

  • não sabendo disso (eu antes de ler isso)
  • ter que ser compatível com o Python 2.5

Para responder à sua segunda pergunta, a formatação de string acontece ao mesmo tempo que qualquer outra operação - quando a expressão de formatação de string é avaliada. E o Python, não sendo uma linguagem preguiçosa, avalia expressões antes de chamar funções, portanto, no seu exemplo log.debug, a expressão "some debug info: %s"%some_info será avaliada primeiro, por exemplo, "some debug info: roflcopters are active", então essa string será passada para log.debug()

888
Claudiu

Algo que o operador de módulo (%) não pode fazer, afaik:

tu = (12,45,22222,103,6)
print '{0} {2} {1} {2} {3} {2} {4} {2}'.format(*tu)

resultado

12 22222 45 22222 103 22222 6 22222

Muito útil.

Outro ponto: format(), sendo uma função, pode ser usado como um argumento em outras funções: 

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

Resulta em:

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

Supondo que você esteja usando o módulo logging do Python, você pode passar os argumentos de formatação de string como argumentos para o método .debug() em vez de fazer a formatação por conta própria:

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

o que evita a formatação, a menos que o registrador realmente registre alguma coisa.

132
Wooble

A partir do Python 3.6 (2016) você pode usar f-strings para substituir variáveis:

>>> Origin = "London"
>>> destination = "Paris"
>>> f"from {Origin} to {destination}"
'from London to Paris'

Observe o prefixo f". Se você tentar isso no Python 3.5 ou anterior, você obterá uma SyntaxError.

Veja https://docs.python.org/3.6/reference/lexical_analysis.html#f-strings

98
Colonel Panic

PEP 3101 propõe a substituição do operador % pela nova formatação avançada de strings no Python 3, onde seria o padrão.

55
BrainStorm

Mas, por favor, tenha cuidado, só agora descobri um problema ao tentar substituir todo % por .format no código existente: '{}'.format(unicode_string) tentará codificar unicode_string e provavelmente falhará.

Basta olhar para este log de sessão interativo do 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 é apenas uma string (chamada 'byte array' em Python3) e u é uma string Unicode (chamada 'string' em Python3):

; '%s' % s
'\xd0\xb9'
; '%s' % u
u'\u0439'

Quando você der um objeto Unicode como um parâmetro para o operador %, ele produzirá uma string Unicode mesmo que a string original não seja 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)

mas a função .format irá gerar "UnicodeEncodeError":

; u'{}'.format(s)
u'\xd0\xb9'
; u'{}'.format(u)
u'\u0439'

e ele só funcionará com um argumento Unicode se a string original for Unicode.

; '{}'.format(u'i')
'i'

ou se a string de argumento puder ser convertida em uma string (chamada de 'array de bytes')

51
rslnx

Ainda outra vantagem de .format (que não vejo nas respostas): pode levar propriedades de objeto.

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'

Ou como um argumento de palavra-chave:

In [15]: 'x is {a.x}, y is {a.y}'.format(a=a)
Out[15]: 'x is 2, y is 3'

Isso não é possível com %, tanto quanto eu posso dizer.

33
matiasg

Como eu descobri hoje, a maneira antiga de formatação de strings via % não suporta Decimal, o módulo do Python para aritmética decimal de ponto fixo e ponto flutuante, fora da caixa.

Exemplo (usando o 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))

Saída:

0.00000000000000000000000312375239000000009907464850 0,00000000000000000000000312375239000000000000000000

Certamente pode haver soluções alternativas, mas você ainda pode considerar o uso do método format() imediatamente. 

28
balu

% oferece melhor desempenho que format do meu teste.

Código de teste:

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

Resultado:

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

Resultado

> format: 0.5864730989560485
> %: 0.013593495357781649

Parece em Python2, a diferença é pequena enquanto que em Python3, % é muito mais rápido que format.

Obrigado @Chris Cogdon pelo código de exemplo.

25
lcltj

Como uma nota lateral, você não precisa obter um desempenho para usar a nova formatação de estilo com o registro. Você pode passar qualquer objeto para logging.debug, logging.info, etc., que implemente o método mágico __str__. Quando o módulo de log decidiu que deve emitir seu objeto de mensagem (seja ele qual for), ele chama str(message_object) antes de fazer isso. Então você poderia fazer algo assim:

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

Tudo isso está descrito na documentação do Python 3 ( https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles ). No entanto, ele também funcionará com o Python 2.6 ( https://docs.python.org/2.6/library/logging.html#using-arbitrary-objects-as-messages ).

Uma das vantagens de utilizar esta técnica, para além do facto de ser agnóstico ao estilo de formatação, é que permite valores preguiçosos, e. a função expensive_func acima. Isso fornece uma alternativa mais elegante aos conselhos dados na documentação do Python aqui: https://docs.python.org/2.6/library/logging.html#optimization .

14
David Sanders

Uma situação em que % pode ajudar é quando você está formatando expressões regex. Por exemplo, 

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

levanta IndexError. Nesta situação, você pode usar:

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

Isso evita gravar o regex como '{type_names} [a-z]{{2}}'. Isso pode ser útil quando você tem dois regexes, onde um é usado sozinho sem formato, mas a concatenação de ambos é formatada.

8
Jorge Leitão

Se o seu literal python> = 3.6, F-string formatado for o seu novo amigo.

É mais simples, limpo e melhor desempenho.

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

Gostaria de acrescentar que, desde a versão 3.6, podemos usar fstrings como o seguinte

foo = "john"
bar = "smith"
print(f"My name is {foo} {bar}")

Que dão

Meu nome é joão smith

tudo é convertido em seqüências de caracteres

mylist = ["foo", "bar"]
print(f"mylist = {mylist}")

Resultado:

mylist = ['foo', 'bar']

você pode passar função, como em outros formatos método

print(f'Hello, here is the date : {time.strftime("%d/%m/%Y")}')

Dando por exemplo

Olá, aqui é a data: 16/04/2018

4
Sylvan LE DEUNFF

Para a versão do python> = 3.6 (consulte PEP 498 )

s1='albha'
s2='beta'

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

#output
'albha      beta'
2
Roushan

Comparativo do 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()

Saída:

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

Mas uma coisa é que também se você tiver chaves aninhadas, não funcionará para o formato, mas % funcionará.

Exemplo:

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

Visto estritamente, estamos realmente distantes do tópico original, mas por que não: 

Ao usar o módulo gettext para fornecer, e. uma GUI localizada, strings de estilo antigas e novas são o único caminho; F-strings não podem ser usadas lá. IMHO o novo estilo é a melhor escolha para este caso. Existe uma questão SO neste aqui .

0
jake77