-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathiteratori.html
394 lines (316 loc) · 34.5 KB
/
iteratori.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
<!DOCTYPE html>
<meta charset=utf-8>
<title>Classi & iteratori - Immersione in Python 3</title>
<!--[if IE]><script src=j/html5.js></script><![endif]-->
<link rel=stylesheet href=dip3.css>
<style>
body{counter-reset:h1 7}
</style>
<link rel=stylesheet media='only screen and (max-device-width: 480px)' href=mobile.css>
<link rel=stylesheet media=print href=print.css>
<meta name=viewport content='initial-scale=1.0'>
<form action=http://www.google.com/cse><div><input type=hidden name=cx value=014021643941856155761:l5eihuescdw><input type=hidden name=ie value=UTF-8> <input type=search name=q size=25 placeholder="powered by Google™"> <input type=submit name=sa value=Search></div></form>
<p>Voi siete qui: <a href=index.html>Inizio</a> <span class=u>‣</span> <a href=indice.html#iteratori>Immersione in Python 3</a> <span class=u>‣</span>
<p id=level>Livello di difficoltà: <span class=u title=intermedio>♦♦♦♢♢</span>
<h1>Classi <i class=baa>&</i> iteratori</h1>
<blockquote class=q>
<p><span class=u>❝</span> L’Oriente è l’Oriente e l’Occidente è l’Occidente, e mai i due si incontreranno. <span class=u>❞</span><br>— <a href=http://en.wikiquote.org/wiki/Rudyard_Kipling>Rudyard Kipling</a>
</blockquote>
<p id=toc>
<h2 id=divingin>Immersione!</h2>
<p class=f>In verità, gli iteratori sono l’“ingrediente segreto” di Python 3: si trovano ovunque, nascosti dietro le quinte di ogni funzionalità, sempre appena fuori vista. Le <a href=descrizioni.html>descrizioni</a> sono solo un caso speciale di <i>iteratori</i>. Anche i generatori sono solo un caso speciale di <i>iteratori</i>: una funzione che produce valori tramite <code>yield</code> è un modo gradevole e compatto di costruire un iteratore senza costruire un iteratore. Lasciate che vi mostri cosa voglio dire.
<p>Ricordate il <a href=generatori.html#a-fibonacci-generator>generatore di Fibonacci</a>? Qui di seguito lo trovate reimplementato da zero sotto forma di iteratore:
<p class=d>[<a href=esempi/fibonacci2.py>scarica <code>fibonacci2.py</code></a>]
<pre class=pp><code>class Fib:
'''iteratore che produce i numeri della sequenza di Fibonacci'''
def __init__(self, max):
self.max = max
def __iter__(self):
self.a = 0
self.b = 1
return self
def __next__(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib</code></pre>
<p>Analizziamolo una riga alla volta.
<pre class='nd pp'><code>class Fib:</code></pre>
<p><code>class</code>? Classe? Che cos’è una classe?
<p class=a>⁂
<h2 id=defining-classes>Definire le classi</h2>
<p>Python è completamente orientato agli oggetti: potete definire le vostre classi, ereditare dalle vostre classi o dalle classi built-in e istanziare le classi che avete definito.
<p>Definire una classe in Python è semplice. Come per le funzioni, non c’è una definizione di interfaccia separata. Vi basta definire la classe e cominciare a programmarla. Una classe Python comincia con la parola riservata <code>class</code>, seguita dal nome della classe. Tecnicamente, questo è tutto quello che è richiesto, in quanto una classe non ha bisogno di ereditare da nessun’altra classe.
<pre class=pp><code><a>class PapayaWhip: <span class=u>①</span></a>
<a> pass <span class=u>②</span></a></code></pre>
<ol>
<li>Il nome di questa classe è <code>PapayaWhip</code>, e non eredita da nessun’altra classe. I nomi delle classi solitamente cominciano con una lettera maiuscola, <code>OgniParolaInQuestoModo</code>, ma questa è solo una convenzione, non un obbligo.
<li>Lo avrete probabilmente già indovinato, ma ogni cosa in una classe è indentata, esattamente come il codice all’interno di una funzione, di un’istruzione <code>if</code>, di un ciclo <code>for</code>, o di ogni altro blocco di codice. La prima riga non indentata non fa più parte della definizione di classe.
</ol>
<p>Questa classe <code>PapayaWhip</code> non definisce alcun metodo o attributo, ma sintatticamente la definizione deve contenere qualcosa, ed ecco il motivo per la presenza di quella istruzione <code>pass</code>. Questa è una parola riservata di Python che significa solo “circolate, non c’è nulla da vedere”. È un’istruzione che non fa nulla, e un buon segnaposto da sfruttare quando state definendo lo scheletro di funzioni o classi.
<blockquote class='note compare java'>
<p><span class=u>☞</span>L’istruzione <code>pass</code> in Python è come un insieme vuoto di parentesi graffe (<code>{}</code>) in Java o C.
</blockquote>
<p>Molte classi ereditano da altre classi, ma questa non lo fa. Molte classi definiscono metodi, ma questa non lo fa. Non c’è nulla che una classe Python debba assolutamente avere, a parte un nome. In particolare, i programmatori <abbr>C++</abbr> potrebbero trovare strano che le classi Python non abbiano costruttori e distruttori espliciti. Sebbene non sia obbligatorio, le classi Python <em>possono</em> avere qualcosa di simile a un costruttore: il metodo <code>__init__()</code>.
<h3 id=init-method>Il metodo <code>__init__()</code></h3>
<p>Questo esempio mostra l’inizializzazione della classe <code>Fib</code> usando il metodo <code>__init__()</code>.
<pre class=pp><code>class Fib:
<a> '''iteratore che produce i numeri della sequenza di Fibonacci''' <span class=u>①</span></a>
<a> def __init__(self, max): <span class=u>②</span></a></code></pre>
<ol>
<li>Anche le classi possono (e dovrebbero) avere una <code>docstring</code>, esattamente come i moduli e le funzioni.
<li>Il metodo <code>__init__()</code> viene chiamato immediatamente dopo la creazione di un’istanza della classe. Sarebbe allettante — ma tecnicamente scorretto — dire che questo metodo è il “costruttore” della classe. È allettante perché il metodo somiglia a un costruttore <abbr>C++</abbr> (per convenzione, il metodo <code>__init__()</code> è il primo metodo definito nella classe), agisce come un costruttore (è il primo frammento di codice eseguito in un’istanza appena creata della classe) e ha persino un nome che suona come quello di un costruttore. Ma è scorretto perché l’oggetto è già stato costruito nel momento in cui il metodo <code>__init__()</code> viene invocato, e un riferimento valido alla nuova istanza della classe è già disponibile.
</ol>
<p>Il primo argomento di ogni metodo in una classe, compreso il metodo <code>__init__()</code>, è sempre un riferimento all’istanza corrente della classe. Per convenzione, questo argomento viene chiamato <var>self</var>. Questo argomento occupa il ruolo della parola riservata <code>this</code> in <abbr>C++</abbr> o Java, ma <var>self</var> non è una parola riservata in Python, bensì semplicemente una convenzione nominale. Nondimeno, per favore non chiamatelo in modi diversi da <var>self</var> perché questa è una convenzione molto forte.
<p>Nel metodo <code>__init__()</code> <var>self</var> si riferisce all’oggetto appena creato; in altri metodi della classe si riferisce all’istanza il cui metodo è stato invocato. Sebbene abbiate bisogno di specificare esplicitamente <var>self</var> quando definite il metodo, <em>non</em> dovete specificarlo quando chiamate il metodo; Python lo aggiungerà per voi automaticamente.
<p class=a>⁂
<h2 id=instantiating-classes>Istanziare le classi</h2>
<p>Istanziare le classi in Python è semplice. Per istanziare una classe vi basta invocare la classe come se fosse una funzione, passando gli argomenti richiesti dal metodo <code>__init__()</code>. Il valore di ritorno sarà l’oggetto appena creato.
<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>import fibonacci2</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>fib = fibonacci2.Fib(100)</kbd> <span class=u>①</span></a>
<a><samp class=p>>>> </samp><kbd class=pp>fib</kbd> <span class=u>②</span></a>
<samp><fibonacci2.Fib object at 0x00DB8810></samp>
<a><samp class=p>>>> </samp><kbd class=pp>fib.__class__</kbd> <span class=u>③</span></a>
<samp class=pp><class 'fibonacci2.Fib'></samp>
<a><samp class=p>>>> </samp><kbd class=pp>fib.__doc__</kbd> <span class=u>④</span></a>
<samp class=pp>'iteratore che produce i numeri della sequenza di Fibonacci'</samp></pre>
<ol>
<li>State creando un’istanza della classe <code>Fib</code> (definita nel modulo <code>fibonacci2</code>) e assegnando l’istanza appena creata alla variabile <var>fib</var>. State passando un parametro, <code>100</code>, che finirà per diventare l’argomento <var>max</var> del metodo <code>__init__()</code> in <code>Fib</code>.
<li><var>fib</var> è ora un’istanza della classe <code>Fib</code>.
<li>Ogni istanza di una classe ha un attributo built-in chiamato <code>__class__</code> che è la classe dell’oggetto. I programmatori Java potrebbero avere familiarità con la classe <code>Class</code>, che contiene metodi come <code>getName()</code> e <code>getSuperclass()</code> per ottenere metainformazioni su un oggetto. In Python, questo tipo di metadati si può ottenere attraverso gli attributi, ma l’idea è la stessa.
<li>Potete accedere alla <code>docstring</code> dell’istanza esattamente come con una funzione o un modulo. Tutte le istanze di una classe condividono la stessa <code>docstring</code>.
</ol>
<blockquote class='note compare java'>
<p><span class=u>☞</span>In Python, vi basta invocare una classe come se fosse una funzione per creare una nuova istanza della classe. Non c’è nessun operatore <code>new</code> esplicito come in <abbr>C++</abbr> o in Java.
</blockquote>
<p class=a>⁂
<h2 id=instance-variables>Variabili di istanza</h2>
<p>Sulla riga successiva troviamo:
<pre class=pp><code>class Fib:
def __init__(self, max):
<a> self.max = max <span class=u>①</span></a></code></pre>
<ol>
<li>Che cos’è <var>self.max</var>? È una variabile di istanza. È completamente separata da <var>max</var>, che è passato al metodo <code>__init__()</code> come un argomento. <var>self.max</var> è “globale” per l’istanza. Questo significa che potete accedervi da altri metodi.
</ol>
<pre class=pp><code>class Fib:
def __init__(self, max):
<a> self.max = max <span class=u>①</span></a>
.
.
.
def __next__(self):
fib = self.a
<a> if fib > self.max: <span class=u>②</span></a></code></pre>
<ol>
<li><var>self.max</var> è definito nel metodo <code>__init__()</code>…
<li>…e riferito nel metodo <code>__next__()</code>.
</ol>
<p>Le variabili di istanza sono specifiche per un’istanza di una classe. Per esempio, se create due istanze di <code>Fib</code> con differenti valori massimi, ognuna manterrà in memoria il proprio valore.
<pre class='nd screen'>
<samp class=p>>>> </samp><kbd class=pp>import fibonacci2</kbd>
<samp class=p>>>> </samp><kbd class=pp>fib1 = fibonacci2.Fib(100)</kbd>
<samp class=p>>>> </samp><kbd class=pp>fib2 = fibonacci2.Fib(200)</kbd>
<samp class=p>>>> </samp><kbd class=pp>fib1.max</kbd>
<samp class=pp>100</samp>
<samp class=p>>>> </samp><kbd class=pp>fib2.max</kbd>
<samp class=pp>200</samp></pre>
<p class=a>⁂
<h2 id=a-fibonacci-iterator>Un iteratore di Fibonacci</h2>
<p><em>Ora</em> siete pronti per imparare come si costruisce un iteratore. Un iteratore è semplicemente una classe che definisce un metodo <code>__iter__()</code>.
<aside class=ots>
Tutti e tre i metodi di classe <code>__init__</code>, <code>__iter__</code> e <code>__next__</code> cominciano e finiscono con una coppia di caratteri di sottolineatura (<code>_</code>). Come mai? Non c’è nulla di magico in questo, ma di solito indica che questi metodi sono “<dfn>metodi speciali</dfn>”. L’unica cosa “speciale” dei metodi speciali è che non vengono invocati direttamente, ma Python li chiama quando usate qualche altra sintassi sulla classe o su un’istanza della classe. <a href=nomi-dei-metodi-speciali.html>Ulteriori informazioni sui metodi speciali</a>.
</aside>
<p class=d>[<a href=esempi/fibonacci2.py>scarica <code>fibonacci2.py</code></a>]
<pre class=pp><code><a>class Fib: <span class=u>①</span></a>
<a> def __init__(self, max): <span class=u>②</span></a>
self.max = max
<a> def __iter__(self): <span class=u>③</span></a>
self.a = 0
self.b = 1
return self
<a> def __next__(self): <span class=u>④</span></a>
fib = self.a
if fib > self.max:
<a> raise StopIteration <span class=u>⑤</span></a>
self.a, self.b = self.b, self.a + self.b
<a> return fib <span class=u>⑥</span></a></code></pre>
<ol>
<li>Per costruire un iteratore partendo da zero, <code>Fib</code> deve essere una classe, non una funzione.
<li>“Invocare” <code>Fib(max)</code> vuol dire in realtà creare un’istanza di questa classe e chiamare il suo metodo <code>__init__()</code> con <var>max</var>. Il metodo <code>__init__()</code> salva il valore massimo come una variabile di istanza in modo che altri metodi possano usarlo più tardi.
<li>Il metodo <code>__iter__()</code> viene invocato ogni volta che qualcuno chiama <code>iter(fib)</code>. (Come vedrete fra un minuto, un ciclo <code>for</code> chiama questo metodo automaticamente, ma potete chiamarlo anche voi manualmente.) Dopo aver eseguito l’inizializzazione che prepara l’iterazione (in questo caso, impostando i valori di <code>self.a</code> e <code>self.b</code>, i nostri due contatori), il metodo <code>__iter__()</code> può restituire qualsiasi oggetto che implementi un metodo <code>__next__()</code>. In questo caso (e nella maggior parte dei casi) <code>__iter__()</code> restituisce semplicemente <var>self</var>, dato che questa classe implementa il proprio metodo <code>__next__()</code>.
<li>Il metodo <code>__next__()</code> viene invocato ogni volta che qualcuno chiama <code>next()</code> su un iteratore di un’istanza di una classe. Questo avrà più senso fra un minuto.
<li>Quando il metodo <code>__next__()</code> solleva un’eccezione di tipo <code>StopIteration</code>, questo segnala al chiamante che l’iterazione è esaurita. A differenza della maggior parte delle eccezioni questa non rappresenta un errore, ma una condizione normale che significa solamente che l’iteratore non ha più valori da generare. Se il chiamante è un ciclo <code>for</code>, noterà questa eccezione di tipo <code>StopIteration</code> e uscirà normalmente dal ciclo. (In altre parole, sopprimerà l’eccezione.) Questo piccolo tocco di magia è in realtà la chiave per usare gli iteratori nei cicli <code>for</code>.
<li>Per produrre in uscita il valore successivo, il metodo <code>__next__()</code> di un iteratore restituisce semplicemente il valore tramite l’istruzione <code>return</code>. Qui <code>yield</code> non viene usato; quello è zucchero sintattico che si applica solo quando state usando i generatori. Qui invece state creando il vostro iteratore da zero; quindi, usate <code>return</code>.
</ol>
<p>Non siete ancora completamente confusi? Eccellente. Vediamo come invocare questo iteratore:
<pre class='nd screen'>
<samp class=p>>>> </samp><kbd class=pp>from fibonacci2 import Fib</kbd>
<samp class=p>>>> </samp><kbd class=pp>for n in Fib(1000):</kbd>
<samp class=p>... </samp><kbd class=pp> print(n, end=' ')</kbd>
<samp class=pp>0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987</samp></pre>
<p>Ehi, è esattamente lo stesso! Identico byte per byte al modo di invocare il <a href=generatori.html#a-fibonacci-generator>generatore di Fibonacci</a> (a parte una lettera maiuscola). Ma com’è possibile?
<p>C’è un pizzico di magia nel funzionamento dei cicli <code>for</code>. Ecco cosa succede.
<ul>
<li>Il ciclo <code>for</code> invoca <code>Fib(1000)</code>, come mostrato. Questo restituisce un’istanza della classe <code>Fib</code>. Chiamiamo questa istanza <var>fib_inst</var>.
<li>Segretamente, e piuttosto ingegnosamente, il ciclo <code>for</code> chiama <code>iter(fib_inst)</code>, che restituisce un oggetto iteratore. Chiamiamo questo oggetto <var>fib_iter</var>. In questo caso <var>fib_iter</var> == <var>fib_inst</var>, perché il metodo <code>__iter__()</code> restituisce <var>self</var> anche se il ciclo <code>for</code> non lo sa (o non se ne cura).
<li>Per “attraversare” l’iteratore, il ciclo <code>for</code> chiama <code>next(fib_iter)</code>, che chiama il metodo <code>__next__()</code> sull’oggetto <var>fib_iter</var>, che calcola il successivo numero di Fibonacci e restituisce un valore. Il ciclo <code>for</code> prende questo valore e lo assegna a <var>n</var>, poi esegue il corpo del ciclo <code>for</code> per quel valore di <var>n</var>.
<li>Come fa il ciclo <code>for</code> a sapere quando fermarsi? Sono contento che lo abbiate chiesto! Quando <code>next(fib_iter)</code> solleva un’eccezione di tipo <code>StopIteration</code>, il ciclo <code>for</code> sopprimerà l’eccezione e uscirà normalmente. (Ogni altra eccezione vi passerà attraverso e verrà sollevata come al solito.) E dov’è che avete visto un’eccezione di tipo <code>StopIteration</code>? Nel metodo <code>__next__()</code>, naturalmente!
</ul>
<p class=a>⁂
<h2 id=a-plural-rule-iterator>Un iteratore di regole per i sostantivi plurali</h2>
<aside>iter(f) chiama f.__iter__<br>next(f) chiama f.__next__</aside>
<p>Ora è il momento del gran finale. Riscriviamo il <a href=generatori.html>generatore di regole per i sostantivi plurali</a> sotto forma di iteratore.
<p class=d>[<a href=esempi/plural6.py>scarica <code>plural6.py</code></a>]
<pre class=pp><code>class LazyRules:
rules_filename = 'plural6-rules.txt'
def __init__(self):
self.pattern_file = open(self.rules_filename, encoding='utf-8')
self.cache = []
def __iter__(self):
self.cache_index = 0
return self
def __next__(self):
self.cache_index += 1
if len(self.cache) >= self.cache_index:
return self.cache[self.cache_index - 1]
if self.pattern_file.closed:
raise StopIteration
line = self.pattern_file.readline()
if not line:
self.pattern_file.close()
raise StopIteration
pattern, search, replace = line.split(None, 3)
funcs = build_match_and_apply_functions(
pattern, search, replace)
self.cache.append(funcs)
return funcs
rules = LazyRules()</code></pre>
<p>Così questa classe implementa <code>__iter__()</code> e <code>__next__()</code> e perciò può essere usata come un iteratore. Dunque, ne create un’istanza e l’assegnate a <var>rules</var>. Questo accade solo una volta, al momento di importare il modulo.
<p>Analizziamo la classe un pezzo alla volta.
<pre class=pp><code>class LazyRules:
rules_filename = 'plural6-rules.txt'
def __init__(self):
<a> self.pattern_file = open(self.rules_filename, encoding='utf-8') <span class=u>①</span></a>
<a> self.cache = [] <span class=u>②</span></a></code></pre>
<ol>
<li>Quando istanziamo la classe <code>LazyRules</code>, apriamo il file dei pattern ma non lo leggiamo. (Lo faremo più tardi.)
<li>Dopo aver aperto il file dei pattern, inizializzate la cache che userete più tardi (nel metodo <code>__next__()</code>) durante la lettura dei pattern dal file.
</ol>
<p>Prima di continuare, diamo un’occhiata più da vicino a <var>rules_filename</var>. Non è definita all’interno del metodo <code>__iter__()</code>. In effetti, non è definita all’interno di <em>nessun</em> metodo. È definita a livello della classe. È una <i>variabile di classe</i>, e sebbene possiate accedervi esattamente come una variabile di istanza (<var>self.rules_filename</var>), è condivisa da tutte le istanze della classe <code>LazyRules</code>.
<pre class=screen>
<samp class=p>>>> </samp><kbd class=pp>import plural6</kbd>
<samp class=p>>>> </samp><kbd class=pp>r1 = plural6.LazyRules()</kbd>
<samp class=p>>>> </samp><kbd class=pp>r2 = plural6.LazyRules()</kbd>
<a><samp class=p>>>> </samp><kbd class=pp>r1.rules_filename</kbd> <span class=u>①</span></a>
<samp class=pp>'plural6-rules.txt'</samp>
<samp class=p>>>> </samp><kbd class=pp>r2.rules_filename</kbd>
<samp class=pp>'plural6-rules.txt'</samp>
<a><samp class=p>>>> </samp><kbd class=pp>r2.rules_filename = 'r2-override.txt'</kbd> <span class=u>②</span></a>
<samp class=p>>>> </samp><kbd class=pp>r2.rules_filename</kbd>
<samp class=pp>'r2-override.txt'</samp>
<samp class=p>>>> </samp><kbd class=pp>r1.rules_filename</kbd>
<samp class=pp>'plural6-rules.txt'</samp>
<a><samp class=p>>>> </samp><kbd class=pp>r2.__class__.rules_filename</kbd> <span class=u>③</span></a>
<samp class=pp>'plural6-rules.txt'</samp>
<a><samp class=p>>>> </samp><kbd class=pp>r2.__class__.rules_filename = 'papayawhip.txt'</kbd> <span class=u>④</span></a>
<samp class=p>>>> </samp><kbd class=pp>r1.rules_filename</kbd>
<samp class=pp>'papayawhip.txt'</samp>
<a><samp class=p>>>> </samp><kbd class=pp>r2.rules_filename</kbd> <span class=u>⑤</span></a>
<samp class=pp>'r2-overridetxt'</samp></pre>
<ol>
<li>Ogni istanza della classe eredita l’attributo <var>rules_filename</var> con il valore definito dalla classe.
<li>Cambiare il valore dell’attributo in un’istanza non ha effetto sulle altre istanze…
<li>…né modifica l’attributo di classe. Potete accedere all’attributo di classe (al contrario dell’attributo di una singola istanza) usando lo speciale attributo <code>__class__</code> per accedere alla classe stessa.
<li>Se modificate l’attributo di classe, tutte le istanze che stanno ancora ereditando quel valore (come <var>r1</var> qui) verranno affette dal cambiamento.
<li>Le istanze che hanno sostituito quell’attributo (come <var>r2</var> qui) non verranno affette.
</ol>
<p>E ora torniamo al nostro spettacolo.
<pre class=pp><code><a> def __iter__(self): <span class=u>①</span></a>
self.cache_index = 0
<a> return self <span class=u>②</span></a>
</code></pre>
<ol>
<li>Il metodo <code>__iter__()</code> verrà chiamato ogni volta che qualcuno — diciamo, un ciclo <code>for</code> — chiama <code>iter(rules)</code>.
<li>L’unica cosa che ogni metodo <code>__iter__()</code> deve fare è restituire un iteratore. In questo caso, il metodo restituisce <var>self</var>, segnalando che questa classe definisce un metodo <code>__next__()</code> che si prenderà cura di restituire i valori durante l’iterazione.
</ol>
<pre class=pp><code><a> def __next__(self): <span class=u>①</span></a>
.
.
.
pattern, search, replace = line.split(None, 3)
<a> funcs = build_match_and_apply_functions( <span class=u>②</span></a>
pattern, search, replace)
<a> self.cache.append(funcs) <span class=u>③</span></a>
return funcs</code></pre>
<ol>
<li>Il metodo <code>__next__()</code> viene chiamato ogni volta che qualcuno — diciamo, un ciclo <code>for</code> — chiama <code>next(rules)</code>. Questo metodo si capisce meglio se cominciamo dalla fine e lo leggiamo all’indietro. Quindi, facciamolo.
<li>L’ultima parte di questa funzione dovrebbe sembrare quantomeno familiare. La funzione <code>build_match_and_apply_functions()</code> non è cambiata, è la stessa che è sempre stata.
<li>L’unica differenza è che, prima di restituire le funzioni di ricerca e sostituzione (che sono memorizzate nella tupla <var>funcs</var>), dobbiamo salvarle in <code>self.cache</code>.
</ol>
<p>Muovendoci all’indietro…
<pre class=pp><code> def __next__(self):
.
.
.
<a> line = self.pattern_file.readline() <span class=u>①</span></a>
<a> if not line: <span class=u>②</span></a>
self.pattern_file.close()
<a> raise StopIteration <span class=u>③</span></a>
.
.
.</code></pre>
<ol>
<li>Qui utilizziamo i file in maniera astuta. Il metodo <code>readline()</code> (notate: singolare, non il plurale <code>readlines()</code>) legge esattamente una riga da un file aperto. Nello specifico, la riga successiva. (<em>Anche gli oggetti file sono iteratori! Ci sono iteratori dappertutto…</em>)
<li>Se <code>readline()</code> legge una riga, <var>line</var> non sarà una stringa vuota. Anche se il file contenesse una riga vuota, <var>line</var> finirebbe per essere la stringa <code>'\n'</code> contenente il carattere di ritorno a capo. Quando <var>line</var> è davvero la stringa vuota significa che non ci sono più righe da leggere nel file.
<li>Quando raggiungiamo la fine del file, dobbiamo chiudere il file e lanciare la magica eccezione di tipo <code>StopIteration</code>. Ricordate, siamo arrivati a questo punto perché avevamo bisogno di una coppia di funzioni di ricerca e sostituzione per la regola successiva. Tale regola proviene dalla riga successiva nel file… ma non c’è nessun’altra riga! Di conseguenza, non abbiamo alcun valore da restituire. L’iterazione è finita. (<span class=u>♫</span> The party’s over… <span class=u>♫</span>)
</ol>
<p>Spostandoci all’indietro fino all’inizio del metodo <code>__next__()</code>…
<pre class=pp><code> def __next__(self):
self.cache_index += 1
if len(self.cache) >= self.cache_index:
<a> return self.cache[self.cache_index - 1] <span class=u>①</span></a>
if self.pattern_file.closed:
<a> raise StopIteration <span class=u>②</span></a>
.
.
.</code></pre>
<ol>
<li><code>self.cache</code> sarà una lista di funzioni per cercare corrispondenze con le singole regole e poi sostituirle. (Almeno <em>questo</em> dovrebbe suonarvi familiare!) <code>self.cache_index</code> tiene traccia di quale elemento in cache dovremo restituire successivamente. Se non abbiamo ancora esaurito la cache (cioè se la lunghezza di <code>self.cache</code> è maggiore di <code>self.cache_index</code>), allora l’accesso alla cache ha successo! Urrà! Possiamo restituire le funzioni di ricerca e sostituzione dalla cache anziché costruirle da zero.
<li>D’altra parte, se il tentativo di accesso alla cache fallisce <em>e</em> l’oggetto file è stato chiuso (cosa che potrebbe accadere più avanti nel metodo, come avete visto nel precedente frammento di codice) allora non c’è nient’altro che possiamo fare. Se il file è chiuso, vuol dire che lo abbiamo esaurito — abbiamo già letto ogni riga dal file dei pattern e abbiamo già costruito e memorizzato le funzioni di ricerca e sostituzione per ogni pattern. Il file è esaurito; la cache è esaurita; anche io sono ormai esaurito... Ma tenete duro, abbiamo quasi finito.
</ol>
<p>Mettendo insieme tutti i pezzi, ecco cosa succede e quando.
<ul>
<li>Quando il modulo viene importato, viene creata una singola istanza della classe <code>LazyRules</code>, chiamata <var>rules</var>, che apre il file dei pattern ma non lo legge.
<li>Quando si richiedono a <var>rules</var> le prime funzioni di ricerca e sostituzione, l’istanza controlla la propria cache ma scopre che è vuota. Quindi legge una singola riga dal file dei pattern, costruisce le funzioni di ricerca e sostituzione per i pattern di quella riga e le memorizza nella cache.
<li>Diciamo che, per fare un esempio, proprio la prima regola trova una corrispondenza. Se è così, nessuna nuova coppia di funzioni viene costruita e nessun’altra riga viene letta dal file.
<li>In più, sempre per fare un esempio, supponiamo che il chiamante invochi <em>di nuovo</em> la funzione <code>plural()</code> per costruire il plurale di una parola diversa. Il ciclo <code>for</code> nella funzione <code>plural()</code> chiamerà <code>iter(rules)</code>, che azzererà l’indice della cache ma non toccherà l’oggetto file aperto.
<li>Alla prima iterazione, il ciclo <code>for</code> chiederà un valore a <var>rules</var>, che invocherà il proprio metodo <code>__next__()</code>. Questa volta, però, la cache è caricata con una singola coppia di funzioni di ricerca e sostituzione, corrispondenti ai pattern nella prima riga del file dei pattern. Visto che la coppia è stata costruita e memorizzata nel tentativo di pluralizzare la parola precedente, viene recuperata dalla cache. L’indice della cache viene incrementato e il file aperto non viene mai toccato.
<li>Diciamo ora che, per fare un esempio, la prima regola questa volta non genera alcuna corrispondenza. Quindi il ciclo <code>for</code> ricomincia da capo e chiede un altro valore a <var>rules</var>, che invoca il metodo <code>__next__()</code> una seconda volta. Questa volta la cache è esaurita — conteneva un solo elemento e noi ne stiamo chiedendo un secondo — quindi il metodo <code>__next__()</code> prosegue. Legge un’altra riga dal file aperto, costruisce le funzioni di ricerca e sostituzione sulla base dei pattern e le memorizza in cache.
<li>Questo processo di lettura-costruzione-memorizzazione continuerà fino a quando le regole lette dal file dei pattern non corrisponderanno alla parola che stiamo cercando di pluralizzare. Se troviamo una regola che corrisponde prima della fine del file, semplicemente la usiamo e ci fermiamo, con il file ancora aperto. Il puntatore del file rimarrà ovunque avremo smesso di leggere, aspettando la prossima invocazione di <code>readline()</code>. Nel frattempo, la cache contiene ora più elementi, e se ricominciamo ancora a cercare di pluralizzare una nuova parola ognuno degli elementi in cache verrà provato prima di leggere la riga successiva dal file dei pattern.
</ul>
<p>Abbiamo raggiunto il nirvana della pluralizzazione.
<ol>
<li><strong>Costo di inizializzazione minimo.</strong> L’unica cosa che accade durante l’importazione del modulo è la creazione di una singola istanza di una classe e l’apertura di un file (ma senza alcuna lettura).
<li><strong>Prestazioni massime.</strong> L’esempio precedente avrebbe letto tutto il file e costruito funzioni dinamicamente ogni volta che volevate pluralizzare una parola. Questa versione memorizza le funzioni non appena vengono costruite e, nel caso peggiore, leggerà tutto il file dei pattern una volta sola a prescindere dal numero di parole che pluralizzate.
<li><strong>Separazione di codice e dati.</strong> Tutti i pattern sono archiviati in un file separato. Il codice è codice, i dati sono dati, “e mai i due si incontreranno”.
</ol>
<blockquote class=note>
<p><span class=u>☞</span>Questo è veramente il nirvana? Be’, sì e no. Ecco una cosa da considerare a proposito dell’esempio con <code>LazyRules</code>: il file dei pattern aperto durante l’esecuzione di <code>__init__()</code> rimane aperto fino a quando viene ragguinta l’ultima regola. Python chiuderà il file alla fine, quando uscite dall’interprete, o dopo che l’ultima istanza della classe <code>LazyRules</code> viene distrutta, ma l’intervallo di tempo trascorso fino a quel momento potrebbe ancora essere molto <em>lungo</em>. Se questa classe è parte di un processo Python di lunga durata, l’interprete potrebbe non terminare mai e l’oggetto <code>LazyRules</code> potrebbe non venire mai distrutto.
<p>Ci sono alcuni modi per aggirare questo problema. Invece di aprire il file durante l’esecuzione di <code>__init__()</code> e lasciarlo aperto mentre leggete le regole una alla volta, potreste aprire il file, leggere tutte le regole e chiudere immediatamente il file. Oppue potreste aprire il file, leggere una regola, salvare la posizione di lettura corrente del file con il <a href=file.html#read>metodo <code>tell()</code></a>, chiudere il file e riaprirlo più tardi, usando il <a href=file.html#read>metodo <code>seek()</code></a> per continuare a leggere dal punto in cui eravate rimasti. Oppure potreste non preoccuparvi del problema e lasciare semplicemente il file aperto, come in questo esempio di codice. La programmazione è progettazione, e la progettazione è fatta di compromessi e vincoli. Lasciare un file aperto troppo a lungo potrebbe essere un problema; rendere il vostro codice più complicato potrebbe essere un problema. Qual è il problema più grande dipende dal vostro gruppo di sviluppo, dalla vostra applicazione e dal vostro ambiente di esecuzione.
</blockquote>
<p class=a>⁂
<h2 id=furtherreading>Letture di approfondimento</h2>
<ul>
<li><a href=http://docs.python.org/3.1/library/stdtypes.html#iterator-types>Tipi di iteratore</a>
<li><a href=http://www.python.org/dev/peps/pep-0234/><abbr>PEP</abbr> 234: Iteratori</a>
<li><a href=http://www.python.org/dev/peps/pep-0255/><abbr>PEP</abbr> 255: Generatori semplici</a>
<li><a href=http://www.dabeaz.com/generators/>Trucchi con i generatori per programmatori di sistema</a>
</ul>
<p class=v><a rel=prev href=generatori.html title='indietro a “Chiusure & generatori”'><span class=u>☜</span></a> <a rel=next href=uso-avanzato-degli-iteratori.html title='avanti a “Uso avanzato degli iteratori”'><span class=u>☞</span></a>
<p class=c>© 2001–10 <a href=informazioni-sul-libro.html>Mark Pilgrim</a><br>
© 2009–10 <a href=informazioni-sulla-traduzione.html>Giulio Piancastelli</a> per la traduzione italiana
<script src=j/jquery.js></script>
<script src=j/prettify.js></script>
<script src=j/dip3.js></script>