Python 2.6 a introduit la méthode str.format()
avec une syntaxe légèrement différente de l'opérateur %
existant. Quel est le meilleur et pour quelles situations?
Ce qui suit utilise chaque méthode et a le même résultat, alors quelle est la différence?
#!/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!"
De plus, quand le formatage des chaînes a-t-il lieu en Python? Par exemple, si mon niveau de journalisation est défini sur ÉLEVÉ, est-ce que je vais continuer à utiliser pour exécuter l'opération %
suivante? Et si oui, y a-t-il un moyen d'éviter cela?
log.debug("some debug info: %s" % some_info)
Pour répondre à votre première question ... .format
semble plus sophistiqué à bien des égards. Une chose ennuyeuse à propos de %
est aussi comment il peut prendre une variable ou un tuple. Vous penseriez que ce qui suit fonctionnerait toujours:
"hi there %s" % name
cependant, si name
se trouve être (1, 2, 3)
, il lancera une TypeError
. Pour garantir qu’il imprime toujours, vous devez faire
"hi there %s" % (name,) # supply the single argument as a single-item Tuple
ce qui est juste moche. .format
n'a pas ces problèmes. Également dans le deuxième exemple que vous avez donné, l'exemple .format
est beaucoup plus propre.
Pourquoi ne l'utiliseriez-vous pas?
Pour répondre à votre deuxième question, le formatage de chaîne a lieu en même temps que toute autre opération, lorsque l'expression de formatage de chaîne est évaluée. Et Python, n'étant pas un langage paresseux, évalue les expressions avant d'appeler des fonctions. Ainsi, dans votre exemple log.debug
, l'expression "some debug info: %s"%some_info
sera d'abord évaluée, par exemple. "some debug info: roflcopters are active"
, cette chaîne sera transmise à log.debug()
.
Quelque chose que l'opérateur de modulo (%) ne peut pas faire, autant que je sache:
tu = (12,45,22222,103,6)
print '{0} {2} {1} {2} {3} {2} {4} {2}'.format(*tu)
résultat
12 22222 45 22222 103 22222 6 22222
Très utile.
Un autre point: format()
, étant une fonction, peut être utilisé comme argument dans d'autres fonctions:
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))
Résulte en:
['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
En supposant que vous utilisiez le module logging
de Python, vous pouvez transmettre les arguments de formatage de chaîne comme arguments à la méthode .debug()
plutôt que de procéder vous-même au formatage:
log.debug("some debug info: %s", some_info)
ce qui évite de faire le formatage à moins que l'enregistreur enregistre quelque chose.
À partir de Python 3.6 (2016), vous pouvez utiliser f-strings pour substituer des variables:
>>> Origin = "London"
>>> destination = "Paris"
>>> f"from {Origin} to {destination}"
'from London to Paris'
Notez le préfixe f"
. Si vous essayez ceci avec Python 3.5 ou une version antérieure, vous obtiendrez une SyntaxError
.
Voir https://docs.python.org/3.6/reference/lexical_analysis.html#f-strings
PEP 3101 propose le remplacement de l'opérateur %
par le nouveau formatage de chaîne avancé en Python 3, où il s'agirait du comportement par défaut.
Mais s'il vous plaît soyez prudent, je viens de découvrir un problème en essayant de remplacer tout %
par .format
dans le code existant:'{}'.format(unicode_string)
essaiera de coder unicode_string et échouera probablement.
Il suffit de regarder ce journal de session interactif 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
est juste une chaîne (appelée 'tableau d'octets' en Python3) et u
est une chaîne Unicode (appelée 'chaîne' en Python3):
; '%s' % s
'\xd0\xb9'
; '%s' % u
u'\u0439'
Lorsque vous donnez un objet Unicode en tant que paramètre à l'opérateur %
, il produira une chaîne Unicode, même si la chaîne d'origine n'était pas 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)
mais la fonction .format
lèvera "UnicodeEncodeError":
; u'{}'.format(s)
u'\xd0\xb9'
; u'{}'.format(u)
u'\u0439'
et cela fonctionnera avec un argument Unicode très bien seulement si la chaîne originale était Unicode.
; '{}'.format(u'i')
'i'
ou si la chaîne d'argument peut être convertie en chaîne (appelée "tableau d'octets")
Encore un autre avantage de .format
(que je ne vois pas dans les réponses): cela peut prendre des propriétés d'objet.
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, en tant qu'argument de mot clé:
In [15]: 'x is {a.x}, y is {a.y}'.format(a=a)
Out[15]: 'x is 2, y is 3'
Autant que je sache, ceci n’est pas possible avec %
.
Comme je l’ai découvert aujourd’hui, l’ancienne méthode de formatage des chaînes via %
ne prend pas en charge Decimal
, le module Python pour l’arithmétique décimale à virgule fixe et à virgule flottante.
Exemple (utilisant 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))
Sortie:
0.0000000000000000000000300007533975000000009907464850 0.00000000000000000000003000312375239000000000000000000
Il y a sûrement des solutions de rechange, mais vous pouvez quand même envisager d'utiliser la méthode format()
tout de suite.
%
donne de meilleures performances que format
de mon test.
Code de test:
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')")
Résultat:
> 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')"))
Résultat
> format: 0.5864730989560485
> %: 0.013593495357781649
Cela ressemble à Python2, la différence est petite, alors que dans Python3, %
est beaucoup plus rapide que format
.
Merci @Chris Cogdon pour l'exemple de code.
En guise de remarque, il n'est pas nécessaire de perdre des performances pour utiliser un nouveau formatage de style avec la journalisation. Vous pouvez transmettre n'importe quel objet à logging.debug
, logging.info
, etc. implémentant la méthode magique __str__
. Lorsque le module de journalisation a décidé qu'il doit émettre votre objet de message (quel qu'il soit), il appelle str(message_object)
avant de le faire. Donc, vous pourriez faire quelque chose comme ça:
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))
Tout cela est décrit dans la documentation de Python 3 ( https://docs.python.org/3/howto/logging-cookbook.html#formatting-styles ). Cependant, cela fonctionnera également avec Python 2.6 ( https://docs.python.org/2.6/library/logging.html#using-arbitrary-objects-as-messages ).
L’un des avantages de l’utilisation de cette technique, outre le fait qu’elle est agnostique selon le style de formatage, est qu’elle permet des valeurs paresseuses, par exemple. la fonction expensive_func
ci-dessus. Ceci fournit une alternative plus élégante aux conseils donnés dans la documentation Python ici: https://docs.python.org/2.6/library/logging.html#optimization .
%
peut vous aider lorsque vous formatez des expressions regex. Par exemple,
'{type_names} [a-z]{2}'.format(type_names='triangle|square')
soulève IndexError
. Dans cette situation, vous pouvez utiliser:
'%(type_names)s [a-z]{2}' % {'type_names': 'triangle|square'}
Cela évite d’écrire le regex avec '{type_names} [a-z]{{2}}'
. Cela peut être utile lorsque vous avez deux expressions rationnelles, où l'une est utilisée seule sans format, mais la concaténation des deux est formatée.
Si votre python> = 3.6, le littéral au format F-string est votre nouvel ami.
C'est plus simple, plus propre et de meilleures performances.
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)
J'ajouterais que depuis la version 3.6, on peut utiliser des fstrings comme celui-ci
foo = "john"
bar = "smith"
print(f"My name is {foo} {bar}")
Qui donnent
Je m'appelle john smith
Tout est converti en chaînes
mylist = ["foo", "bar"]
print(f"mylist = {mylist}")
Résultat:
mylist = ['foo', 'bar']
vous pouvez passer la fonction, comme dans la méthode des autres formats
print(f'Hello, here is the date : {time.strftime("%d/%m/%Y")}')
Donner par exemple
Bonjour, voici la date: 16/04/2018
Pour la version python> = 3.6 (voir PEP 498 )
s1='albha'
s2='beta'
f'{s1}{s2:>10}'
#output
'albha beta'
Comparatif 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()
Sortie:
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
-----
Mais une chose est que même si vous avez des accolades imbriquées, cela ne fonctionnera pas pour le format, mais %
fonctionnera.
Exemple:
>>> '{{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}'
>>>
Strictement vu, nous nous éloignons vraiment du sujet initial, mais alors pourquoi pas:
Lorsque vous utilisez le module gettext pour fournir, par exemple, une interface graphique localisée, des chaînes de style anciennes et nouvelles sont le seul moyen; Les f-strings ne peuvent pas être utilisés ici. IMHO le nouveau style sont le meilleur choix pour ce cas. Il y a une SO question à ce sujet ici .