Fondamentals du processus itératif

Beginner

This tutorial is from open-source community. Access the source code

Introduction

Cette section examine le processus sous-jacent d'itération.

Iteration partout

De nombreux objets différents prennent en charge l'itération.

a = 'hello'
for c in a: ## Itérer sur les caractères de a
  ...

b = { 'name': 'Dave', 'password': 'foo'}
for k in b: ## Itérer sur les clés dans le dictionnaire
  ...

c = [1,2,3,4]
for i in c: ## Itérer sur les éléments d'une liste/tuple
  ...

f = open('foo.txt')
for x in f: ## Itérer sur les lignes d'un fichier
  ...

Itération : Protocole

Considérez l'instruction for.

for x in obj:
    ## instructions

Que se passe-t-il en dessous des couvertures?

_iter = obj.__iter__()        ## Obtenir l'objet itérateur
while True:
    try:
        x = _iter.__next__()  ## Obtenir l'élément suivant
        ## instructions...
    except StopIteration:     ## Plus d'éléments
        break

Tous les objets qui fonctionnent avec la boucle for implémentent ce protocole d'itération de bas niveau.

Exemple : Itération manuelle sur une liste.

>>> x = [1,2,3]
>>> it = x.__iter__()
>>> it
<listiterator object at 0x590b0>
>>> it.__next__()
1
>>> it.__next__()
2
>>> it.__next__()
3
>>> it.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in? StopIteration
>>>

Prise en charge de l'itération

Il est utile de connaître l'itération si vous voulez l'ajouter à vos propres objets. Par exemple, pour créer un conteneur personnalisé.

class Portfolio:
    def __init__(self):
        self.holdings = []

    def __iter__(self):
        return self.holdings.__iter__()
  ...

port = Portfolio()
for s in port:
  ...

Exercice 6.1 : Illustration de l'itération

Créez la liste suivante :

a = [1,9,4,25,16]

Itérez manuellement sur cette liste. Appelez __iter__() pour obtenir un itérateur et appelez la méthode __next__() pour obtenir les éléments successifs.

>>> i = a.__iter__()
>>> i
<listiterator object at 0x64c10>
>>> i.__next__()
1
>>> i.__next__()
9
>>> i.__next__()
4
>>> i.__next__()
25
>>> i.__next__()
16
>>> i.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

La fonction intégrée next() est un raccourci pour appeler la méthode __next__() d'un itérateur. Essayez de l'utiliser sur un fichier :

>>> f = open('portfolio.csv')
>>> f.__iter__()    ## Note : Cela renvoie le fichier lui-même
<_io.TextIOWrapper name='portfolio.csv' mode='r' encoding='UTF-8'>
>>> next(f)
'name,shares,price\n'
>>> next(f)
'"AA",100,32.20\n'
>>> next(f)
'"IBM",50,91.10\n'
>>>

Appelez next(f) jusqu'à la fin du fichier. Regardez ce qui se passe.

Exercice 6.2 : Prise en charge de l'itération

Parfois, vous souhaiterez peut-être que l'un de vos propres objets prenne en charge l'itération - en particulier si votre objet encapsule une liste existante ou un autre itérable. Dans un nouveau fichier portfolio.py, définissez la classe suivante :

## portfolio.py

class Portfolio:

    def __init__(self, holdings):
        self._holdings = holdings

    @property
    def total_cost(self):
        return sum([s.cost for s in self._holdings])

    def tabulate_shares(self):
        from collections import Counter
        total_shares = Counter()
        for s in self._holdings:
            total_shares[s.name] += s.shares
        return total_shares

Cette classe est censée être une couche autour d'une liste, mais avec quelques méthodes supplémentaires telles que la propriété total_cost. Modifiez la fonction read_portfolio() dans report.py de manière à ce qu'elle crée une instance de Portfolio comme ceci :

## report.py

...

import fileparse
from stock import Stock
from portfolio import Portfolio

def read_portfolio(filename):
    '''
    Lit un fichier de portefeuille d'actions dans une liste de dictionnaires avec les clés
    name, shares et price.
    '''
    with open(filename) as file:
        portdicts = fileparse.parse_csv(file,
                                        select=['name','shares','price'],
                                        types=[str,int,float])

    portfolio = [ Stock(d['name'], d['shares'], d['price']) for d in portdicts ]
    return Portfolio(portfolio)

...

Essayez d'exécuter le programme report.py. Vous constaterez qu'il échoue de manière spectaculaire en raison du fait que les instances de Portfolio ne sont pas itérables.

>>> import report
>>> report.portfolio_report('portfolio.csv', 'prices.csv')
... plante...

Corrigez ce problème en modifiant la classe Portfolio pour qu'elle prenne en charge l'itération :

class Portfolio:

    def __init__(self, holdings):
        self._holdings = holdings

    def __iter__(self):
        return self._holdings.__iter__()

    @property
    def total_cost(self):
        return sum([s.shares*s.price for s in self._holdings])

    def tabulate_shares(self):
        from collections import Counter
        total_shares = Counter()
        for s in self._holdings:
            total_shares[s.name] += s.shares
        return total_shares

Après avoir effectué ce changement, votre programme report.py devrait fonctionner à nouveau. Pendant que vous y êtes, corrigez votre programme pcost.py pour utiliser le nouveau Portfolio objet. Comme ceci :

## pcost.py

import report

def portfolio_cost(filename):
    '''
    Calcule le coût total (shares*price) d'un fichier de portefeuille
    '''
    portfolio = report.read_portfolio(filename)
    return portfolio.total_cost
...

Testez-le pour vous assurer qu'il fonctionne :

>>> import pcost
>>> pcost.portfolio_cost('portfolio.csv')
44671.15
>>>

Exercice 6.3 : Création d'un conteneur plus approprié

Lorsque vous créez une classe de conteneur, vous voulez souvent faire plus que simplement itérer. Modifiez la classe Portfolio de sorte qu'elle ait d'autres méthodes spéciales comme ceci :

class Portfolio:
    def __init__(self, holdings):
        self._holdings = holdings

    def __iter__(self):
        return self._holdings.__iter__()

    def __len__(self):
        return len(self._holdings)

    def __getitem__(self, index):
        return self._holdings[index]

    def __contains__(self, name):
        return any([s.name == name for s in self._holdings])

    @property
    def total_cost(self):
        return sum([s.shares*s.price for s in self._holdings])

    def tabulate_shares(self):
        from collections import Counter
        total_shares = Counter()
        for s in self._holdings:
            total_shares[s.name] += s.shares
        return total_shares

Maintenant, essayez quelques expériences avec cette nouvelle classe :

>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> len(portfolio)
7
>>> portfolio[0]
Stock('AA', 100, 32.2)
>>> portfolio[1]
Stock('IBM', 50, 91.1)
>>> portfolio[0:3]
[Stock('AA', 100, 32.2), Stock('IBM', 50, 91.1), Stock('CAT', 150, 83.44)]
>>> 'IBM' in portfolio
True
>>> 'AAPL' in portfolio
False
>>>

Une observation importante à propos de ceci - généralement, le code est considéré comme "Pythonique" s'il utilise le vocabulaire commun de la manière dont les autres parties de Python fonctionnent normalement. Pour les objets conteneurs, prendre en charge l'itération, l'indexation, la containment et d'autres types d'opérateurs est une partie importante de cela.

Sommaire

Félicitations ! Vous avez terminé le laboratoire sur le protocole d'itération. Vous pouvez pratiquer d'autres laboratoires sur LabEx pour améliorer vos compétences.