Ένας generator είναι μια συνάρτηση ή έκφραση που επιστρέφει έναν ειδικό τύπο iterator που ονομάζεται generator iterator.
Οι Generator-iterators είναι οκνηροί: δεν αποθηκεύουν τις values τους στη μνήμη, αλλά παράγουν τις τιμές τους όταν χρειάζεται.
Μια συνάρτηση generator μοιάζει με οποιαδήποτε άλλη συνάρτηση, αλλά περιέχει μία ή περισσότερες εκφράσεις yield.
Κάθε yield αναστέλλει την εκτέλεση του κώδικα, αποθηκεύοντας την τρέχουσα κατάσταση εκτέλεσης (συμπεριλαμβανομένων όλων των τοπικών μεταβλητών και των εντολών try).
Όταν ο generator συνεχίσει, ανακτά την κατάσταση από το σημείο της αναστολής, σε αντίθεση με τις κανονικές συναρτήσεις που επαναφέρονται σε κάθε κλήση.
Οι generators κατασκευάζονται σχεδόν όπως άλλες συναρτήσεις με βρόχους ή αναδρομικές συναρτήσεις, αλλά απαιτούν μια έκφραση yield, την οποία θα εξετάσουμε σε βάθος λίγο αργότερα.
Ένα παράδειγμα είναι μια συνάρτηση που επιστρέφει τα τετράγωνα των αριθμών από μια δοσμένη λίστα.
Όπως είναι γραμμένη τώρα, όλα τα δεδομένα εισόδου πρέπει να υποστούν επεξεργασία πριν επιστραφεί οποιαδήποτε τιμή:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
Μπορείς να μετατρέψεις αυτή τη συνάρτηση σε generator ως εξής:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
Το σκεπτικό πίσω από αυτό είναι ότι χρησιμοποιείς έναν generator όταν δεν χρειάζεσαι όλες τις τιμές ταυτόχρονα.
Αυτό εξοικονομεί μνήμη και υπολογιστική ισχύ, αφού υπολογίζεται μόνο η τιμή με την οποία δουλεύεις αυτή τη στιγμή.
Οι generators (τεχνικά generator-iterators, δες το σημείωμα παρακάτω.) είναι ένα είδος iterator και μπορούν να χρησιμοποιηθούν οπουδήποτε στην Python περιμένει κάποιος έναν iterator ή ένα iterable.
Αυτό περιλαμβάνει συναρτήσεις ή αντικείμενα που απαιτούν ένα iterable/iterator ως όρισμα.
Για μια πιο βαθιά ματιά, δες το How to Make an Iterator in Python.
Οι generator-iterators είναι ένα ειδικό υποσύνολο των iterators.
Οι Iterators είναι ο μηχανισμός/πρωτόκολλο που επιτρέπει την επανάληψη πάνω σε iterables.
Οι generator-iterators και οι iterators που επιστρέφουν τα συνηθισμένα iterables της Python συμπεριφέρονται πολύ παρόμοια, αλλά υπάρχουν μερικές σημαντικές διαφορές που αξίζει να σημειώσεις:
indexes.
Δεν μπορείς να αναφερθείς σε μια προηγούμενη ή μελλοντική τιμή με πρόσθεση ή αφαίρεση και δεν μπορείς να χρησιμοποιήσεις συμβολισμό με αγκύλη ([]) ή τεμαχισμό.len(), αφού δεν έχουν μήκος.generator-iterator!Για να χρησιμοποιήσεις τον generator squares_generator():
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
Οι τιμές μέσα σε έναν generator μπορούν επίσης να παραχθούν/προσπελαστούν μέσω της συνάρτησης next().
Η next() καλεί τη μέθοδο __next__() ενός αντικειμένου generator, "προωθώντας" ή αξιολογώντας τον κώδικα του generator μέχρι την έκφραση yield, η οποία τότε "δίνει" ή επιστρέφει την τιμή.
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
Όταν ένας generator καταναλωθεί πλήρως και δεν έχει άλλες τιμές να επιστρέψει, πετάει ένα σφάλμα StopIteration.
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Οι generators είναι ένα ειδικό υποσύνολο των iterators.
Οι Iterators είναι ο μηχανισμός/πρωτόκολλο που επιτρέπει την επανάληψη πάνω σε iterables.
Οι generators και οι iterators που επιστρέφουν τα συνηθισμένα iterables της Python συμπεριφέρονται πολύ παρόμοια, αλλά υπάρχουν μερικές σημαντικές διαφορές που αξίζει να σημειώσεις:
Οι generators είναι μονόδρομοι: δεν υπάρχει "επιστροφή" σε μια προηγούμενη τιμή.
Η επανάληψη πάνω σε generators καταναλώνει τις τιμές που επιστρέφονται: δεν γίνεται επαναφορά.
Οι generators (καθώς αξιολογούνται οκνηρά) δεν ταξινομούνται και δεν μπορούν να αντιστραφούν.
Οι generators δεν έχουν indexes, οπότε δεν μπορείς να αναφερθείς σε μια προηγούμενη ή μελλοντική τιμή με πρόσθεση ή αφαίρεση.
Οι generators δεν μπορούν να χρησιμοποιηθούν με τη συνάρτηση len().
Οι generators μπορούν να είναι πεπερασμένοι ή άπειροι. Πρόσεχε όταν συλλέγεις όλες τις τιμές από έναν άπειρο generator.
yield
Η έκφραση yield μοιάζει πολύ με την έκφραση return.
Σε αντίθεση με την έκφραση return, το yield παραδίδει τιμές σε αυτόν που το καλεί σε ένα συγκεκριμένο σημείο, αναστέλλοντας την αξιολόγηση/επιστροφή οποιωνδήποτε επιπλέον τιμών μέχρι να ζητηθούν.
Όταν αξιολογείται το yield, παύει η εκτέλεση της συνάρτησης που το περικλείει και επιστρέφονται οι τιμές της συνάρτησης εκείνη τη στιγμή.
Η συνάρτηση τότε παραμένει σε εμβέλεια και, όταν κληθεί η __next__(), η εκτέλεση συνεχίζεται μέχρι να συναντήσει ξανά το yield.
Η χρήση εκφράσεων yield απαγορεύεται έξω από συναρτήσεις.
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
Οι generators είναι χρήσιμοι σε πολλές εφαρμογές.
Όταν δουλεύεις με μια μεγάλη συλλογή, μπορεί να μη θέλεις να βάλεις όλες τις τιμές της στη memory.
Ένας generator μπορεί να χρησιμοποιηθεί για να δουλέψεις με μεγαλύτερα δεδομένα κομμάτι-κομμάτι, εξοικονομώντας μνήμη και βελτιώνοντας την απόδοση.
Οι generators είναι επίσης πολύ χρήσιμοι όταν μια διαδικασία ή ένας υπολογισμός είναι πολύπλοκος, δαπανηρός ή άπειρος:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
Τώρα, κάθε φορά που καλείται η __next__() στο αντικείμενο infinite_sequence, θα επιστρέφει τον προηγούμενο αριθμό + 1.