Je suis tombé dessus par hasard, je ne sais plus trop comment, et j’ai réalisé que j’avais changé d’avis sur l’article. Je ne recommandais plus du tout ce que j’y mettais.
Il fallait le mettre à jour, mais un si petit article, je serais probablement passé à côté dans le futur. En effet, je ne veux pas updater 600 articles, donc je fais ceux qui me paraissent prioritaires.
Du coup je l’ai fait tout de suite, histoire de ne pas oublier. En prime, il m’a donné envie d’écrire un article sur le formatage des chaines en Python en général, qui devrait sortir dans la semaine.
Cette réécriture comprend :
On se chauffe les yeux, et on va le lire !
Dans le cadre de notre parenthèse lubrique, j’ai eu envie de ruiner l’enfance de la nouvelle génération, la règle 34 ayant suffisamment attaqué la mienne :
Je suis quand même épaté de tout cet effort déployé par les monstres de tout poil (mouarf) juste pour insérer leur tentacules dans des orifices. Mais bon, quand ce sont des bites, au Japon, elles doivent être floutées, du coup ceci explique cela. Et puis ce qu’on trouve sur MyLittlePoney n’est pas mieux.
]]>Une notation littérale pour les sets:
>>> {1, 2} == set((1, 2))
True
Une syntaxe pour les dictionnaires en intention:
>>> d = {chr(x): x for x in range(65, 91)}
>>> d
{'A': 65, 'C': 67, 'B': 66, 'E': 69, 'D': 68, 'G': 71, 'F': 70, 'I': 73, 'H': 72, 'K': 75, 'J': 74, 'M': 77, 'L': 76, 'O': 79, 'N': 78, 'Q': 81, 'P': 80, 'S': 83, 'R': 82, 'U': 85, 'T': 84, 'W': 87, 'V': 86, 'Y': 89, 'X': 88, 'Z': 90}
Imbriquer with
:
Avant il fallait utiliser nested() ou imbriquer à la main
with open('fichiera') as a:
with open('fichiera') as b:
# faire un truc
Maintenant on peut faire:
with open('fichiera') as a, open('fichiera') as b:
# faire un truc
Rien à voir, mais toujours sympa. timedelta
a maintenant une méthode total_seconds()
qui retourne la valeur de la durée en seconde. En effet, l’attribut seconds
ne retourne que ce qui reste en seconde une fois qu’on a retiré les jours:
>>> from datetime import timedelta
>>> delta = timedelta(days=1, seconds=1)
>>> delta.seconds
1
>>> delta.total_seconds()
86401.0
Notez qu’il n’y a toujours ni attribut minutes, ni heures.
Le module unittest
gagne une pléthore d’améliorations, et notamment:
L’utilisation de assertRaises
comme context manager:
with self.assertRaises(KeyError):
{}['foo']
Et un bon gros nombres de méthodes:
assertIsNone()
/ assertIsNotNone()
, assertIs()
/ assertIsNot()
, assertIsInstance()
/ assertNotIsInstance()
, assertGreater()
/ assertGreaterEqual()
/ assertLess()
/ assertLessEqual()
, assertRegexpMatches()
/ assertNotRegexpMatches()
, assertRaisesRegexp()
,
assertIn()
/ assertNotIn()
, assertDictContainsSubset()
, assertAlmostEqual()
/ assertNotAlmostEqual()
.
Enfin format()
commence à devenir une alternative valable à %
car il propose maintenant des marqueurs sans noter d’index:
>>> "{}, puis {} et finalement {}".format(*range(3))
'0, puis 1 et finalement 2'
Et il ajoute le séparateur des milliers au mini-langage de formatage, mais pour la virgule uniquement. Par exemple, si avoir un nombre de 15 caractères minimum formater en tant que float, avec deux chiffres après la virgules, et donc les milliers sont groupés à l’américaine:
>>> '{:15,.2f}'.format(54321)
' 54,321.00'
]]>Quand on voulait travailler sur les valeurs d’un dictionnaire en Python, on avait deux choix:
dict.values()
et récupérer une liste entière. Créant une liste entière en mémoire.dict.itervalues()
, et récupérer un générateur. Mais qui ne peut être lu qu’une fois.Les vues sont une solution intermédiaire: ce sont des objets qui prennent peu de mémoire, mais qui peuvent être lus plusieurs fois.
Exemple:
>>> scores = {'foo': 1, 'bar': 0}
>>> val = scores.viewvalues()
>>> print val
dict_values([1, 0])
>>> 1 in val
True
>>> [x * 2 for x in val]
[2, 0]
Contrairement à une liste, les vues issues d’un dictionnaire ne supportent pas le slicing ou l’assignation et il n’y a aucune garantie d’ordre des éléments. De plus, elles ne peuvent être modifiées.
Bref, une vue ne contient rien, c’est juste un objet qui, quand on accède à son contenu, va le chercher dans le dictionnaire et vous le retourne. C’est ce qu’on appelle un objet proxy: il vous donne l’illusion d’accéder directement aux données pour vous faciliter la vie, généralement en vous les présentant sous une forme différente: ici un itérable.
On peut récupérer des vues pour les valeurs, mais également pour les clés et les couples clés / valeurs. Ces deux types de vues se comportent en plus comme des sets:
>>> scores.viewitems()
dict_items([('foo', 1), ('bar', 0)])
>>> scores.viewkeys() | [3,]
set([3, 'foo', 'bar'])
Puisqu’il est rare d’avoir besoin d’une vraie liste, et comme les vues sont une très bonne alternative aux générateurs, dict.values
et consorts retournent des vues en Python 3.
Maintenant vous allez me dire “Mais si les vues sont une si bonne alternative aux générateurs, pourquoi on ne remplace pas tous les générateurs par des vues ?”.
Tout simplement parce que ce n’est pas possible. Un générateur est un mécanisme standard qui permet de produire des valeurs une par une. N’importe qui peut créer un générateur, car c’est un concept portable d’un problème à un autre. On peut l’appliquer à de nombreuses choses: algorithme, flux de données, fichier, etc.
Une vue n’est qu’un proxy qui permet de voir une structure de données sous une autre forme. Il faut coder une vue par type de structure de données, car la vue va chercher les données dans cette structure quand on lui demande. Le code est donc différent à chaque fois.
Python ne permet pas de créer soi-même des vues, mais créer un proxy, c’est à dire un objet qui retourne les valeurs d’un autre objet quand on l’interroge, peut se faire à la main dans tout langage de programmation. Ainsi vous pourriez créer un proxy qui ressemble a une vue des clés d’un dico très simplement:
class keyview(object):
def __init__(self, d):
self.d = d
def __iter__(self):
return self.d.iterkeys()
>>> view = keyview(scores)
>>> for x in view:
... print x
...
foo
bar
>>> list(view)
['foo', 'bar']
>>>
L’implémentation réelle de Python (en C…) ne fait pas vraiment grand chose de plus, juste un travail d’optimisation pour être plus rapide.
Les memory views suivent le même principe, mais appliqué à toute structure de données qui supporte le buffer protocole (un certain nombre de méthodes avec un nom et un comportement défini par ce protocole) comme celles trouvées dans le module struct
ou array
. La structure de données la plus connue qui suit le buffer protocole est la chaîne de caractères.
>>> s = 'Sam & Max eat the road with a Github fork'
>>> ms = memoryview(s)
>>> ms[-1]
'k'
>>> ms[:9]
>>> ''.join(ms[:9])
'Sam & Max'
Le principal intérêt de la memory view appliquée aux strings, c’est que tout slicing retourne une nouvelle memory view. On peut donc travailler sur des parties de la chaînes sans créer une nouvelle chaîne en mémoire.
En revanche, les chaînes unicodes ne sont pas supportées. Il vous faudra jouer avec encode()
et decode()
.