4.2. Les itérables personnalisés¶
Nous allons maintenant voir comment il est possible de rendre nos objets itérables.
Pour illustrer cela, écrivons une classe DNASeq
qui représente une séquence d’ADN.
La séquence est reçue par le constructeur sous forme de chaîne de caractères et stockée dans un attribut public sequence
.
Nous ajoutons à notre classe une méthode privée de vérification qui a pour objectif de lever une ValueError
au cas où la chaîne fournie au constructeur contient autre chose que les quatre bases acceptées A
, T
, C
et G
.
De plus, nous définissons deux méthodes publiques get_complement()
et get_base_counts()
, ainsi que la méthode spéciale __str__()
qui retourne simplement la valeur de l’attribut sequence
afin que les objets de classe puisse être automatiquement représentés sous forme textuelle.
class DNASeq:
__complements = {'A': 'T', 'T': 'A', 'C': 'G', 'G': 'C'}
def __validate_sequence(self, sequence):
valid_bases = self.__complements.keys()
for base in sequence:
if base not in valid_bases:
raise ValueError(f"Base invalide : {base}.")
def __init__(self, sequence):
self.__validate_sequence(sequence)
self.sequence = sequence
def get_complement(self):
complement = ""
for base in self.sequence:
complement += self.__complements[base]
return DNASeq(complement)
def get_base_counts(self):
counts = {'A': 0, 'T': 0, 'C': 0, 'G': 0}
for base in self.sequence:
counts[base] += 1
return counts
def __str__(self):
return self.sequence
seq = DNASeq("TAGTCATTG")
print(seq)
print(seq.get_complement())
print(seq.get_base_counts())
for base in seq.sequence:
print(base)
TAGTCATTG
ATCAGTAAC
{'A': 2, 'T': 4, 'C': 1, 'G': 2}
T
A
G
T
C
A
T
T
G
Puisque nous avons redéfini __str__()
, il est possible d’afficher directement l’objet seq
sans avoir besoin d’accéder à son attribut sequence
.
Cependant, lorsque nous souhaitons parcourir la séquence à l’aide d’une boucle for
, il n’est pour l’instant pas possible de le faire directement sur l’objet de type DNASeq
.
Nous allons précisément remédier à cet inconvénient en implémentant la méthode spéciale __iter__()
de notre classe qui doit impérativement renvoyer un itérateur, ici sur l’attribut sequence
.
Cela nous permettra en outre de ne plus devoir exposer l’attribut sequence
et de le rendre privé (__sequence
).
On pourra ainsi garantir que la séquence ne peut pas être modifiée après avoir été validée par __validate_sequence()
lors de son initialisation.
La version finale de notre programme est présentée ci-dessous.
class DNASeq:
__complements = {'A': 'T', 'T': 'A', 'C': 'G', 'G': 'C'}
def __validate_sequence(self, sequence):
valid_bases = self.__complements.keys()
for base in sequence:
if base not in valid_bases:
raise ValueError(f"Base invalide : {base}.")
def __init__(self, sequence):
self.__validate_sequence(sequence)
self.__sequence = sequence
def get_complement(self):
complement = ""
# On peut désormais itérer sur self à l'intérieur de la classe
for base in self:
complement += self.__complements[base]
return DNASeq(complement)
def get_base_counts(self):
counts = {'A': 0, 'T': 0, 'C': 0, 'G': 0}
for base in self:
counts[base] += 1
return counts
def __str__(self):
return self.__sequence
def __iter__(self):
return self.__sequence.__iter__()
seq = DNASeq("TAGTCATTG")
for base in seq: # on itère maintenant directement sur l'objet `seq`
print(base)
La séquence affichée par la boucle for
est identique mais notre objet est désormais bien plus agréable à utiliser pour l’itération.