Structures de Données
Python inclut de nombreuses normes de programmation des structures de données, telles que list
, tuple
, dict
, et set
, dans le cadre de ses types natifs. Beaucoup d’application ne requièrent pas d’autres structures, mais quand elles le font, la bibliothèque standard fournit des versions puissantes et bien testées, prêtes à être utilisées.
Le module enum
fournit une implémentation du type enumeration qui a des capacités d’itération et de comparaison. Il peut être utilisé pour créer des symboles bien définis pour les valeurs, à la place des chaînes ou entiers littéraux.
Le module collections
inclus les implémentations de nombreuses structures de données qui étendent celles trouvées dans d’autres modules. Par exemple, Deque
est une file d’attente double, qui permet l’addition ou la suppression d’éléments à chaque extrémité. defaultdict
est un dictionnaire qui répond avec une valeur par défaut si une clé est manquante, alors que OrderedDict
se souvient de la séquence dans laquelle les éléments sont ajoutés. namedtuple
étend le tuple normal pour donner à chaque item membre un nom d’attribut en supplément de l’index numérique.
Pour de grandes quantités de données, un array
peut utiliser plus efficacement la mémoire qu’une liste. Puisqu’un array
est limité à un simple type de donnée, il peut utiliser une représentation de mémoire plus compacte qu’une list
à usage général. En même temps, les instances array
peuvent être manipulées en utilisant beaucoup des mêmes méthodes qu’une list
, ainsi il peut être possible de remplacer une list
avec un array
sans faire beaucoup de changements.
Le tri d’items dans une séquence est un aspect fondamental de la manipulation de données. La list
de Python inclus une méthode sort()
, mais parfois il est plus efficient de maintenir une liste dans un ordre trié, sans la trier à nouveau, à chaque fois que son contenu a changé.
Les fonctions dans heapq
modifient le contenu d’une liste tout en préservant l’ordre trié de la liste avec une faible surcharge.
Une autre option pour construire des listes triés ou des tableaux est bisect
. Il utilise une recherche binaire pour trouver le point d’insertion des nouveaux items, et est une alternative au tri répété d’une liste qui change fréquemment.
Bien que la list
native peut simuler une queue, en utilisant les méthodes insert()
et pop()
, ceci n’est pas sûr. Pour avoir de vraies communications ordonnées entre les threads, utilisez le module queue
. multiprocessing
inclus une version de Queue
qui fonctionne entre processus, rendant plus facile la conversion d’un programme multithread pour utiliser des processus à la place.
struct
est utile pour décoder les données d’une autre application, qui viennent peut être d’un fichier binaire ou d’un flux de données, dans les types natifs de Python pour une manipulation plus facile.
Ce chapitre couvre deux modules relatifs à la gestion de la mémoire. Pour des structures de données hautement interconnectées, telles que des graphiques et des arbres, utilisez weakref
pour maintenir des références tout en permettant que le récupérateur de mémoire nettoie les objets après qu’ils ne soient plus nécessaires. Utilisez les fonctions dans copy
pour dupliquer des structures de données et leurs contenus, incluant les copies récursives avec deepcopy()
.
Déboguer des structures de données peut consommer du temps, spécialement lorsque vous parcourez les sorties imprimées de grandes séquences ou de dictionnaires. Utilisez pprint
pour créer des représentations faciles à lire qui peuvent être imprimées dans la console ou écrites dans un fichier journal pour déboguer facilement.
Enfin, si les types disponibles ne répondent pas aux exigences, sous-classez l’un des types natifs et personnalisez-le, ou créez un nouveau type de conteneur en utilisant l’une des classes de base abstraites définies dans les collections
en tant que base de départ.
- enum - Type d’Énumération
- collections - Types de Données de Conteneur
- array - Séquence de Données de Type Fixé
- heapq - Algorithme de Tri Heap
- bisect - Maintenir les Listes dans un Ordre Trié
- queue - Implémentation FIFO Thread-Safe
- struct - Structures de Données Binaires
- weakref - Références Impermanentes aux Objets
- copy - Objets Dupliqués
- pprint - Structures de Données Pretty-Print
enum - Type d’Énumération
Le module enum
définit un type d’énumération avec des capacités d’itération et de comparaison. Il peut être utilisé pour créer des symboles bien définis pour des valeurs, à la place des chaînes ou entiers littéraux.
Créer des Énumérations
Une nouvelle énumération est définie par l’utilisation de la syntaxe class
en sous-classant Enum
et en ajoutant des attributs de classe décrivant les valeurs.
# enum_create.py
import enum
class BugStatus(enum.Enum):
new = 7
incomplete = 6
invalid = 5
wont_fix = 4
in_progress = 3
fix_committed = 2
fix_released = 1
print('\nMember name: {}'.format(BugStatus.wont_fix.name))
print('Member value: {}'.format(BugStatus.wont_fix.value))
Les membres de Enum
sont convertis en instances à mesure que la classe est analysée. Chaque instance a une propriété name
correspondant au nom de membre et une propriété value
correspondante à la valeur assignée au nom dans la définition de classe.
$ python3 enum_create.py
Member name: wont_fix
Member value: 4
Itération
Itérer sur l’énumération class
produit des membres individuels d’énumération.
# enum_iterate.py
import enum
class BugStatus(enum.Enum):
new = 7
incomplete = 6
invalid = 5
wont_fix = 4
in_progress = 3
fix_committed = 2
fix_released = 1
for status in BugStatus:
print('{:15} = {}'.format(status.name, status.value))
Les membres sont produits dans l’ordre dans lequel ils sont déclarés dans la définition de classe.
$ python3 enum_iterate.py
new = 7
incomplete = 6
invalid = 5
wont_fix = 4
in_progress = 3
fix_committed = 2
fix_released = 1
Comparer les Énumérations
Parce que les membres d’énumération ne sont pas ordonnés, ils supportent la comparaison seulement par identité et égalité.
# enum_comparison.py
import enum
class BugStatus(enum.Enum):
new = 7
incomplete = 6
invalid = 5
wont_fix = 4
in_progress = 3
fix_committed = 2
fix_released = 1
actual_state = BugStatus.wont_fix
desired_state = BugStatus.fix_released
print('Equality:',
actual_state == desired_state,
actual_state == BugStatus.wont_fix)
print('Identity:',
actual_state is desired_state,
actual_state is BugStatus.wont_fix)
print('Ordered by value:')
try:
print('\n'.join(' ' + s.name for s in sorted(BugStatus)))
except TypeError as err:
print(' Cannot sort: {}'.format(err))
Les opérateurs de comparaison plus-grand-que et plus-petit-que soulèvent des erreurs d’exceptions TypeErrors
.
$ python3 enum_comparison.py
Equality: False True
Identity: False True
Ordered by value:
Cannot sort: '<' not supported between instances of 'BugStatus
' and 'BugStatus'
Utilisez la classe IntEnum
pour les énumérations dans laquelle les membres doivent se comporter comme des nombres - par exemple, pour supporter les comparaisons.
# enum_intenum.py
import enum
class BugStatus(enum.IntEnum):
new = 7
incomplete = 6
invalid = 5
wont_fix = 4
in_progress = 3
fix_committed = 2
fix_released = 1
print('Ordered by value:')
print('\n'.join(' ' + s.name for s in sorted(BugStatus)))
$ python3 enum_intenum.py
Ordered by value:
fix_released
fix_committed
in_progress
wont_fix
invalid
incomplete
new
Valeurs d’Énumération Uniques
Les membres d’énumération avec les mêmes valeurs sont suivis comme des références d’alias du même objet membre. Les alias n’entraînent pas la présence de valeurs répétées dans l’itérateur pour Enum
.
# enum_aliases.py
import enum
class BugStatus(enum.Enum):
new = 7
incomplete = 6
invalid = 5
wont_fix = 4
in_progress = 3
fix_committed = 2
fix_released = 1
by_design = 4
closed = 1
for status in BugStatus:
print('{:15} = {}'.format(status.name, status.value))
print('\nSame: by_design is wont_fix: ',
BugStatus.by_design is BugStatus.wont_fix)
print('Same: closed is fix_released: ',
BugStatus.closed is BugStatus.fix_released)
Parce que by_design
et closed
sont des alias pour d’autres membres, ils n’apparaissent pas séparément dans la sortie lors de l’itération de Enum
. Le nom canonique pour un membre est le premier nom attaché à la valeur.
$ python3 enum_aliases.py
new = 7
incomplete = 6
invalid = 5
wont_fix = 4
in_progress = 3
fix_committed = 2
fix_released = 1
Same: by_design is wont_fix: True
Same: closed is fix_released: True
Pour que tous les membres aient des valeurs uniques, ajoutez le décorateur @unique
à Enum
.
# enum_unique_enforce.py
import enum
@enum.unique
class BugStatus(enum.Enum):
new = 7
incomplete = 6
invalid = 5
wont_fix = 4
in_progress = 3
fix_committed = 2
fix_released = 1
# This will trigger an error with unique applied.
by_design = 4
closed = 1
Les membres qui ont des valeurs répétées déclenchent une erreur d’exception ValueError
quand la classe Enum
est interprétée.
$ python3 enum_unique_enforce.py
Traceback (most recent call last):
File "enum_unique_enforce.py", line 11, in <module>
class BugStatus(enum.Enum):
File ".../lib/python3.7/enum.py", line 848, in unique
(enumeration, alias_details))
ValueError: duplicate values found in <enum 'BugStatus'>:
by_design -> wont_fix, closed -> fix_released
Création d’Énumérations par Programme
Dans certains cas, il est plus pratique de créer des énumérations par programme, plutôt que les coder en dur dans une définition de classe. Pour ces situations, Enum
prend aussi en charge le fait de passer les noms des membres et les valeurs au constructeur de classe.
# enum_programmatic_create.py
import enum
BugStatus = enum.Enum(
value='BugStatus',
names=('fix_released fix_committed in_progress '
'wont_fix invalid incomplete new'),
)
print('Member: {}'.format(BugStatus.new))
print('\nAll members:')
for status in BugStatus:
print('{:15} = {}'.format(status.name, status.value))
L’argument value
est le nom de l’énumération, qui est utilisée pour construire la représentation des membres. L’argument names
liste les membres de l’énumération. Quand une chaîne unique est passée, elle est divisée en espaces et en virgules, et les jetons résultants sont utilisés comme noms pour les membres, auxquels sont automatiquement attribuées des valeurs commençant par 1
.
$ python3 enum_programmatic_create.py
Member: BugStatus.new
All members:
fix_released = 1
fix_committed = 2
in_progress = 3
wont_fix = 4
invalid = 5
incomplete = 6
new = 7
Pour plus de contrôles sur les valeurs associées aux membres, la chaîne names
peut être remplacée par une séquence de deux tuples ou un dictionnaire faisant correspondre des noms aux valeurs.
# enum_programmatic_mapping.py
import enum
BugStatus = enum.Enum(
value='BugStatus',
names=[
('new', 7),
('incomplete', 6),
('invalid', 5),
('wont_fix', 4),
('in_progress', 3),
('fix_committed', 2),
('fix_released', 1),
],
)
print('All members:')
for status in BugStatus:
print('{:15} = {}'.format(status.name, status.value))
Dans cet exemple, une liste de deux tuples est donnée au lieu d’une chaîne unique contenant seulement les noms des membres. Cela permet de reconstruire l’énumération BugStatus
avec les membres dans le même ordre que la version définie dans enum_create.py
.
$ python3 enum_programmatic_mapping.py
All members:
new = 7
incomplete = 6
invalid = 5
wont_fix = 4
in_progress = 3
fix_committed = 2
fix_released = 1
Valeurs des Membres Non Entiers
Les valeurs des membres d’énumération ne sont pas restreints aux entiers. En fait, tout type d’objet peut être associé avec un membre. Si la valeur est un tuple, les membres sont passés en tant qu’argument individuel à __init__()
.
# enum_tuple_values.py
import enum
class BugStatus(enum.Enum):
new = (7, ['incomplete',
'invalid',
'wont_fix',
'in_progress'])
incomplete = (6, ['new', 'wont_fix'])
invalid = (5, ['new'])
wont_fix = (4, ['new'])
in_progress = (3, ['new', 'fix_committed'])
fix_committed = (2, ['in_progress', 'fix_released'])
fix_released = (1, ['new'])
def __init__(self, num, transitions):
self.num = num
self.transitions = transitions
def can_transition(self, new_state):
return new_state.name in self.transitions
print('Name:', BugStatus.in_progress)
print('Value:', BugStatus.in_progress.value)
print('Custom attribute:', BugStatus.in_progress.transitions)
print('Using attribute:',
BugStatus.in_progress.can_transition(BugStatus.new))
Dans cet exemple, chaque valeur de membre est un tuple contenant l’ID numérique (qui peut être enregistré dans une base de données) et une liste de transitions valide, sortant de l’état actuel.
$ python3 enum_tuple_values.py
Name: BugStatus.in_progress
Value: (3, ['new', 'fix_committed'])
Custom attribute: ['new', 'fix_committed']
Using attribute: True
Pour les cas les plus complexes, les tuples peuvent devenir difficiles à manier. Puisque les valeurs des membres peuvent être n’importe quel type d’objet, les dictionnaires peuvent être utilisés pour les cas où il y a beaucoup d’attributs distincts à suivre pour chaque valeur d’énumération. Les valeurs complexes sont directement transmises à __init __ ()
en tant que seul argument autre que self
.
# enum_complex_values.py
import enum
class BugStatus(enum.Enum):
new = {
'num': 7,
'transitions': [
'incomplete',
'invalid',
'wont_fix',
'in_progress',
],
}
incomplete = {
'num': 6,
'transitions': ['new', 'wont_fix'],
}
invalid = {
'num': 5,
'transitions': ['new'],
}
wont_fix = {
'num': 4,
'transitions': ['new'],
}
in_progress = {
'num': 3,
'transitions': ['new', 'fix_committed'],
}
fix_committed = {
'num': 2,
'transitions': ['in_progress', 'fix_released'],
}
fix_released = {
'num': 1,
'transitions': ['new'],
}
def __init__(self, vals):
self.num = vals['num']
self.transitions = vals['transitions']
def can_transition(self, new_state):
return new_state.name in self.transitions
print('Name:', BugStatus.in_progress)
print('Value:', BugStatus.in_progress.value)
print('Custom attribute:', BugStatus.in_progress.transitions)
print('Using attribute:',
BugStatus.in_progress.can_transition(BugStatus.new))
Cet exemple exprime les mêmes données que l’exemple précédent, utilisant des dictionnaires plutôt que des tuples.
$ python3 enum_complex_values.py
Name: BugStatus.in_progress
Value: {'num': 3, 'transitions': ['new', 'fix_committed']}
Custom attribute: ['new', 'fix_committed']
Using attribute: True
Lire aussi
- Documentation standard de la bibliothèque pour enum
- PEP 435 - Ajouter un type Enum à la bibliothèque standard Python
- flufl.enum – L’inspiration originelle d’enum, par by Barry Warsaw.
collections - Types de Données de Conteneur
But : Types de données de conteneur.
Le module collections
inclus les types de données de conteneur au-delà des types natifs list
, dict
et tuple
.
- ChainMap - Recherche dans plusieurs dictionnaires
- Counter - Compter les objets hachables
- defaultdict - Retourner une valeur par défaut aux clés manquantes
- deque - File d’attente double
- namedtuple - Sous-classe de tuple avec champs nommés
- OrderedDict - Se souvenir de l’ordre des clés ajoutées à un dictionnaire
- collections.abc - Classes de base abstraites pour les conteneurs
Lire aussi
- Documentation standard de la bibliothèque pour collections
- Notes de portage de Python 2 à 3 pour les collections
- PEP 342 - Coroutines via Générateurs Améliorés
- PEP 492 - Coroutines avec syntaxe
async
etawait
ChainMap - Recherche dans plusieurs dictionnaires
La classe ChainMap
gère une séquence de dictionnaires, et recherche au-travers, dans l’ordre où ils sont donnés, pour trouver les valeurs associées aux clés. Une ChainMap
constitue un bon conteneur “contextuel”, car il peut être traité comme une pile pour laquelle des modifications sont apportées à mesure que la pile grandit, ces modifications étant à nouveau ignorées à mesure que la pile se réduit.
Accéder à des valeurs
La ChainMap
supporte la même API qu’une dictionnaire classique pour accéder aux valeurs existantes.
# collections_chainmap_read.py
import collections
a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}
m = collections.ChainMap(a, b)
print('Individual Values')
print('a = {}'.format(m['a']))
print('b = {}'.format(m['b']))
print('c = {}'.format(m['c']))
print()
print('Keys = {}'.format(list(m.keys())))
print('Values = {}'.format(list(m.values())))
print()
print('Items:')
for k, v in m.items():
print('{} = {}'.format(k, v))
print()
print('"d" in m: {}'.format(('d' in m)))
Les correspondances sous-jacentes font l’objet d’une recherche dans l’ordre dans lequel elles ont été transmises au constructeur, ainsi la valeur indiquée pour la clé c
provient du dictionnaire a
.
$ python3 collections_chainmap_read.py
Individual Values
a = A
b = B
c = C
Keys = ['b', 'c', 'a']
Values = ['B', 'C', 'A']
Items:
b = B
c = C
a = A
"d" in m: False
Réordonner
La ChainMap
enregistre dans son attribut maps
une liste de correspondances, sur laquelle elle fait sa recherche. Cette liste est mutable, ainsi il est possible d’ajouter de nouvelles correspondances directement ou de changer l’ordre des éléments pour contrôler la recherche et le comportement de mise à jour.
# collections_chainmap_reorder.py
import collections
a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}
m = collections.ChainMap(a, b)
print(m.maps)
print('c = {}\n'.format(m['c']))
# reverse the list
m.maps = list(reversed(m.maps))
print(m.maps)
print('c = {}'.format(m['c']))
Quand la liste de correspondances est renversée, la valeur associée à c
change.
$ python3 collections_chainmap_reorder.py
[{'a': 'A', 'c': 'C'}, {'b': 'B', 'c': 'D'}]
c = C
[{'b': 'B', 'c': 'D'}, {'a': 'A', 'c': 'C'}]
c = D
Mise à jour des Valeurs
Une ChainMap
ne met pas en cache les valeurs dans les correspondances sous-jacentes. Ainsi, si leur contenu est modifié, les résultats sont reflétés lors de l’accès à ChainMap
.
# collections_chainmap_update_behind.py
import collections
a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}
m = collections.ChainMap(a, b)
print('Before: {}'.format(m['c']))
a['c'] = 'E'
print('After : {}'.format(m['c']))
Changer les valeurs associées avec les clés existantes et ajouter de nouveaux éléments fonctionne de la même manière.
$ python3 collections_chainmap_update_behind.py
Before: C
After : E
Il est également possible de définir directement des valeurs via ChainMap
, bien que seule la première correspondance de la chaîne soit réellement modifiée.
# collections_chainmap_update_directly.py
import collections
a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}
m = collections.ChainMap(a, b)
print('Before:', m)
m['c'] = 'E'
print('After :', m)
print('a:', a)
Quand la nouvelle valeur est enregistrée en utilisant m
, la correspondance a
est mise à jour.
$ python3 collections_chainmap_update_directly.py
Before: ChainMap({'a': 'A', 'c': 'C'}, {'b': 'B', 'c': 'D'})
After : ChainMap({'a': 'A', 'c': 'E'}, {'b': 'B', 'c': 'D'})
a: {'a': 'A', 'c': 'E'}
ChainMap
fournit une méthode pratique pour créer une nouvelle instance avec une correspondance supplémentaire au début de la liste des correspondances pour éviter de modifier les structures de données sous-jacentes existantes.
# collections_chainmap_new_child.py
import collections
a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}
m1 = collections.ChainMap(a, b)
m2 = m1.new_child()
print('m1 before:', m1)
print('m2 before:', m2)
m2['c'] = 'E'
print('m1 after:', m1)
print('m2 after:', m2)
Ce comportement d’empilement est pratique pour utiliser les instances de ChainMap
comme contextes de modèles ou d’applications. Il est spécifiquement facile d’ajouter ou de mettre à jour des valeurs en une itération, puis d’annuler les changements pour la prochaine itération.
$ python3 collections_chainmap_new_child.py
m1 before: ChainMap({'a': 'A', 'c': 'C'}, {'b': 'B', 'c': 'D'})
m2 before: ChainMap({}, {'a': 'A', 'c': 'C'}, {'b': 'B', 'c':
'D'})
m1 after: ChainMap({'a': 'A', 'c': 'C'}, {'b': 'B', 'c': 'D'})
m2 after: ChainMap({'c': 'E'}, {'a': 'A', 'c': 'C'}, {'b': 'B',
'c': 'D'})
Pour les situations où le nouveau contexte est connu ou natif à l’avance, il est aussi possible de passer une correspondance à new_child()
.
# collections_chainmap_new_child_explicit.py
import collections
a = {'a': 'A', 'c': 'C'}
b = {'b': 'B', 'c': 'D'}
c = {'c': 'E'}
m1 = collections.ChainMap(a, b)
m2 = m1.new_child(c)
print('m1["c"] = {}'.format(m1['c']))
print('m2["c"] = {}'.format(m2['c']))
Ce qui est l’équivalent de :
m2 = collections.ChainMap(c, *m1.maps)
et produit :
$ python3 collections_chainmap_new_child_explicit.py
m1["c"] = C
m2["c"] = E
Compter les objets hachables
Un Counter
est un conteneur qui garde une trace du nombre de fois où des valeurs équivalentes sont ajoutées. Il peut être utilisé pour implémenter les mêmes algorithmes pour lesquels d’autres langages utilisent couramment des structures de données en sacs ou en multi-ensembles.
Initialisation
Counter
prend en charge trois formes d’initialisation. Son constructeur peut être appelé avec une séquence d’items, un dictionnaire contenant des clés et des compteurs, ou en utilisant des arguments de mots-clés qui associent les noms de chaîne aux compteurs.
# collections_counter_init.py
import collections
print(collections.Counter(['a', 'b', 'c', 'a', 'b', 'b']))
print(collections.Counter({'a': 2, 'b': 3, 'c': 1}))
print(collections.Counter(a=2, b=3, c=1))
Les résultats des trois formes d’initialisation sont les mêmes.
$ python3 collections_counter_init.py
Counter({'b': 3, 'a': 2, 'c': 1})
Counter({'b': 3, 'a': 2, 'c': 1})
Counter({'b': 3, 'a': 2, 'c': 1})
Un Counter
vide peut être construit sans arguments et rempli via la méthode update()
.
# collections_counter_update.py
import collections
c = collections.Counter()
print('Initial :', c)
c.update('abcdaab')
print('Sequence:', c)
c.update({'a': 1, 'd': 5})
print('Dict :', c)
Les valeurs comptées sont augmentées en se basant sur les nouvelles données, plutôt que remplacées. Dans l’exemple précédent, le compteur pour a
va de 3
à 4
.
$ python3 collections_counter_update.py
Initial : Counter()
Sequence: Counter({'a': 3, 'b': 2, 'c': 1, 'd': 1})
Dict : Counter({'d': 6, 'a': 4, 'b': 2, 'c': 1})
Accéder aux Compteurs
Une fois qu’un Counter
est rempli, ses valeurs peuvent être récupérées en utilisant l’API de dictionnaire.
# collections_counter_get_values.py
import collections
c = collections.Counter('abcdaab')
for letter in 'abcde':
print('{} : {}'.format(letter, c[letter]))
Le Counter
ne soulève pas d’erreur KeyError
pour les items inconnus. Si une valeur n’est pas vue dans l’entrée (comme c’est le cas dans l’exemple avec e
), son compteur est à 0
.
$ python3 collections_counter_get_values.py
a : 3
b : 2
c : 1
d : 1
e : 0
La méthode elements()
retourne un itérateur qui produit tous les items connus de Counter
.
# collections_counter_elements.py
import collections
c = collections.Counter('extremely')
c['z'] = 0
print(c)
print(list(c.elements()))
L’ordre des éléments n’est pas garanti, et les items avec des compteurs inférieurs ou égales à zéro ne sont pas inclus.
$ python3 collections_counter_elements.py
Counter({'e': 3, 'x': 1, 't': 1, 'r': 1, 'm': 1, 'l': 1, 'y': 1,
'z': 0})
['e', 'e', 'e', 'x', 't', 'r', 'm', 'l', 'y']
Utilisez most_common()
pour produire une séquence de n
valeurs d’entrées les plus fréquemment rencontrées et de leurs compteurs respectifs.
# collections_counter_most_common.py
import collections
c = collections.Counter()
with open('/usr/share/dict/words', 'rt') as f:
for line in f:
c.update(line.rstrip().lower())
print('Most common:')
for letter, count in c.most_common(3):
print('{}: {:>7}'.format(letter, count))
Cet exemple compte les lettres apparaissant dans tous les mots du dictionnaire système pour produire une distribution de fréquence, puis imprime les trois lettres les plus courantes. Si vous omettez l’argument à most_common ()
, vous obtenez une liste de tous les éléments, par ordre de fréquence.
$ python3 collections_counter_most_common.py
Most common:
e: 235331
i: 201032
a: 199554
Arithmétique
Les instances de Counter
supportent l’arithmétique et paramètrent des opérations pour l’agrégation des résultats. Cet exemple montre les opérateurs standards pour créer de nouvelles instances de Counter
, mais les opérateurs sur place +=
, -=
, &=
, et |=
sont aussi supportés.
# collections_counter_arithmetic.py
import collections
c1 = collections.Counter(['a', 'b', 'c', 'a', 'b', 'b'])
c2 = collections.Counter('alphabet')
print('C1:', c1)
print('C2:', c2)
print('\nCombined counts:')
print(c1 + c2)
print('\nSubtraction:')
print(c1 - c2)
print('\nIntersection (taking positive minimums):')
print(c1 & c2)
print('\nUnion (taking maximums):')
print(c1 | c2)
Chaque fois qu’un nouveau Counter
est produit par opération, chaque item avec un compteur zéro ou négatif est supprimé. Le compteur pour a
est le même dans c1
et c2
, la soustraction le laisse à zéro.
$ python3 collections_counter_arithmetic.py
C1: Counter({'b': 3, 'a': 2, 'c': 1})
C2: Counter({'a': 2, 'l': 1, 'p': 1, 'h': 1, 'b': 1, 'e': 1, 't': 1})
Combined counts:
Counter({'a': 4, 'b': 4, 'c': 1, 'l': 1, 'p': 1, 'h': 1, 'e': 1, 't': 1})
Subtraction:
Counter({'b': 2, 'c': 1})
Intersection (taking positive minimums):
Counter({'a': 2, 'b': 1})
Union (taking maximums):
Counter({'b': 3, 'a': 2, 'c': 1, 'l': 1, 'p': 1, 'h': 1, 'e': 1, 't': 1})
defaultdict - Retourner une Valeur par Défaut aux Clés Manquantes
Le dictionnaire standard inclut la méthode setdefault()
pour récupérer une valeur et en établir une par défaut si la valeur n’existe pas. Par contraste, defaultdict
permet à l’appelant de spécifier la valeur par défaut dès l’initialisation du conteneur.
# collections_defaultdict.py
import collections
def default_factory():
return 'default value'
d = collections.defaultdict(default_factory, foo='bar')
print('d:', d)
print('foo =>', d['foo'])
print('bar =>', d['bar'])
Cette méthode fonctionne aussi dans la mesure où il est approprié que toutes les clés aient la même valeur par défaut. Elle peut être spécialement utile si la valeur par défaut est un type utilisé pour l’agrégation ou l’accumulation de valeurs, tels que list
, set
, ou même int
. La documentation standard de la bibliothèque inclus de nombreux exemples dans lesquels defaultdict
est utilisé de cette manière.
$ python3 collections_defaultdict.py
d: defaultdict(<function default_factory at 0x101341950>,
{'foo': 'bar'})
foo => bar
bar => default value
Lire aussi
- defaultdict : exemples – Exemples d’utilisation de
defaultdict
dans la documentation standard de la bibliothèque - Évolution des Dictionnaires par défaut en Python – Article, en anglais, de James Tauber de la relation entre
defaultdict
et d’autres moyens d’initialisation des dictionnaires.
deque - File d’Attente Double
Une file d’attente double, ou deque
prend en charge l’ajout ou la suppression d’éléments depuis chaque extrémité de la queue. Les piles et les files d’attente les plus couramment utilisées sont des formes dégénérées de deques où les entrées et les sorties sont restreintes à une seule extrémité.
# collections_deque.py
import collections
d = collections.deque('abcdefg')
print('Deque:', d)
print('Length:', len(d))
print('Left end:', d[0])
print('Right end:', d[-1])
d.remove('c')
print('remove(c):', d)
Puisque les deques sont un type de conteneur de séquences, ils prennent en charge certaines des opérations similaires à list
, tel que l’examen des contenus avec __getitem__()
, déterminer la longueur, et la suppression des éléments depuis le milieu de la queue par correspondance d’identité.
$ python3 collections_deque.py
Deque: deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
Length: 7
Left end: a
Right end: g
remove(c): deque(['a', 'b', 'd', 'e', 'f', 'g'])
Remplissage
Une deque peut être remplis depuis chaque extrémité, décrite à «gauche» ou à «droite» dans l’implémentation Python.
# collections_deque_populating.py
import collections
# Add to the right
d1 = collections.deque()
d1.extend('abcdefg')
print('extend :', d1)
d1.append('h')
print('append :', d1)
# Add to the left
d2 = collections.deque()
d2.extendleft(range(6))
print('extendleft:', d2)
d2.appendleft(6)
print('appendleft:', d2)
La fonction extendleft()
effectue une itération sur son entrée et effectue l’équivalent d’un appendleft()
pour chaque item. Le résultat final est que deque
contient la séquence d’entrée dans l’ordre inverse.
$ python3 collections_deque_populating.py
extend : deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
append : deque(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
extendleft: deque([5, 4, 3, 2, 1, 0])
appendleft: deque([6, 5, 4, 3, 2, 1, 0])
Consommation
De la même manière, les éléments de deque
peuvent être consommés depuis les deux extrémités, ou de l’une, selon l’algorithme qui est appliqué.
# collections_deque_consuming.py
import collections
print('From the right:')
d = collections.deque('abcdefg')
while True:
try:
print(d.pop(), end='')
except IndexError:
break
print()
print('\nFrom the left:')
d = collections.deque(range(6))
while True:
try:
print(d.popleft(), end='')
except IndexError:
break
print()
Utilisez pop()
pour supprimer un item depuis l’extrémité «droite» de deque
et popleft()
pour prendre un item depuis l’extrémité «gauche».
$ python3 collections_deque_consuming.py
From the right:
gfedcba
From the left:
012345
Puisque les deques sont des processus sûrs (dits «thead-safe»), les contenus peuvent être consommés depuis chaque extrémité en même temps, depuis des processus séparés.
# collections_deque_both_ends.py
import collections
import threading
import time
candle = collections.deque(range(5))
def burn(direction, nextSource):
while True:
try:
next = nextSource()
except IndexError:
break
else:
print('{:>8}: {}'.format(direction, next))
time.sleep(0.1)
print('{:>8} done'.format(direction))
return
left = threading.Thread(target=burn,
args=('Left', candle.popleft))
right = threading.Thread(target=burn,
args=('Right', candle.pop))
left.start()
right.start()
left.join()
right.join()
Les processus dans cet exemple alternent entre chaque extrémité, supprimant les éléments jusqu’à ce que deque
soit vide.
$ python3 collections_deque_both_ends.py
Left: 0
Right: 4
Right: 3
Left: 1
Right: 2
Left done
Right done
Rotation
Un autre aspect utile de deque
est la capacité à se retourner dans chaque direction, et ainsi à sauter par dessus certains items.
# collections_deque_rotate.py
import collections
d = collections.deque(range(10))
print('Normal :', d)
d = collections.deque(range(10))
d.rotate(2)
print('Right rotation:', d)
d = collections.deque(range(10))
d.rotate(-2)
print('Left rotation :', d)
La rotation de deque
par la droite (utilisant la rotation positive) prend les éléments depuis l’extrémité droite et les déplacer à l’extrémité gauche. Les retourner par la gauche (avec une valeur négative) prend les items depuis l’extrémité gauche et les déplacent vers l’extrémité droite. Il peut être utile de visualiser les éléments dans la deque comme étant gravés le long du bord d’un cadran.
$ python3 collections_deque_rotate.py
Normal : deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Right rotation: deque([8, 9, 0, 1, 2, 3, 4, 5, 6, 7])
Left rotation : deque([2, 3, 4, 5, 6, 7, 8, 9, 0, 1])
Contraindre la Taille de la Queue
Une instance deque
peut être configuré avec une longueur maximale, ainsi elle ne pourra jamais grandir plus que sa taille. Quand la file d’attente atteint la taille spécifiée, les éléments existants sont abandonnés au profit des nouveaux items ajoutés. Ce comportement est pratique pour chercher les n
derniers éléments d’un flux d’une longueur indéterminé.
# collections_deque_maxlen.py
import collections
import random
# Set the random seed so we see the same output each time
# the script is run.
random.seed(1)
d1 = collections.deque(maxlen=3)
d2 = collections.deque(maxlen=3)
for i in range(5):
n = random.randint(0, 100)
print('n =', n)
d1.append(n)
d2.appendleft(n)
print('D1:', d1)
print('D2:', d2)
La longueur de la file d’attente est maintenue quelque soit l’extrémité à laquelle sont ajoutés les éléments.
$ python3 collections_deque_maxlen.py
n = 17
D1: deque([17], maxlen=3)
D2: deque([17], maxlen=3)
n = 72
D1: deque([17, 72], maxlen=3)
D2: deque([72, 17], maxlen=3)
n = 97
D1: deque([17, 72, 97], maxlen=3)
D2: deque([97, 72, 17], maxlen=3)
n = 8
D1: deque([72, 97, 8], maxlen=3)
D2: deque([8, 97, 72], maxlen=3)
n = 32
D1: deque([97, 8, 32], maxlen=3)
D2: deque([32, 8, 97], maxlen=3)
Lire aussi
- Wikipedia : Deque - Une discussion sur la structure de données deque (en anglais)
- Recettes Deque - Exemples d’utilisations des files d’attente depuis la documentation de la bibliothèque standard.
namedtuple - Sous-classe de tuple avec champs nommés
Le tuple
standard utilise les index numériques pour accéder à ses membres.
# collections_tuple.py
bob = ('Bob', 30, 'male')
print('Representation:', bob)
jane = ('Jane', 29, 'female')
print('\nField by index:', jane[0])
print('\nFields by index:')
for p in [bob, jane]:
print('{} is a {} year old {}'.format(*p))
Ce qui fait des tuples des conteneurs pratiques pour des utilisations simples.
$ python3 collections_tuple.py
Representation: ('Bob', 30, 'male')
Field by index: Jane
Fields by index:
Bob is a 30 year old male
Jane is a 29 year old female
Par contraste, se souvenir quel index utiliser pour chaque valeur peut entraîner des erreurs, spécialement si le tuple a beaucoup de champs et est construit loin de l’endroit où il est utilisé. Un namedtuple
assigne des noms, aussi bien que des index numériques, pour chaque membre.
Définitions
Les instances namedtuple
sont juste plus efficient en mémoire que les tuples ordinaires car ils n’ont pas de dictionnaires par instance. Chaque type de namedtuple
est représenté par sa propre classe, qui est créée en utilisant la fonction de fabrication namedtuple()
. Les arguments sont des noms de la nouvelle classe et une chaîne contenant les noms des éléments.
# collections_namedtuple_person.py
import collections
Person = collections.namedtuple('Person', 'name age')
bob = Person(name='Bob', age=30)
print('\nRepresentation:', bob)
jane = Person(name='Jane', age=29)
print('\nField by name:', jane.name)
print('\nFields by index:')
for p in [bob, jane]:
print('{} is {} years old'.format(*p))
Comme l’exemple l’illustre, il est possible d’accéder aux champs du namedtuple
par nom en utilisant la notation (obj.attr
) aussi bien que par l’utilisation des index positionnés des tuples standards.
$ python3 collections_namedtuple_person.py
Representation: Person(name='Bob', age=30)
Field by name: Jane
Fields by index:
Bob is 30 years old
Jane is 29 years old
Tout comme un tuple
ordinaire, un namedtuple
est immuable. Cette restriction permet aux instances tuples
d’avoir une valeur de hachage cohérente, ce qui rend possible leur utilisation comme clés de dictionnaires et d’être incluses dans des ensembles.
# collections_namedtuple_immutable.py
import collections
Person = collections.namedtuple('Person', 'name age')
pat = Person(name='Pat', age=12)
print('\nRepresentation:', pat)
pat.age = 21
Essayer de changer sa valeur par son nom d’attribut résulte en une erreur AttributError
.
$ python3 collections_namedtuple_immutable.py
Representation: Person(name='Pat', age=12)
Traceback (most recent call last):
File "collections_namedtuple_immutable.py", line 17, in
<module>
pat.age = 21
AttributeError: can't set attribute
Noms de Champs Invalides
Les noms de champs sont invalides s’ils sont répétés ou en conflit avec des mots clés Python.
# collections_namedtuple_bad_fields.py
import collections
try:
collections.namedtuple('Person', 'name class age')
except ValueError as err:
print(err)
try:
collections.namedtuple('Person', 'name age age')
except ValueError as err:
print(err)
Comme les noms des champs sont analysés, les valeurs invalides causent des erreurs d’exceptions ValueError
.
$ python3 collections_namedtuple_bad_fields.py
Type names and field names cannot be a keyword: 'class'
Encountered duplicate field name: 'age'
Dans les situations où un namedtuple
est créé sur la base des valeurs en-dehors du contrôle du programme (telle que représenter les lignes renvoyées par une requête de base de données, lorsque le schéma n’est pas connu à l’avance), l’option rename
doit être paramétrée à True
pour que les champs invalides soient renommés.
# collections_namedtuple_rename.py
import collections
with_class = collections.namedtuple(
'Person', 'name class age',
rename=True)
print(with_class._fields)
two_ages = collections.namedtuple(
'Person', 'name age age',
rename=True)
print(two_ages._fields)
Les nouveaux noms des champs renommés dépendent de leur index dans le tuple, ainsi le champ avec le nom class
devient _1
et le champ dupliqué age
est changé en _2
.
$ python3 collections_namedtuple_rename.py
('name', '_1', 'age')
('name', 'age', '_2')
Attributs Spéciaux
namedtuple
fournit de nombreux attributs et méthodes pratiques pour travailler avec les sous-classes et les instances. Toutes ces propriétés natives ont des noms préfixés avec un tiret bas (_
), qui par convention dans la plupart des programmes Python indique un attribut privé. Pour namedtuple
, cependant, le préfixe est intentionnel pour protéger le nom d’une collision avec les noms d’attributs fournis par utilisateur.
Les noms de ces champs passés à namedtuple
pour définir de nouvelle classe sont sauvegardés sont l’attribut _fields
.
# collections_namedtuple_fields.py
import collections
Person = collections.namedtuple('Person', 'name age')
bob = Person(name='Bob', age=30)
print('Representation:', bob)
print('Fields:', bob._fields)
Bien que l’argument soit une seule chaîne séparée par des espaces, la valeur enregistrée est la séquence de noms individuels.
$ python3 collections_namedtuple_fields.py
Representation: Person(name='Bob', age=30)
Fields: ('name', 'age')
Les instances namedtuple
peuvent être converties en instances OrderedDict
par l’usage de _asdict()
.
# collections_namedtuple_asdict.py
import collections
Person = collections.namedtuple('Person', 'name age')
bob = Person(name='Bob', age=30)
print('Representation:', bob)
print('As Dictionary:', bob._asdict())
Les clés de OrderedDict
sont dans le même ordre que les champs du namedtuple
.
$ python3 collections_namedtuple_asdict.py
Representation: Person(name='Bob', age=30)
As Dictionary: OrderedDict([('name', 'Bob'), ('age', 30)])
La méthode _replace()
construit une nouvelle instance, en remplaçant les valeur de certains champs dans le processus.
# collections_namedtuple_replace.py
import collections
Person = collections.namedtuple('Person', 'name age')
bob = Person(name='Bob', age=30)
print('\nBefore:', bob)
bob2 = bob._replace(name='Robert')
print('After:', bob2)
print('Same?:', bob is bob2)
Bien que le nom implique que l’objet existant ait été modifié, parce que les instances namedtuple
sont immuables, la méthode retourne actuellement un nouvel objet.
$ python3 collections_namedtuple_replace.py
Before: Person(name='Bob', age=30)
After: Person(name='Robert', age=30)
Same?: False
OrderedDict - Se souvenir de l’ordre des clés ajoutées à un dictionnaire
Un OrderedDict
est une sous-classe de dictionnaire qui se souvient de l’ordre dans lequel son contenu est ajouté.
# collections_ordereddict_iter.py
import collections
print('Regular dictionary:')
d = {}
d['a'] = 'A'
d['b'] = 'B'
d['c'] = 'C'
for k, v in d.items():
print(k, v)
print('\nOrderedDict:')
d = collections.OrderedDict()
d['a'] = 'A'
d['b'] = 'B'
d['c'] = 'C'
for k, v in d.items():
print(k, v)
Avant Python 3.6 un dict
ordinaire ne piste pas l’ordre d’insertion, et itère dessus les valeurs produites dans l’ordre en fonction de la manière dont les clés étaient stockées dans la table de hachage, qui à son tour, est influencé par une valeur aléatoire pour réduire les collisions. Dans un OrderedDict
, par contraste, l’ordre dans lequel les éléments sont insérés est retenu et utilisé lors de la création de l’itérateur.
$ python3.5 collections_ordereddict_iter.py
Regular dictionary:
c C
b B
a A
OrderedDict:
a A
b B
c C
Sous Python 3.6, le natif dict
piste l’ordre d’insertion, bien que ce comportement soit l’un des effets secondaires d’un changement d’implémentation, il ne faut pas s’y fier.
$ python3.6 collections_ordereddict_iter.py
Regular dictionary:
a A
b B
c C
OrderedDict:
a A
b B
c C
Égalité
Un dict
ordinaire cherche son contenu lorsqu’il teste l’égalité. Un OrderedDict
considère lui aussi l’ordre dans lequel les items sont ajoutés.
# collections_ordereddict_equality.py
import collections
print('dict :', end=' ')
d1 = {}
d1['a'] = 'A'
d1['b'] = 'B'
d1['c'] = 'C'
d2 = {}
d2['c'] = 'C'
d2['b'] = 'B'
d2['a'] = 'A'
print(d1 == d2)
print('OrderedDict:', end=' ')
d1 = collections.OrderedDict()
d1['a'] = 'A'
d1['b'] = 'B'
d1['c'] = 'C'
d2 = collections.OrderedDict()
d2['c'] = 'C'
d2['b'] = 'B'
d2['a'] = 'A'
print(d1 == d2)
Dans ce cas, puisque deux dictionnaires ordonnés sont créés depuis des valeurs dans un ordre différent, ils sont considérés différents.
$ python3 collections_ordereddict_equality.py
dict : True
OrderedDict: False
Réordonner
Il est possible de changer l’ordre des clés dans un OrderedDict
en les bougeant selon le début ou la fin de la séquence en utilisant move_to_end()
.
# collections_ordereddict_move_to_end.py
import collections
d = collections.OrderedDict(
[('a', 'A'), ('b', 'B'), ('c', 'C')]
)
print('Before:')
for k, v in d.items():
print(k, v)
d.move_to_end('b')
print('\nmove_to_end():')
for k, v in d.items():
print(k, v)
d.move_to_end('b', last=False)
print('\nmove_to_end(last=False):')
for k, v in d.items():
print(k, v)
Le dernier argument demande à move_to_end()
de bouger l’élément afin de devenir le dernier dans la séquence de clé (quand True
) ou le premier (quand False
).
$ python3 collections_ordereddict_move_to_end.py
Before:
a A
b B
c C
move_to_end():
a A
c C
b B
move_to_end(last=False):
b B
a A
c C
Lire aussi
collections.abc - Classes de base abstraites pour les conteneurs
But : Classes de bases abstraites pour types de données de conteneur.
Le module collections.abc
contient des classes de base abstraites qui définissent les API pour les structures de conteneur de données natives dans Python et fournies dans le module collections
. Référez-vous à la table ci-dessous pour la liste des classes et leur but.
+——————–+——————————+——————————————————————————————————————-+
| Classe | Classe(s) de Base | But de l’API |
+——————–+——————————+——————————————————————————————————————-+
| Container | | Fonctionnalités de base de conteneur, tel que l’opérateur inc
. |
| Hashable | | Ajoute le support de fourniture d’une valeur de hachage pour l’instance du conteneur. |
| Iterable | | Peut créer un itérateur sur le contenu du conteneur. |
| Iterator | Iterable | Est un itérateur sur le contenu du conteneur. |
| Generator | Iterator | Étend les itérateurs avec le protocole générateur du PEP 342. |
| Sized | | Ajoute les méthodes de conteneurs pour connaître la grosseur de leur taille. |
| Callable | | Pour les conteneurs invoqués en tant que fonction. |
| Sequence | Sized, Iterable, Container | Prend en charge la récupération d’éléments individuels, l’itération et la modification de l’ordre des éléments. |
| MutableSequence | Sequence | Prend en charge l’ajout et la suppression des éléments d’une instance après qu’ils soient créés. |
| ByteString | Sequence | API combinées de bytes
et de bytearray
. |
| Set | Sized, Iterable, Container | Prend en charges les opérations d’ensemble telles que l’intersection et l’union. |
| MutableSet | Set | Ajoute les méthodes de manipulation pour le contenu d’ensemble après qu’il soit créé. |
| Mapping | Sized, Iterable, Container | Définit l’API en lecture seule utilisé par dict
. |
| MutableMapping | Mapping | Définit les méthodes de manipulation des contenus pour la correspondance après qu’ils soient créés. |
| MappingView | Sized | Définit la vue de l’API pour l’accès à la correspondance depuis un itérateur. |
| ItemsView | MappingView, Set | Partie de la vue de l’API. |
| KeysView | MappingView, Set | Partie de la vue de l’API. |
| ValuesView | MappingView | Partie de la vue de l’API. |
| Awaitable | | API pour les objets qui peuvent être utilisés dans des expressions await
, telles que des coroutines. |
| Coroutine | Awaitable | API pour les classes qui implémentent le protocole coroutine. |
| AsyncIterable | | API pour les itérables compatibles avec async for
, telle que définit dans la PEP 492. |
| AsyncIterator | AsyncIterable | API pour les itérateurs asynchrones. |
+——————–+——————————+——————————————————————————————————————-+
En plus de définir clairement les API pour les conteneurs avec des sémantiques différentes, ces classes de base abstraites peuvent être utilisées pour tester n’importe quel objet qui prend en charge une API avant de l’invoquer par l’usage d’ isinstance()
. Certaines de ces classes fournissent aussi des implémentations de méthodes, et elles peuvent être utilisées comme mixages(?) pour créer des types de conteneurs personnalisés sans implémenter chaque méthode à partir de rien.
array - Séquence de Données de Type Fixé
But : Gérer efficacement des séquences de données numériques de type fixé.
Le module array
définit une structure de données de séquence qui ressemble beaucoup à une list
, excepté que tous les membres doivent être du même type primitif. Les types supportés sont tous les types numériques ou les autres primitifs de taille fixée, tels que les bytes
.
Référez-vous à la table ci-dessous pour connaître certains des types supportés. La documentation de la bibliothèque standard pour array
inclus une liste complète des codes de types.
+———-+———————-+—————————–+ | Code | Type | Taille minimal (octets) | +———-+———————-+—————————–+ | b | int | 1 | | B | int | 1 | | h | signed short | 2 | | H | unsigned short | 2 | | i | signed int | 2 | | I | unsigned int | 2 | | l | signed long | 4 | | L | unsigned long | 4 | | q | signed long long | 8 | | Q | unsigned long long | 8 | | f | float | 4 | | d | double float | 8 | +———-+———————-+—————————–+
Initialisation
Un array
est instancié avec un argument décrivant le type de donnée à allouer, et si possible une séquence initiale de données à enregistrer dans le tableau.
# array_string.py
import array
import binascii
s = b'This is the array.'
a = array.array('b', s)
print('As byte string:', s)
print('As array :', a)
print('As hex :', binascii.hexlify(a))
Dans cet exemple, le tableau est configuré pour capturer une séquence d’octets et est initialisé avec une simple chaîne d’octets.
$ python3 array_string.py
As byte string: b'This is the array.'
As array : array('b', [84, 104, 105, 115, 32, 105, 115, 32,
116, 104, 101, 32, 97, 114, 114, 97, 121, 46])
As hex : b'54686973206973207468652061727261792e'
Manipuler des Tableaux
Un array
peut être étendu ou autrement manipulé de la même manière que les autres séquences Python.
# array_sequence.py
import array
import pprint
a = array.array('i', range(3))
print('Initial :', a)
a.extend(range(3))
print('Extended:', a)
print('Slice :', a[2:5])
print('Iterator:')
print(list(enumerate(a)))
Les opérations supportées incluent le découpage, l’itération, et l’ajout d’éléments à la fin.
$ python3 array_sequence.py
Initial : array('i', [0, 1, 2])
Extended: array('i', [0, 1, 2, 0, 1, 2])
Slice : array('i', [2, 0, 1])
Iterator:
[(0, 0), (1, 1), (2, 2), (3, 0), (4, 1), (5, 2)]
Tableaux et Fichiers
Le contenu d’un tableau peut être écrit et lit depuis et vers des fichiers en utilisant des méthodes natives codées efficacement dans ce but.
# array_file.py
import array
import binascii
import tempfile
a = array.array('i', range(5))
print('A1:', a)
# Write the array of numbers to a temporary file
output = tempfile.NamedTemporaryFile()
a.tofile(output.file) # must pass an *actual* file
output.flush()
# Read the raw data
with open(output.name, 'rb') as input:
raw_data = input.read()
print('Raw Contents:', binascii.hexlify(raw_data))
# Read the data into an array
input.seek(0)
a2 = array.array('i')
a2.fromfile(input, len(a))
print('A2:', a2)
Cet exemple illustre la lecture de donnée «brute», venant directement depuis le fichier binaire, versus la lecture dans un autre tableau et convertissant les octets dans les types appropriés.
$ python3 array_file.py
A1: array('i', [0, 1, 2, 3, 4])
Raw Contents: b'0000000001000000020000000300000004000000'
A2: array('i', [0, 1, 2, 3, 4])
tofile()
utilise tobytes()
pour formater les données, et fromfile()
utilise frombytes()
pour les convertir à nouveau en une instance de tableau.
# array_tobytes.py
import array
import binascii
a = array.array('i', range(5))
print('A1:', a)
as_bytes = a.tobytes()
print('Bytes:', binascii.hexlify(as_bytes))
a2 = array.array('i')
a2.frombytes(as_bytes)
print('A2:', a2)
Les deux fonctions tobytes()
et frombytes()
fonctionnent sur des chaînes d’octets, et non pas des chaînes Unicode.
$ python3 array_tobytes.py
A1: array('i', [0, 1, 2, 3, 4])
Bytes: b'0000000001000000020000000300000004000000'
A2: array('i', [0, 1, 2, 3, 4])
Ordre Alternatif d’Octet
Si la donnée dans le tableau n’est pas de l’ordre natif d’octet, ou si la donnée nécessite d’être échangée avant d’être envoyée au système avec un ordre d’octet différent (ou sur le réseau), il est possible de convertir un tableau entier sans itérer les éléments dans Python.
# array_byteswap.py
import array
import binascii
def to_hex(a):
chars_per_item = a.itemsize * 2 # 2 hex digits
hex_version = binascii.hexlify(a)
num_chunks = len(hex_version) // chars_per_item
for i in range(num_chunks):
start = i * chars_per_item
end = start + chars_per_item
yield hex_version[start:end]
start = int('0x12345678', 16)
end = start + 5
a1 = array.array('i', range(start, end))
a2 = array.array('i', range(start, end))
a2.byteswap()
fmt = '{:>12} {:>12} {:>12} {:>12}'
print(fmt.format('A1 hex', 'A1', 'A2 hex', 'A2'))
print(fmt.format('-' * 12, '-' * 12, '-' * 12, '-' * 12))
fmt = '{!r:>12} {:12} {!r:>12} {:12}'
for values in zip(to_hex(a1), a1, to_hex(a2), a2):
print(fmt.format(*values))
La méthode byteswap()
échange l’ordre d’octet des éléments dans le tableau depuis le langage C, qui est plus efficace pour boucler sur les données en Python.
$ python3 array_byteswap.py
A1 hex A1 A2 hex A2
------------ ------------ ------------ ------------
b'78563412' 305419896 b'12345678' 2018915346
b'79563412' 305419897 b'12345679' 2035692562
b'7a563412' 305419898 b'1234567a' 2052469778
b'7b563412' 305419899 b'1234567b' 2069246994
b'7c563412' 305419900 b'1234567c' 2086024210
Lire aussi
- Documentation de la bibliothèque standard pour
array
- struct - Le module
struct
. - Python Numérique - NumPy est une bibliothèque Python pour travailler efficacement avec de grands ensembles de données.
- Notes de Portage de Python 2 vers 3 sur array
heapq - Algorithme de Tri Heap
But : Heapq implémente un algorithme de tri minitas(?) approprié pour utiliser les listes Python.
Un heap
est tel une arborescence de structure de données dans laquelle les nœuds enfants ont une relation d’ordre trié avec les nœuds parents. Le binary heaps
peut être représenté en utilisant une liste ou un tableau organisé afin que les nœuds enfants d’élément N
soient à la position 2 * N + 1 et 2 * N + 2 (pour les index basés sur zéro). Ce calque rend cela possible en réarrangeant le tas en place, ainsi il est n’est pas nécessaire de ré-allouer beaucoup de mémoire lors de l’ajout ou de la suppression d’items.
Un max-heap s’assure que le nœud parent est plus large que ou égal à deux de ces enfants. Un min-heap requiert que le nœud parent soit plus petit ou égal à ces nœuds enfants. Le module Python heapq
implémente un min-heap.
Exemple de Donnée
Cet exemple dans ce chapitre utilise la donnée dans le script heapq_heapdata.py
.
# heapq_heapdata.py
# This data was generated with the random module.
data = [19, 9, 4, 10, 11]
La sortie du tas est imprimé en utilisant le script heapq_showtree.py
.
# heapq_showtree.py
import math
from io import StringIO
def show_tree(tree, total_width=36, fill=' '):
"""Pretty-print a tree."""
output = StringIO()
last_row = -1
for i, n in enumerate(tree):
if i:
row = int(math.floor(math.log(i + 1, 2)))
else:
row = 0
if row != last_row:
output.write('\n')
columns = 2 ** row
col_width = int(math.floor(total_width / columns))
output.write(str(n).center(col_width, fill))
last_row = row
print(output.getvalue())
print('-' * total_width)
print()
Créer un Heap
Il y a deux manières de base de créer un heap : heappush()
et heapify()
.
# heapq_heappush.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
heap = []
print('random :', data)
print()
for n in data:
print('add {:>3}:'.format(n))
heapq.heappush(heap, n)
show_tree(heap)
Quand heappush()
est utilisé, l’ordre de tri du tas des éléments est maintenu lorsque de nouveaux éléments sont ajoutés à partir d’une source de données.
$ python3 heapq_heappush.py
random : [19, 9, 4, 10, 11]
add 19:
19
------------------------------------
add 9:
9
19
------------------------------------
add 4:
4
19 9
------------------------------------
add 10:
4
10 9
19
------------------------------------
add 11:
4
10 9
19 11
------------------------------------
Si la donnée est toujours en mémoire, il est plus efficient d’utiliser heapify()
pour réarranger les items dans la liste.
# heapq_heapify.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
print('random :', data)
heapq.heapify(data)
print('heapified :')
show_tree(data)
Le résultat d’une construction d’une liste dans un ordre heap, un item à la fois, est le même que le fait de construire une liste non ordonnée en appelant heapify()
.
$ python3 heapq_heapify.py
random : [19, 9, 4, 10, 11]
heapified :
4
9 19
10 11
------------------------------------
Accéder au Contenu d’un Heap
Une fois que le heap est organisé correctement, utilisez heappop()
pour supprimer l’élément ayant la valeur la plus petite.
# heapq_heappop.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
print('random :', data)
heapq.heapify(data)
print('heapified :')
show_tree(data)
print()
for i in range(2):
smallest = heapq.heappop(data)
print('pop {:>3}:'.format(smallest))
show_tree(data)
Dans cet exemple, adapté de la documentation de la bibliothèque standard, heapify()
et heappop()
sont utilisées pour trier une liste de nombres.
$ python3 heapq_heappop.py
random : [19, 9, 4, 10, 11]
heapified :
4
9 19
10 11
------------------------------------
pop 4:
9
10 19
11
------------------------------------
pop 9:
10
11 19
------------------------------------
Pour supprimer des éléments existants et les remplacer par de nouvelles valeurs en une unique opération, utilisez heapreplace()
.
# heapq_heapreplace.py
import heapq
from heapq_showtree import show_tree
from heapq_heapdata import data
heapq.heapify(data)
print('start:')
show_tree(data)
for n in [0, 13]:
smallest = heapq.heapreplace(data, n)
print('replace {:>2} with {:>2}:'.format(smallest, n))
show_tree(data)
Remplacer des éléments rend possible de maintenir un heap de taille fixée, tel qu’une file de travaux ordonnés par priorité.
$ python3 heapq_heapreplace.py
start:
4
9 19
10 11
------------------------------------
replace 4 with 0:
0
9 19
10 11
------------------------------------
replace 0 with 13:
9
10 19
13 11
------------------------------------
Extrêmes des Données d’un Heap
heapq
inclus aussi deux fonctions qui examine un itérable et trouve une rangée des valeurs les plus grandes ou les plus petites qu’il contient.
# heapq_extremes.py
import heapq
from heapq_heapdata import data
print('all :', data)
print('3 largest :', heapq.nlargest(3, data))
print('from sort :', list(reversed(sorted(data)[-3:])))
print('3 smallest:', heapq.nsmallest(3, data))
print('from sort :', sorted(data)[:3])
Utiliser nlargest()
et nsmallest()
est efficace seulement pour les valeurs relativement petites de n > 1, mais peut toujours devenir plus pratique dans quelques cas.
$ python3 heapq_extremes.py
all : [19, 9, 4, 10, 11]
3 largest : [19, 11, 10]
from sort : [19, 11, 10]
3 smallest: [4, 9, 10]
from sort : [4, 9, 10]
Fusion Efficace des Séquences Triées
Combiner de nombreuses séquences triées en une nouvelle séquence est facile pour de petits ensembles de données.
python list(sorted(itertools.chain(*data)))
Pour de plus grand ensembles de données, cette technique peut utiliser une somme considérable de mémoire. Au lieu de trier la séquence triée en entier, merge()
utilise un tas pour générer un nouvelle séquence une à la fois, déterminant le nouvel item en utilisant une somme de mémoire fixée.
# heapq_merge.py
import heapq
import random
random.seed(2016)
data = []
for i in range(4):
new_data = list(random.sample(range(1, 101), 5))
new_data.sort()
data.append(new_data)
for i, d in enumerate(data):
print('{}: {}'.format(i, d))
print('\nMerged:')
for i in heapq.merge(*data):
print(i, end=' ')
print()
Puisque l’implémentation de merge()
utilise un tas, il consomme la mémoire basée sur le nombre de séquences à fusionner, plutôt que le nombre des éléments dans ces séquences.
$ python3 heapq_merge.py
0: [33, 58, 71, 88, 95]
1: [10, 11, 17, 38, 91]
2: [13, 18, 39, 61, 63]
3: [20, 27, 31, 42, 45]
Merged:
10 11 13 17 18 20 27 31 33 38 39 42 45 58 61 63 71 88 91 95
Lire aussi
- Documentation de la bibliothèque standard pour heapq
- Wikipedia - Heap (structure de données) - Une description générale des structures de données heap
- Priorité de la File d’attente - Une implémentation prioritaire de la file d’attente de
Queue
dans la bibliothèque standard.
bisect - Maintenir les Listes dans un Ordre Trié
But : Maintenir une liste dans un ordre trié sans avoir à appeler le tri chaque fois qu’un item est ajouté à la liste.
Le module bisect
implémente un algorithme pour insérer des éléments dans une liste tout en maintenant la liste dans un ordre trié.
Insérer dans un Ordre Trié
Voici un exemple dans lequel insort()
est utilisé pour insérer des items dans la liste en ordre trié.
# bisect_example.py
import bisect
# A series of random numbers
values = [14, 85, 77, 26, 50, 45, 66, 79, 10, 3, 84, 77, 1]
print('New Pos Contents')
print('--- --- --------')
l = []
for i in values:
position = bisect.bisect(l, i)
bisect.insort(l, i)
print('{:3} {:3}'.format(i, position), l)
La première colonne de sortie montre un nouveau nombre aléatoire. La seconde colonne montre la position où le nombre sera inséré dans la liste. Le reste de chaque ligne est la liste triée actuelle.
$ python3 bisect_example.py
New Pos Contents
--- --- --------
14 0 [14]
85 1 [14, 85]
77 1 [14, 77, 85]
26 1 [14, 26, 77, 85]
50 2 [14, 26, 50, 77, 85]
45 2 [14, 26, 45, 50, 77, 85]
66 4 [14, 26, 45, 50, 66, 77, 85]
79 6 [14, 26, 45, 50, 66, 77, 79, 85]
10 0 [10, 14, 26, 45, 50, 66, 77, 79, 85]
3 0 [3, 10, 14, 26, 45, 50, 66, 77, 79, 85]
84 9 [3, 10, 14, 26, 45, 50, 66, 77, 79, 84, 85]
77 8 [3, 10, 14, 26, 45, 50, 66, 77, 77, 79, 84, 85]
1 0 [1, 3, 10, 14, 26, 45, 50, 66, 77, 77, 79, 84, 85]
Ceci est un simple exemple. En fait, compte tenu de la quantité de données manipulées, il est plus rapide de simplifier la construction de la liste et la trier en une fois. Par contraste, pour des listes longues, des économies de temps et de mémoire considérables peuvent être obtenues à l’aide d’un algorithme de tri par insertion tel que celui-ci, spécialement quand l’opération comparant deux membres d’une liste requiert de coûteux calculs.
Traitement des Doublons
Le résultat de l’ensemble montré précédemment inclus une valeur répétée, 77
. Le module bisect
fournit deux manière de traiter les répétitions : les nouvelles valeurs peuvent être insérées soit à la gauche des valeurs existantes, soit à la droite. La fonction insort()
est actuellement un alias de insort_right()
, qui insère un item après une valeur existante. La fonction correspondante insort_left()
insère un item avant la valeur existante.
# bisect_example2.py
import bisect
# A series of random numbers
values = [14, 85, 77, 26, 50, 45, 66, 79, 10, 3, 84, 77, 1]
print('New Pos Contents')
print('--- --- --------')
# Use bisect_left and insort_left.
l = []
for i in values:
position = bisect.bisect_left(l, i)
bisect.insort_left(l, i)
print('{:3} {:3}'.format(i, position), l)
Quand la même donnée est manipulée par l’utilisation des fonctions bisect_left()
et insort_left()
, il en résulte la même liste triée mais l’insertion des positions est différente pour les valeurs dupliquées.
$ python3 bisect_example2.py
New Pos Contents
--- --- --------
14 0 [14]
85 1 [14, 85]
77 1 [14, 77, 85]
26 1 [14, 26, 77, 85]
50 2 [14, 26, 50, 77, 85]
45 2 [14, 26, 45, 50, 77, 85]
66 4 [14, 26, 45, 50, 66, 77, 85]
79 6 [14, 26, 45, 50, 66, 77, 79, 85]
10 0 [10, 14, 26, 45, 50, 66, 77, 79, 85]
3 0 [3, 10, 14, 26, 45, 50, 66, 77, 79, 85]
84 9 [3, 10, 14, 26, 45, 50, 66, 77, 79, 84, 85]
77 7 [3, 10, 14, 26, 45, 50, 66, 77, 77, 79, 84, 85]
1 0 [1, 3, 10, 14, 26, 45, 50, 66, 77, 77, 79, 84, 85]
Lire aussi
- Documentation de la bibliothèque standard pour bisect
- Wikipedia - Tri d’insertion - Une description de l’algorithme d’insertion de tri.
queue - Implémentation FIFO Thread-Safe
But : Fournir une implémentation FIFO de processus sûrs.
Le module queue
fournit une structure de données premier entré, premier sorti (FIFO) adaptée à la programmation multithread. Elle peut être utilisée pour passer des messages ou d’autres choses en toute sécurité entre les threads producteur et consommateur. Le verrouillage est géré pour l’appelant. Ainsi, de nombreux threads peuvent fonctionner avec la même instance de Queue
facilement et en toute sécurité. La taille de la Queue
(le nombre des éléments qu’elle contient) peut être restreinte à l’utilisation ou au traitement de la mémoire.
Note Cette discussion présume que vous comprenez la nature générale d’une file d’attente. Si ce n’est pas le cas, vous devriez lire certaines références avant de continuer.
File d’attente FIFO de base
La classe Queue
implémente un conteneur premier entré, premier sorti de base. Les éléments sont ajoutés à la «fin» de la séquence en utilisant la fonction put()
, et supprimé à la fin d’une autre par l’utilisation de get()
.
# queue_fifo.py
import queue
q = queue.Queue()
for i in range(5):
q.put(i)
while not q.empty():
print(q.get(), end=' ')
print()
Cet exemple utilise un unique thread pour illustrer comment les éléments sont supprimés de la file d’attente, dans le même ordre dans lequel ils ont été insérés.
$ python3 queue_fifo.py
0 1 2 3 4
File d’Attente LIFO
Par contraste à l’implémentation standard FIFO de Queue
, la LifoQueue
utilise l’ordre dernier entré, premier sorti (normalement associé à la pile de la structure de données).
# queue_lifo.py
import queue
q = queue.LifoQueue()
for i in range(5):
q.put(i)
while not q.empty():
print(q.get(), end=' ')
print()
L’élément le plus récemment entré dans la file d’attente est supprimé par get
.
$ python3 queue_lifo.py
4 3 2 1 0
Priorité dans la File d’Attente
Parfois, l’ordre de traitement des éléments d’une file d’attente doit être basé sur les caractéristiques de ces éléments, plutôt que sur l’ordre dans lequel ils ont été créés ou ajoutés à la file d’attente. Par exemple, les travaux d’impression du service de la comptabilité peuvent avoir priorité sur une liste de codes qu’un développeur souhaite imprimer. PriorityQueues
utilise l’ordre de tri de la file d’attente pour décider quel élément est à récupérer.
# queue_priority.py
import functools
import queue
import threading
@functools.total_ordering
class Job:
def __init__(self, priority, description):
self.priority = priority
self.description = description
print('New job:', description)
return
def __eq__(self, other):
try:
return self.priority == other.priority
except AttributeError:
return NotImplemented
def __lt__(self, other):
try:
return self.priority < other.priority
except AttributeError:
return NotImplemented
q = queue.PriorityQueue()
q.put(Job(3, 'Mid-level job'))
q.put(Job(10, 'Low-level job'))
q.put(Job(1, 'Important job'))
def process_job(q):
while True:
next_job = q.get()
print('Processing job:', next_job.description)
q.task_done()
workers = [
threading.Thread(target=process_job, args=(q,)),
threading.Thread(target=process_job, args=(q,)),
]
for w in workers:
w.setDaemon(True)
w.start()
q.join()
Cet exemple comporte de multiples threads consommant les travaux, qui sont traités en fonction de la priorité des éléments dans la file d’attente au moment où get()
est appelé. L’ordre de traitement des éléments ajoutés à la file d’attente, pendant l’exécution des threads consommateurs, dépend du basculement du contexte du thread.
$ python3 queue_priority.py
New job: Mid-level job
New job: Low-level job
New job: Important job
Processing job: Important job
Processing job: Mid-level job
Processing job: Low-level job
Construire un Thread Client de Podcast
Le code source pour un client de podcast dans ce chapitre démontre comment utiliser la classe Queue
avec de multiples threads. Le programme lit un ou plusieurs flux RSS, met en file d’attente les pièces jointes des cinq derniers épisodes de chaque flux à télécharger et traite plusieurs téléchargements en parallèle à l’aide de threads. La gestion des erreurs n’est pas suffisante pour une utilisation en production, mais l’implémentation du squelette illustre l’utilisation du module queue
.
En premier, certains paramètres de fonctionnement sont établis. Habituellement, ce sont des entrées d’utilisateur (p.ex. : des préférences ou d’une base de données). L’exemple utilise des valeurs codées en dur pour le nombre des threads et la liste des URL à récupérer.
# fetch_podcasts.py
from queue import Queue
import threading
import time
import urllib
from urllib.parse import urlparse
import feedparser
# Set up some global variables
num_fetch_threads = 2
enclosure_queue = Queue()
# A real app wouldn't use hard-coded data...
feed_urls = [
'http://talkpython.fm/episodes/rss',
]
def message(s):
print('{}: {}'.format(threading.current_thread().name, s))
La fonction download_enclosures()
s’exécute dans le thread de travail et traite les téléchargements en utilisant urllib
.
def download_enclosures(q):
"""This is the worker thread function.
It processes items in the queue one after
another. These daemon threads go into an
infinite loop, and exit only when
the main thread ends.
"""
while True:
message('looking for the next enclosure')
url = q.get()
filename = url.rpartition('/')[-1]
message('downloading {}'.format(filename))
response = urllib.request.urlopen(url)
data = response.read()
# Save the downloaded file to the current directory
message('writing to {}'.format(filename))
with open(filename, 'wb') as outfile:
outfile.write(data)
q.task_done()
Une fois que la fonction cible pour les threads est définie, le thread de travail peut être démarré. Quand download_enclosures()
s’occupe de l’instruction url = q.get()
, elle la bloque et attend jusqu’à ce que la file d’attente ait quelque chose à retourner. Cela signifie qu’il est prudent de démarrer les threads avant qu’il n’y ait quoi que ce soit dans la file d’attente.
# Set up some threads to fetch the enclosures
for i in range(num_fetch_threads):
worker = threading.Thread(
target=download_enclosures,
args=(enclosure_queue,),
name='worker-{}'.format(i),
)
worker.setDaemon(True)
worker.start()
L’étape suivante est de récupérer le contenu de flux en utilisant le module feedparser
et de mettre en file d’attente les URL. Dès que possible, la première URL est ajoutée à la file d’attente, un des threads de travail la prend et démarre le téléchargement. La boucle continue d’ajouter les éléments jusqu’à ce que le flux soit complet, et les threads de travail retirent à tour de rôle de la file d’attente les URL pour les télécharger.
# Download the feed(s) and put the enclosure URLs into
# the queue.
for url in feed_urls:
response = feedparser.parse(url, agent='fetch_podcasts.py')
for entry in response['entries'][:5]:
for enclosure in entry.get('enclosures', []):
parsed_url = urlparse(enclosure['url'])
message('queuing {}'.format(
parsed_url.path.rpartition('/')[-1]))
enclosure_queue.put(enclosure['url'])
Il ne reste plus qu’à attendre que la file se vide à nouveau, par l’utilisation de join()
.
# Now wait for the queue to be empty, indicating that we have
# processed all of the downloads.
message('*** main thread waiting')
enclosure_queue.join()
message('*** done')
L’exécution de cet exemple de script produit une sortie similaire à celle qui suit.
python3 fetch_podcasts.py
worker-0: looking for the next enclosure
worker-1: looking for the next enclosure
MainThread: queuing turbogears-and-the-future-of-python-web-frameworks.mp3
MainThread: queuing continuum-scientific-python-and-the-business-of-open-source.mp3
MainThread: queuing openstack-cloud-computing-built-on-python.mp3
MainThread: queuing pypy.js-pypy-python-in-your-browser.mp3
MainThread: queuing machine-learning-with-python-and-scikit-learn.mp3
MainThread: *** main thread waiting
worker-0: downloading turbogears-and-the-future-of-python-web-frameworks.mp3
worker-1: downloading continuum-scientific-python-and-the-business-of-open-source.mp3
worker-0: looking for the next enclosure
worker-0: downloading openstack-cloud-computing-built-on-python.mp3
worker-1: looking for the next enclosure
worker-1: downloading pypy.js-pypy-python-in-your-browser.mp3
worker-0: looking for the next enclosure
worker-0: downloading machine-learning-with-python-and-scikit-learn.mp3
worker-1: looking for the next enclosure
worker-0: looking for the next enclosure
MainThread: *** done
La sortie actuelle dépendra du contenu des flux RSS utilisés.
Lire aussi
- Documentation de la bibliothèque standard pour queue
- deque - File d’Attente Double dans le chapitre collections
- Structures de Données en Files d’Attente - Un article Wikipédia en anglais expliquant les files d’attentes.
- FIFO - Un article Wikipédia expliquant les structures de données, premier entré, premier sorti.
- module feedparser - Un module pour analyser les flux RSS et Atom, créé par Mark Pilgrim et maintenu par Kurt McKee.
struct - Structures de Données Binaires
But : Conversion entre chaînes et données binaires.
Le module struct
inclut des fonctions pour la conversion entre les octets de chaînes et les types de données Python natifs tels que les nombres et les chaînes.
Fonctions versus Classe Struct
Un ensemble de fonctions au niveau du module est disponible pour travailler avec des valeurs structurées, tout comme la classe Struct
. Les spécificateurs de format sont convertis depuis leur format de chaînes vers une représentation compilée, similaire à la façon dont sont pratiques les expressions régulières. La conversion nécessite certaines ressources, ainsi il est plus efficient de la faire en une fois lors de la création de l’instance Struct
et de l’appel des méthodes sur l’instance plutôt que d’utiliser les fonctions au niveau du module. Tous les exemples suivants utilisent la classe Struct
.
Emballage et Déballage
Les structures prennent en charge l’emballage de données dans les chaînes, et le déballage de données depuis les chaînes par l’usage des spécificateurs de format composés de caractères représentant le type des données et d’indicateurs facultatifs de comptage et de finalité. Se référer à la documentation standard de la bibliothèque pour avoir la liste complète des spécificateurs de format supportés.
Dans cet exemple, le spécificateur appelle comme valeurs, un entier, un entier long, et un nombre à virgule flottante. Les espaces dans le spécificateur de format sont inclus pour séparer le type d’indicateurs, et sont ignorés lorsque le format est compilé.
# struct_pack.py
import struct
import binascii
values = (1, 'ab'.encode('utf-8'), 2.7)
s = struct.Struct('I 2s f')
packed_data = s.pack(*values)
print('Original values:', values)
print('Format string :', s.format)
print('Uses :', s.size, 'bytes')
print('Packed Value :', binascii.hexlify(packed_data))
L’exemple convertit la valeur encodée en une séquence d’octets hexadécimaux pour l’imprimer avec la fonction binascii.hexlify()
, puisque certains des caractères sont nuls.
$ python3 struct_pack.py
Original values: (1, b'ab', 2.7)
Format string : I 2s f
Uses : 12 bytes
Packed Value : b'0100000061620000cdcc2c40'
Utilisez unpack()
pour extraire la donnée de sa représentation encodée.
# struct_unpack.py
import struct
import binascii
packed_data = binascii.unhexlify(b'0100000061620000cdcc2c40')
s = struct.Struct('I 2s f')
unpacked_data = s.unpack(packed_data)
print('Unpacked Values:', unpacked_data)
Passer la valeur condensée à unpack()
renvoie les mêmes valeurs (notez l’écart dans la valeur à virgule flottante).
$ python3 struct_unpack.py
Unpacked Values: (1, b'ab', 2.700000047683716)
Endianness
Par défaut, les valeurs sont codées à l’aide de la notion d’endianness de la bibliothèque C native. Il est facile de surcharger ce choix en fournissant une directive endianness explicite dans le format de chaînes.
# struct_endianness.py
import struct
import binascii
values = (1, 'ab'.encode('utf-8'), 2.7)
print('Original values:', values)
endianness = [
('@', 'native, native'),
('=', 'native, standard'),
('<', 'little-endian'),
('>', 'big-endian'),
('!', 'network'),
]
for code, name in endianness:
s = struct.Struct(code + ' I 2s f')
packed_data = s.pack(*values)
print()
print('Format string :', s.format, 'for', name)
print('Uses :', s.size, 'bytes')
print('Packed Value :', binascii.hexlify(packed_data))
print('Unpacked Value :', s.unpack(packed_data))
La table ci-dessous liste les spécificateurs d’ordre d’octets utilisés par Struct
.
+——+—————–+ | Code | Signification | +——+—————–+ | @ | Ordre natif | | = | Standard natif | | < | little-endian | | > | big-endian | | ! | Ordre réseau | +——+—————–+
$ python3 struct_endianness.py
Original values: (1, b'ab', 2.7)
Format string : @ I 2s f for native, native
Uses : 12 bytes
Packed Value : b'0100000061620000cdcc2c40'
Unpacked Value : (1, b'ab', 2.700000047683716)
Format string : = I 2s f for native, standard
Uses : 10 bytes
Packed Value : b'010000006162cdcc2c40'
Unpacked Value : (1, b'ab', 2.700000047683716)
Format string : < I 2s f for little-endian
Uses : 10 bytes
Packed Value : b'010000006162cdcc2c40'
Unpacked Value : (1, b'ab', 2.700000047683716)
Format string : > I 2s f for big-endian
Uses : 10 bytes
Packed Value : b'000000016162402ccccd'
Unpacked Value : (1, b'ab', 2.700000047683716)
Format string : ! I 2s f for network
Uses : 10 bytes
Packed Value : b'000000016162402ccccd'
Unpacked Value : (1, b'ab', 2.700000047683716)
Tampons
Travailler avec une donnée encodée en binaire est typiquement réservé pour les situations nécessitant de la performance ou de passer la donnée vers ou depuis des modules d’extensions. Ces cas peuvent être optimisés en évitant la surcharge liée à l’allocation d’un nouveau tampon pour chaque structure codée. Les méthodes pack_into()
et unpack_from()
prennent en charge l’écriture directe de tampons préalloués.
# struct_buffers.py
import array
import binascii
import ctypes
import struct
s = struct.Struct('I 2s f')
values = (1, 'ab'.encode('utf-8'), 2.7)
print('Original:', values)
print()
print('ctypes string buffer')
b = ctypes.create_string_buffer(s.size)
print('Before :', binascii.hexlify(b.raw))
s.pack_into(b, 0, *values)
print('After :', binascii.hexlify(b.raw))
print('Unpacked:', s.unpack_from(b, 0))
print()
print('array')
a = array.array('b', b'\0' * s.size)
print('Before :', binascii.hexlify(a))
s.pack_into(a, 0, *values)
print('After :', binascii.hexlify(a))
print('Unpacked:', s.unpack_from(a, 0))
L’attribut size
à Struct
nous dit la taille dont a besoin le tampon.
$ python3 struct_buffers.py
Original: (1, b'ab', 2.7)
ctypes string buffer
Before : b'000000000000000000000000'
After : b'0100000061620000cdcc2c40'
Unpacked: (1, b'ab', 2.700000047683716)
array
Before : b'000000000000000000000000'
After : b'0100000061620000cdcc2c40'
Unpacked: (1, b'ab', 2.700000047683716)
Lire aussi
- Documentation de la bibliothèque standard pour struct
- Notes de portage de Python 2 vers 3 pour struct
- array - Le module
array
, pour travailler avec des séquences de valeurs de type fixé binascii
- Le modulebinascii
, pour produire des représentations ASCII de donnée binaire.- Wikipédia : Endianess
weakref - Références Impermanentes aux Objets
But: Référer un objet «coûteux», tout en permettant à sa mémoire d’être réclamée par le ramasse miettes s’il n’y a pas d’autres références non faibles.
Le module weakref
prend en charge les références faibles aux objets. Une référence normale incrémente le compteur de référence sur l’objet et l’empêche d’être nettoyé. Ce résultat n’est pas toujours souhaitable, spécifiquement quand une référence circulaire doit être présente ou quand un cache d’objet doit être supprimé quand de la mémoire est nécessaire. Une référence faible est une poignée pour un objet qui ne l’empêche pas d’être nettoyé automatiquement.
Références
Les références faibles d’objets sont gérés au-travers de la classe ref
. Pour récupérer l’objet original, appelez la référence objet.
# weakref_ref.py
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
obj = ExpensiveObject()
r = weakref.ref(obj)
print('obj:', obj)
print('ref:', r)
print('r():', r())
print('deleting obj')
del obj
print('r():', r())
Dans ce cas, puisque obj
est supprimé avant le second appel à la référence, la ref
retourne None
.
$ python3 weakref_ref.py
obj: <__main__.ExpensiveObject object at 0x1007b1a58>
ref: <weakref at 0x1007a92c8; to 'ExpensiveObject' at
0x1007b1a58>
r(): <__main__.ExpensiveObject object at 0x1007b1a58>
deleting obj
(Deleting <__main__.ExpensiveObject object at 0x1007b1a58>)
r(): None
Rappels de Référence
Le constructeur ref
accepte une fonction optionnelle de rappel qui est invoquée quand la référence de l’objet est supprimée.
# weakref_ref_callback.py
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
def callback(reference):
"""Invoked when referenced object is deleted"""
print('callback({!r})'.format(reference))
obj = ExpensiveObject()
r = weakref.ref(obj, callback)
print('obj:', obj)
print('ref:', r)
print('r():', r())
print('deleting obj')
del obj
print('r():', r())
Le rappel reçoit la référence de l’objet en tant qu’argument après que la référence soit «morte» et ne se réfère plus à l’objet d’origine. Une utilisation de cette fonctionnalité est de supprimer la référence faible d’un objet depuis un cache.
$ python3 weakref_ref_callback.py
obj: <__main__.ExpensiveObject object at 0x1010b1978>
ref: <weakref at 0x1010a92c8; to 'ExpensiveObject' at
0x1010b1978>
r(): <__main__.ExpensiveObject object at 0x1010b1978>
deleting obj
(Deleting <__main__.ExpensiveObject object at 0x1010b1978>)
callback(<weakref at 0x1010a92c8; dead>)
r(): None
Finalisation des Objets
Pour une gestion plus robuste des ressources lorsque des références faibles sont nettoyées, utilisez la méthode finalize
pour associer les rappels aux objets. Une instance finalize
est retenue jusqu’à ce que l’objet attaché soit supprimé, même si l’application ne retient pas de référence à la fin.
# weakref_finalize.py
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
def on_finalize(*args):
print('on_finalize({!r})'.format(args))
obj = ExpensiveObject()
weakref.finalize(obj, on_finalize, 'extra argument')
del obj
Les arguments de finalize
sont les objets à suivre, un rappel à invoquer quand les objets sont nettoyés, et que les arguments de positions ou nommés sont passés au rappel.
$ python3 weakref_finalize.py
(Deleting <__main__.ExpensiveObject object at 0x1019b10f0>)
on_finalize(('extra argument',))
L’instance finalize
a une propriété en écriture nommée atexit
pour contrôler quand le rappel est invoqué lorsqu’un programme se termine, s’il n’a pas déjà été appelé.
# weakref_finalize_atexit.py
import sys
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
def on_finalize(*args):
print('on_finalize({!r})'.format(args))
obj = ExpensiveObject()
f = weakref.finalize(obj, on_finalize, 'extra argument')
f.atexit = bool(int(sys.argv[1]))
Le comportement par défaut est d’invoquer le rappel. Paramétrer atexit
sur false
désactivera ce comportement.
$ python3 weakref_finalize_atexit.py 1
on_finalize(('extra argument',))
(Deleting <__main__.ExpensiveObject object at 0x1007b10f0>)
$ python3 weakref_finalize_atexit.py 0
Donner à l’instance finalize
une référence vers l’objet à suivre a pour conséquence de le retenir, ainsi l’objet n’est jamais passé au ramasse miettes.
# weakref_finalize_reference.py
import gc
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
def on_finalize(*args):
print('on_finalize({!r})'.format(args))
obj = ExpensiveObject()
obj_id = id(obj)
f = weakref.finalize(obj, on_finalize, obj)
f.atexit = False
del obj
for o in gc.get_objects():
if id(o) == obj_id:
print('found uncollected object in gc')
Comme cet exemple le montre, même si la référence explicite vers obj
est supprimée, l’objet est retenu et visible par le ramasse miettes au-travers de f
.
$ python3 weakref_finalize_reference.py
found uncollected object in gc
Utiliser une méthode liée à un objet suivi en tant rappel peut également empêcher la finalisation correcte de l’objet.
# weakref_finalize_reference_method.py
import gc
import weakref
class ExpensiveObject:
def __del__(self):
print('(Deleting {})'.format(self))
def do_finalize(self):
print('do_finalize')
obj = ExpensiveObject()
obj_id = id(obj)
f = weakref.finalize(obj, obj.do_finalize)
f.atexit = False
del obj
for o in gc.get_objects():
if id(o) == obj_id:
print('found uncollected object in gc')
Puisque le rappel donné à finalize
est une méthode liée à l’instance obj
, l’objet de finalisation capture une référence vers obj
, qui ne peut être supprimé ou collecté par le ramasse miettes.
$ python3 weakref_finalize_reference_method.py
found uncollected object in gc
Mandataires
Il est parfois plus pratique d’utiliser un mandataire, plutôt qu’une référence faible. Les mandataires peuvent être utilisés comme s’il s’agissait de l’objet d’origine et n’ont pas besoin d’être appelés avant que l’objet ne soit accessible. En conséquence, ils peuvent être passés à une bibliothèque qui ne sait pas qu’elle reçoit une référence au lieu d’un objet réel.
# weakref_proxy.py
import weakref
class ExpensiveObject:
def __init__(self, name):
self.name = name
def __del__(self):
print('(Deleting {})'.format(self))
obj = ExpensiveObject('My Object')
r = weakref.ref(obj)
p = weakref.proxy(obj)
print('via obj:', obj.name)
print('via ref:', r().name)
print('via proxy:', p.name)
del obj
print('via proxy:', p.name)
Si le mandataire est accédé après que l’objet en référence soit supprimé, une erreur d’exception ReferenceError
est levée.
$ python3 weakref_proxy.py
via obj: My Object
via ref: My Object
via proxy: My Object
(Deleting <__main__.ExpensiveObject object at 0x1007aa7b8>)
Traceback (most recent call last):
File "weakref_proxy.py", line 30, in <module>
print('via proxy:', p.name)
ReferenceError: weakly-referenced object no longer exists
Mettre en Cache des Objets
Les classes ref
et obj
sont considérés de «bas niveau». Bien qu’elles soient utiles pour maintenir les références faibles d’objets individuels et de permettre des cycles pour être collectés par le ramasse miettes, les classes WeakKeyDictionary
et WeakValueDictionary
fournissent une API plus appropriée pour créer un cache de plusieurs objets.
La classe WeakValueDictionary
utilise les références faibles vers les valeurs qu’elle capture, leur permettant d’être collectées par le ramasse miettes lorsque tout autre code ne les utilise pas. L’utilisation des appels explicites au ramasse miettes illustre la différence entre la gestion de la mémoire avec un dictionnaire standard et WeakValueDictionary
:
# weakref_valuedict.py
import gc
from pprint import pprint
import weakref
gc.set_debug(gc.DEBUG_UNCOLLECTABLE)
class ExpensiveObject:
def __init__(self, name):
self.name = name
def __repr__(self):
return 'ExpensiveObject({})'.format(self.name)
def __del__(self):
print(' (Deleting {})'.format(self))
def demo(cache_factory):
# hold objects so any weak references
# are not removed immediately
all_refs = {}
# create the cache using the factory
print('CACHE TYPE:', cache_factory)
cache = cache_factory()
for name in ['one', 'two', 'three']:
o = ExpensiveObject(name)
cache[name] = o
all_refs[name] = o
del o # decref
print(' all_refs =', end=' ')
pprint(all_refs)
print('\n Before, cache contains:', list(cache.keys()))
for name, value in cache.items():
print(' {} = {}'.format(name, value))
del value # decref
# remove all references to the objects except the cache
print('\n Cleanup:')
del all_refs
gc.collect()
print('\n After, cache contains:', list(cache.keys()))
for name, value in cache.items():
print(' {} = {}'.format(name, value))
print(' demo returning')
return
demo(dict)
print()
demo(weakref.WeakValueDictionary)
Toutes variables de boucle faisant référence aux valeurs mises en cache doivent être explicitement effacées pour que le compte de références de l’objet soit décrémenté. Autrement, le ramasse miettes ne supprimera pas les objets, et ils resteront toujours dans le cache. De la même manière, la variable all_refs
est utilisée pour capturer les références afin de prévenir que le ramasse miettes les collecte prématurément.
$ python3 weakref_valuedict.py
CACHE TYPE: <class 'dict'>
all_refs = {'one': ExpensiveObject(one),
'three': ExpensiveObject(three),
'two': ExpensiveObject(two)}
Before, cache contains: ['one', 'three', 'two']
one = ExpensiveObject(one)
three = ExpensiveObject(three)
two = ExpensiveObject(two)
Cleanup:
After, cache contains: ['one', 'three', 'two']
one = ExpensiveObject(one)
three = ExpensiveObject(three)
two = ExpensiveObject(two)
demo returning
(Deleting ExpensiveObject(one))
(Deleting ExpensiveObject(three))
(Deleting ExpensiveObject(two))
CACHE TYPE: <class 'weakref.WeakValueDictionary'>
all_refs = {'one': ExpensiveObject(one),
'three': ExpensiveObject(three),
'two': ExpensiveObject(two)}
Before, cache contains: ['one', 'three', 'two']
one = ExpensiveObject(one)
three = ExpensiveObject(three)
two = ExpensiveObject(two)
Cleanup:
(Deleting ExpensiveObject(one))
(Deleting ExpensiveObject(three))
(Deleting ExpensiveObject(two))
After, cache contains: []
demo returning
La classe WeakKeyDictionary
fonctionne de manière similaire mais utilise les références faibles pour les clés au lieu des valeurs dans le dictionnaire.
Attention La documentation de la bibliothèque pour
weakref
contient cet avertissement : Mise en garde : Étant donné qu’unWeakValueDictionary
est construit sur un dictionnaire Python, sa taille ne doit pas changer lors de son itération. Cela peut être difficile à garantir pour unWeakValueDictionary
parce que les actions effectuées par le programme pendant l’itération peuvent entraîner la disparition «par magie» d’éléments du dictionnaire (c’est un effet de bord du ramasse miettes des collections).
Lire aussi
- Documentation de la bibliothèque standard pour weakref
- gc - le module
gc
est l’interface pour l’interprétateur du ramasse miettes. - PEP 205 - Proposition d’amélioration des “Références Faibles”.
copy - Objets Dupliqués
But : Fournir des fonctions pour dupliquer les objets en utilisant une sémantique de copie superficielle ou profonde
Le module copy
inclut deux fonctions, copy()
et deepcopy()
, pour dupliquer des objets existants.
Copies Superficielles
La copie superficielle créée par copy()
est un nouveau conteneur rempli de références vers les contenus de l’objet original. Lors de la copie superficielle d’un objet list
, une nouvelle list
est construite et les éléments de l’objet original y sont ajoutés.
# copy_shallow.py
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
a = MyClass('a')
my_list = [a]
dup = copy.copy(my_list)
print(' my_list:', my_list)
print(' dup:', dup)
print(' dup is my_list:', (dup is my_list))
print(' dup == my_list:', (dup == my_list))
print('dup[0] is my_list[0]:', (dup[0] is my_list[0]))
print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))
Pour une copie superficielle, l’instance MyClass
n’est pas dupliquée, ainsi la référence dans la liste dup
est le même objet que celui qui est dans my_list
.
$ python3 copy_shallow.py
my_list: [<__main__.MyClass object at 0x101f9c160>]
dup: [<__main__.MyClass object at 0x101f9c160>]
dup is my_list: False
dup == my_list: True
dup[0] is my_list[0]: True
dup[0] == my_list[0]: True
Copies Profondes
La copie profonde créée par deepcopy()
est un nouveau conteneur avec des copies des contenus de l’objet original. Pour faire une copie profonde d’une list
, une nouvelle list
est construite, les éléments de la liste originale sont copiés, et ensuite ces copies sont ajoutées à la nouvelle liste.
Remplacer l’appel de copy()
par deepcopy()
fait la différence dans la sortie apparente.
# copy_deep.py
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
a = MyClass('a')
my_list = [a]
dup = copy.deepcopy(my_list)
print(' my_list:', my_list)
print(' dup:', dup)
print(' dup is my_list:', (dup is my_list))
print(' dup == my_list:', (dup == my_list))
print('dup[0] is my_list[0]:', (dup[0] is my_list[0]))
print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))
Le premier élément de la liste n’a plus du tout la même référence objet, mais quand les deux objets sont comparés, ils sont toujours évalués comme égaux.
$ python3 copy_deep.py
my_list: [<__main__.MyClass object at 0x101e9c160>]
dup: [<__main__.MyClass object at 0x1044e1f98>]
dup is my_list: False
dup == my_list: True
dup[0] is my_list[0]: False
dup[0] == my_list[0]: True
Personnaliser le Comportement des Copies
Il est possible de contrôler comment les copies sont faites en utilisant les méthodes spéciales __copy__()
et __deepcopy__()
.
__copy__()
est appelée sans aucun argument et devrait retourner une copie superficielle de l’objet.__deepcopy__()
est appelée avec un dictionnaire memo et devrait retourner une copie profonde de l’objet. Tous les attributs membres qui ont besoin d’être copiés en profondeur devraient être passé verscopy.deepcopy()
, avec le dictionnaire memo, pour contrôler la récursion. (Le dictionnaire memo est expliqué plus tard en détails).
L’exemple suivant illustre comment les méthodes sont appelées.
# copy_hooks.py
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
def __copy__(self):
print('__copy__()')
return MyClass(self.name)
def __deepcopy__(self, memo):
print('__deepcopy__({})'.format(memo))
return MyClass(copy.deepcopy(self.name, memo))
a = MyClass('a')
sc = copy.copy(a)
dc = copy.deepcopy(a)
Le dictionnaire memo est utilisé pour garder la trace des valeurs qui ont été déjà copiées, évitant ainsi une récursion infinie.
$ python3 copy_hooks.py
__copy__()
__deepcopy__({})
Récursion dans les Copies Profondes
Pour éviter les problèmes lors de la duplication récursive des structures de données, deepcopy()
utilise un dictionnaire pour suivre les objets qui ont déjà été copiés. Le dictionnaire est passé à la méthode __deepcopy__()
afin qu’il puisse être examiné là aussi.
Le nouvel exemple montre comment une structure de données interconnectée telle qu’un graphe dirigé peut aider à protéger contre la récursivité, en implémentant la méthode __deepcopy__()
.
# copy_recursion.py
import copy
class Graph:
def __init__(self, name, connections):
self.name = name
self.connections = connections
def add_connection(self, other):
self.connections.append(other)
def __repr__(self):
return 'Graph(name={}, id={})'.format(
self.name, id(self))
def __deepcopy__(self, memo):
print('\nCalling __deepcopy__ for {!r}'.format(self))
if self in memo:
existing = memo.get(self)
print(' Already copied to {!r}'.format(existing))
return existing
print(' Memo dictionary:')
if memo:
for k, v in memo.items():
print(' {}: {}'.format(k, v))
else:
print(' (empty)')
dup = Graph(copy.deepcopy(self.name, memo), [])
print(' Copying to new object {}'.format(dup))
memo[self] = dup
for c in self.connections:
dup.add_connection(copy.deepcopy(c, memo))
return dup
root = Graph('root', [])
a = Graph('a', [root])
b = Graph('b', [a, root])
root.add_connection(a)
root.add_connection(b)
dup = copy.deepcopy(root)
La classe Graph
inclut certaines méthodes basiques de graphe dirigé. Une instance peut être initialisée avec un nom et une liste de nœuds existants auxquels il est connecté. La méthode add_connection()
est utilisée pour paramétrer les connexions bidirectionnelles. Elle est aussi utilisée par l’opérateur de copie profonde.
La méthode __deepcopy__()
imprime les messages pour montrer comment elle est appelée, et gère le contenu du dictionnaire memo selon les besoins. Au lieu de copier en gros la liste complète de connexions, il crée une nouvelle liste et y ajoute des copies des connexions individuelles. Cela permet que le dictionnaire memo soit mise à jour à chaque fois qu’un nouveau nœud est dupliqué, et évite les problèmes de récursion ou de copies supplémentaires de nœuds. Comme précédemment, la méthode retourne la copie d’objet quand il est fait.
Le graphe vu dans la figure inclut les nombreux cycles, mais la gestion de la récursion avec le dictionnaire memo empêche la traversée de provoquer une erreur de débordement de pile. Quand le nœud root est copié, il produit la sortie suivante.
$ python3 copy_recursion.py
Calling __deepcopy__ for Graph(name=root, id=4326183824)
Memo dictionary:
(empty)
Copying to new object Graph(name=root, id=4367233208)
Calling __deepcopy__ for Graph(name=a, id=4326186344)
Memo dictionary:
Graph(name=root, id=4326183824): Graph(name=root,
id=4367233208)
Copying to new object Graph(name=a, id=4367234720)
Calling __deepcopy__ for Graph(name=root, id=4326183824)
Already copied to Graph(name=root, id=4367233208)
Calling __deepcopy__ for Graph(name=b, id=4326183880)
Memo dictionary:
Graph(name=root, id=4326183824): Graph(name=root,
id=4367233208)
Graph(name=a, id=4326186344): Graph(name=a, id=4367234720)
4326183824: Graph(name=root, id=4367233208)
4367217936: [Graph(name=root, id=4326183824), Graph(name=a,
id=4326186344)]
4326186344: Graph(name=a, id=4367234720)
Copying to new object Graph(name=b, id=4367235000)
La seconde fois où le nœud root est rencontré, pendant que le nœud a soit copié, __deepcopy__()
détecte la récursion et réutilise la valeur existante du dictionnaire memo au lieu de créer un nouvel objet.
Lire aussi
pprint - Structures de Données Pretty-Print
But : Structures de données Pretty-print
Le module pprint
contient une «jolie imprimante» pour produire des vues esthétiques des structures de données. Le formateur produit des représentations de structures de données qui peuvent être analysées correctement par l’interpréteur, et qui sont aussi faciles à lire pour un humain. La sortie est conservée sur une seule ligne, si possible, et en retrait lorsqu’elle est fractionnée sur plusieurs lignes.
Les exemples dans ce chapitre dépendent tous du script pprint_data.py
, qui est montré ici.
# pprint_data.py
data = [
(1, {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}),
(2, {'e': 'E', 'f': 'F', 'g': 'G', 'h': 'H',
'i': 'I', 'j': 'J', 'k': 'K', 'l': 'L'}),
(3, ['m', 'n']),
(4, ['o', 'p', 'q']),
(5, ['r', 's', 't''u', 'v', 'x', 'y', 'z']),
]
Impression
La manière la plus simple d’utiliser le module est au-travers de la fonction pprint()
.
# pprint_pprint.py
from pprint import pprint
from pprint_data import data
print('PRINT:')
print(data)
print()
print('PPRINT:')
pprint(data)
pprint()
formate un objet et l’écrit dans le flux de données en le passant en argument (ou sys.stdout
par défaut).
$ python3 pprint_pprint.py
PRINT:
[(1, {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}), (2, {'e': 'E', 'f':
'F', 'g': 'G', 'h': 'H', 'i': 'I', 'j': 'J', 'k': 'K', 'l': 'L'}), (
3, ['m', 'n']), (4, ['o', 'p', 'q']), (5, ['r', 's', 'tu', 'v', 'x',
'y', 'z'])]
PPRINT:
[(1, {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}),
(2,
{'e': 'E',
'f': 'F',
'g': 'G',
'h': 'H',
'i': 'I',
'j': 'J',
'k': 'K',
'l': 'L'}),
(3, ['m', 'n']),
(4, ['o', 'p', 'q']),
(5, ['r', 's', 'tu', 'v', 'x', 'y', 'z'])]
Formatage
Pour formater une structure de données sans l’écrire directement vers un flux (par exemple, pour la journalisation) utilisez pformat()
pour construire une représentation de chaînes.
# pprint_pformat.py
import logging
from pprint import pformat
from pprint_data import data
logging.basicConfig(
level=logging.DEBUG,
format='%(levelname)-8s %(message)s',
)
logging.debug('Logging pformatted data')
formatted = pformat(data)
for line in formatted.splitlines():
logging.debug(line.rstrip())
La chaîne formatée peut alors être imprimée ou journalisée indépendamment.
$ python3 pprint_pformat.py
DEBUG Logging pformatted data
DEBUG [(1, {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}),
DEBUG (2,
DEBUG {'e': 'E',
DEBUG 'f': 'F',
DEBUG 'g': 'G',
DEBUG 'h': 'H',
DEBUG 'i': 'I',
DEBUG 'j': 'J',
DEBUG 'k': 'K',
DEBUG 'l': 'L'}),
DEBUG (3, ['m', 'n']),
DEBUG (4, ['o', 'p', 'q']),
DEBUG (5, ['r', 's', 'tu', 'v', 'x', 'y', 'z'])]
Classes Arbitraires
La classe PrettyPrinter
utilisée par pprint()
peut aussi fonctionner avec des classes personnalisées, si elles définissent une méthode __repr__()
.
# pprint_arbitrary_object.py
from pprint import pprint
class node:
def __init__(self, name, contents=[]):
self.name = name
self.contents = contents[:]
def __repr__(self):
return (
'node(' + repr(self.name) + ', ' +
repr(self.contents) + ')'
)
trees = [
node('node-1'),
node('node-2', [node('node-2-1')]),
node('node-3', [node('node-3-1')]),
]
pprint(trees)
Les représentations de ces objets imbriqués sont combinées par PrettyPrinter
pour retourner une représentation complète de chaînes.
$ python3 pprint_arbitrary_object.py
[node('node-1', []),
node('node-2', [node('node-2-1', [])]),
node('node-3', [node('node-3-1', [])])]
Récursion
Les structures de données récursives sont représentées avec une référence vers la source originale de données, restituées dans le format <Recursion on typename with id=number>
.
# pprint_recursion.py
from pprint import pprint
local_data = ['a', 'b', 1, 2]
local_data.append(local_data)
print('id(local_data) =>', id(local_data))
pprint(local_data)
Dans cet exemple, la liste local_data
est ajoutée à elle-même, créant une référence récursive.
$ python3 pprint_recursion.py
id(local_data) => 4358913288
['a', 'b', 1, 2, <Recursion on list with id=4358913288>]
Limiter la Sortie Imbriquée
Pour des structures de données très profondes, il peut ne pas être désirable que la sortie inclut tous les détails. La donnée peut ne pas être formatée proprement, le texte formaté est peut-être trop volumineux pour être géré, ou certaines données peuvent être superflues.
# pprint_depth.py
from pprint import pprint
from pprint_data import data
pprint(data, depth=1)
pprint(data, depth=2)
Utilisez l’argument depth
pour contrôler jusqu’où dans la structure de données imbriquée l’imprimante peut faire une récursion. Les niveaux non inclus dans la sortie sont représentés par une ellipse.
$ python3 pprint_depth.py
[(...), (...), (...), (...), (...)]
[(1, {...}), (2, {...}), (3, [...]), (4, [...]), (5, [...])]
Contrôler la Largeur de la Sortie
La sortie par défaut pour le texte formaté est de 80 colonnes. Pour ajuster la largeur, utilisez l’argument width
de pprint()
.
# pprint_width.py
from pprint import pprint
from pprint_data import data
for width in [80, 5]:
print('WIDTH =', width)
pprint(data, width=width)
print()
Quand la largeur est trop petite pour accommoder la structure de données formatée, les lignes ne sont pas tronquées, ni encapsulées au cas où cela introduirait une syntaxe non valide.
$ python3 pprint_width.py
WIDTH = 80
[(1, {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}),
(2,
{'e': 'E',
'f': 'F',
'g': 'G',
'h': 'H',
'i': 'I',
'j': 'J',
'k': 'K',
'l': 'L'}),
(3, ['m', 'n']),
(4, ['o', 'p', 'q']),
(5, ['r', 's', 'tu', 'v', 'x', 'y', 'z'])]
WIDTH = 5
[(1,
{'a': 'A',
'b': 'B',
'c': 'C',
'd': 'D'}),
(2,
{'e': 'E',
'f': 'F',
'g': 'G',
'h': 'H',
'i': 'I',
'j': 'J',
'k': 'K',
'l': 'L'}),
(3,
['m',
'n']),
(4,
['o',
'p',
'q']),
(5,
['r',
's',
'tu',
'v',
'x',
'y',
'z'])]
Le drapeau compact
demande à pprint()
d’essayer de mettre plus de données sur chaque ligne individuelle, plutôt que d’étendre des structures de données complexes sur plusieurs lignes.
# pprint_compact.py
from pprint import pprint
from pprint_data import data
print('DEFAULT:')
pprint(data, compact=False)
print('\nCOMPACT:')
pprint(data, compact=True)
Cet exemple montre que quand une structure de données ne tient pas sur une ligne, elle est scindée (comme pour le deuxième élément de la liste de données). Quand de multiples éléments peuvent tenir sur une ligne, comme avec le troisième ou quatrième membre, ils sont placés de cette façon.
$ python3 pprint_compact.py
DEFAULT:
[(1, {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}),
(2,
{'e': 'E',
'f': 'F',
'g': 'G',
'h': 'H',
'i': 'I',
'j': 'J',
'k': 'K',
'l': 'L'}),
(3, ['m', 'n']),
(4, ['o', 'p', 'q']),
(5, ['r', 's', 'tu', 'v', 'x', 'y', 'z'])]
COMPACT:
[(1, {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}),
(2,
{'e': 'E',
'f': 'F',
'g': 'G',
'h': 'H',
'i': 'I',
'j': 'J',
'k': 'K',
'l': 'L'}),
(3, ['m', 'n']), (4, ['o', 'p', 'q']),
(5, ['r', 's', 'tu', 'v', 'x', 'y', 'z'])]
*[API]: Application Programming Interface - Interface de Programmation Applicative *[PEP]: Python Enhancement Proposals - Propositions d’Amélioration Python *[RSS]: Really Simple Syndication - Syndication de contenu réellement simple *[URL]: Uniform Resource Locator - Repère uniforme de ressource *[p.ex.]: par exemple