Formatowanie łańcuchów Pythona: % vs..format

Python 2.6 wprowadził str.format() metoda o nieco innej składni niż istniejący operator %. Co jest lepsze i w jakich sytuacjach?

  1. Poniższa metoda wykorzystuje każdą metodę i ma ten sam wynik, więc jaka jest różnica?

    #!/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. Ponadto kiedy formatowanie łańcuchów występuje w Pythonie? Na przykład, jeśli mój poziom logowania jest ustawiony na wysoki, czy nadal otrzymam trafienie za wykonanie następującej operacji %? A jeśli tak, to jest jest sposób, aby tego uniknąć?

    log.debug("some debug info: %s" % some_info)
    
Author: the Tin Man, 2011-02-22

15 answers

Aby odpowiedzieć na twoje pierwsze pytanie... Wydaje się bardziej wyrafinowany pod wieloma względami. Irytującą rzeczą w % jest również to, jak może przyjmować zmienną lub krotkę. Można by pomyśleć, że zawsze będzie działać:

"hi there %s" % name

Jednak, Jeśli name stanie się (1, 2, 3), rzuci TypeError. Aby zagwarantować, że zawsze drukuje, musisz wykonać

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

Który jest po prostu brzydki. Nie ma takich problemów. Również w drugim przykładzie, który podałeś, przykład .format jest znacznie czystszy Szukam.

Dlaczego go nie użyłeś?

  • nie wiedząc o tym (ja przed przeczytaniem tego)
  • musi być kompatybilny z Pythonem 2.5

Aby odpowiedzieć na drugie pytanie, formatowanie łańcuchów odbywa się w tym samym czasie, co każda inna operacja - gdy wyrażenie formatowania łańcuchów jest obliczane. Python, nie będąc leniwym językiem, ocenia wyrażenia przed wywołaniem funkcji, więc w twoim przykładzie log.debug, wyrażenie "some debug info: %s"%some_info najpierw oceni do, np. "some debug info: roflcopters are active", wtedy ten łańcuch zostanie przekazany do log.debug().

 857
Author: Claudiu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-01 12:12:38

Coś, czego operator modulo ( % ) nie może zrobić, afaik:

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

Wynik

12 22222 45 22222 103 22222 6 22222
Bardzo przydatne.

Inny punkt: format(), będący funkcją, może być używany jako argument w innych funkcjach:

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

Wyniki w:

['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
 282
Author: eyquem,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-14 19:24:14

Zakładając, że używasz modułu Pythona logging, możesz przekazać argumenty formatowania łańcuchów jako argumenty do metody .debug(), a nie samemu formatować:

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

Który unika formatowania, chyba że rejestrator rzeczywiście coś rejestruje.

 126
Author: Wooble,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-14 19:28:08

Od wersji Python 3.6 (2016) można używać F-stringi do zastąpienia zmiennych:

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

Zwróć uwagę na przedrostek f". Jeśli spróbujesz tego w Pythonie 3.5 lub wcześniejszym, otrzymasz SyntaxError.

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

 90
Author: Colonel Panic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-13 16:52:45

PEP 3101 proponuje zastąpienie operatora % nowym, zaawansowanym formatowaniem łańcuchów w Pythonie 3, gdzie byłoby to domyślne.

 54
Author: BrainStorm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-13 15:51:53

Ale proszę uważać, właśnie teraz odkryłem jeden problem podczas próby zastąpienia wszystkich % przez .format w istniejącym kodzie: '{}'.format(unicode_string) spróbuje zakodować unicode_string i prawdopodobnie się nie powiedzie.

Wystarczy spojrzeć na ten interaktywny dziennik sesji Pythona:

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 jest tylko ciągiem znaków (nazywanym 'tablicą bajtów' w Python3), a {[9] } jest ciągiem znaków Unicode (nazywanym 'łańcuchem' w Python3):

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

Gdy podasz obiekt Unicode jako parametr operatorowi % to tworzy ciąg Unicode, nawet jeśli oryginalny ciąg nie był 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)

Ale .format funkcja wywoła "UnicodeEncodeError":

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

I będzie działać z argumentem Unicode tylko wtedy, gdy oryginalny ciąg znaków był Unicode.

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

Or if argument string can be converted to a string (so called 'byte array')

 51
Author: rslnx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-14 19:27:33

Kolejna zaleta .format (której nie widzę w odpowiedziach): może przyjmować właściwości obiektu.

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'

Lub, jako argument słowa kluczowego:

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

To nie jest możliwe z % z tego co wiem.

 33
Author: matiasg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-19 16:42:39

Jak odkryłem dzisiaj, stary sposób formatowania łańcuchów za pomocą % nie obsługuje Decimal, modułu Pythona do dziesiętnej arytmetyki punktowej i zmiennoprzecinkowej, po wyjęciu z pudełka.

Przykład (używając Pythona 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))

Wyjście:

0.00000000000000000000000312375239000000009907464850 0.00000000000000000000000312375239000000000000000000

Z pewnością może być praca wokół, ale nadal można rozważyć użycie metody format() od razu.

 27
Author: balu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-14 19:24:41

% daje lepsze wyniki niż format z mojego testu.

Kod testu:

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

Wynik:

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

Wynik

> format: 0.5864730989560485
> %: 0.013593495357781649

Wygląda w Python2, różnica jest niewielka, podczas gdy w Python3, % jest znacznie szybszy niż format.

Dzięki @ Chris Cogdon za przykładowy kod.

 23
Author: lcltj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-26 18:11:52

Na marginesie, nie musisz brać pod uwagę wydajności, aby używać nowego formatowania stylu z logowaniem. Możesz przekazać dowolny obiekt do logging.debug, logging.info, itd. to implementuje magiczną metodę __str__. Gdy moduł logowania zdecyduje, że musi emitować obiekt wiadomości (cokolwiek to jest), wywoła str(message_object) zanim to zrobi. Więc mógłbyś zrobić coś takiego:

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

To wszystko jest opisane w dokumentacji Pythona 3 ( https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles ). jednak będzie działać również z Pythonem 2.6(https://docs.python.org/2.6/library/logging.html#using-arbitrary-objects-as-messages).

Jedną z zalet stosowania tej techniki, poza faktem, że jest agnostyczna w stylu formatowania, jest to, że pozwala na leniwe wartości, np. funkcja expensive_func powyżej. Stanowi to bardziej elegancką alternatywę dla porad udzielanych w Pythonie dokumenty tutaj: https://docs.python.org/2.6/library/logging.html#optimization .

 14
Author: David Sanders,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-21 19:55:49

Jedną z sytuacji, w której % może pomóc, jest formatowanie wyrażeń regex. Na przykład,

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

Podnosi IndexError. W tej sytuacji można użyć:

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

Pozwala to uniknąć zapisywania regex jako '{type_names} [a-z]{{2}}'. Może to być przydatne, gdy masz dwa wyrażenia regularne, gdzie jeden jest używany samodzielnie bez formatu, ale połączenie obu jest sformatowane.

 8
Author: Jorge Leitão,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-09 20:41:40

Dla wersji Pythona > = 3.6 (zobacz PEP 498)

s1='albha'
s2='beta'

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

#output
'albha      beta'
 2
Author: Roushan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-15 16:52:14

Jeśli twój python > = 3.6, F-string sformatowany literal jest twoim nowym przyjacielem.

To prostsze, czyste i lepsze działanie.
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)
 2
Author: zhengcao,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-25 01:05:27

Dodam, że od wersji 3.6 możemy używać fstringów jak poniżej

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

Które dają

Nazywam się john smith

Wszystko jest konwertowane na ciągi

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

Wynik:

Mylist = ['foo', 'bar']

Można przekazać funkcję, podobnie jak w innych formatach metoda

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

Podając na przykład

Witam, oto Data : 16/04/2018

 1
Author: Sylvan LE DEUNFF,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-16 14:51:11

Ale jedno jest takie, że jeśli masz zagnieżdżone nawiasy klamrowe, nie będzie działać dla formatu, ale % będzie działać.

Przykład:

>>> '{{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}'
>>> 
 0
Author: U9-Forward,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-25 01:08:57