Ένας generator είναι μια συνάρτηση ή έκφραση που επιστρέφει έναν ειδικό τύπο επαναλήπτη που ονομάζεται generator iterator.
Οι Generator-iterators είναι τεμπέληδες: δεν αποθηκεύουν τις values τους στη μνήμη, αλλά παράγουν τις τιμές τους όταν χρειάζεται.
Μια συνάρτηση γεννήτριας μοιάζει με οποιαδήποτε άλλη συνάρτηση, αλλά περιέχει μία ή περισσότερες εκφράσεις yield.
Κάθε yield αναστέλλει την εκτέλεση του κώδικα, αποθηκεύοντας την τρέχουσα κατάσταση εκτέλεσης (συμπεριλαμβανομένων όλων των τοπικών μεταβλητών και των εντολών try).
Όταν η γεννήτρια συνεχίσει, ξεκινά από την κατάσταση όπου είχε ανασταλεί, σε αντίθεση με τις κανονικές συναρτήσεις, που μηδενίζονται σε κάθε κλήση.
Οι γεννήτριες κατασκευάζονται περίπου όπως οι άλλες συναρτήσεις με βρόχους ή οι αναδρομικές συναρτήσεις, αλλά απαιτούν μια έκφραση yield, την οποία θα εξετάσουμε σε βάθος λίγο αργότερα.
Ένα παράδειγμα είναι μια συνάρτηση που επιστρέφει τα τετράγωνα μιας δεδομένης λίστας αριθμών. Όπως είναι γραμμένη αυτή τη στιγμή, όλη η είσοδος πρέπει να υποστεί επεξεργασία πριν μπορέσει να επιστραφεί οποιαδήποτε τιμή:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
Μπορείς να μετατρέψεις αυτή τη συνάρτηση σε γεννήτρια ως εξής:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
Το σκεπτικό πίσω από αυτό είναι ότι χρησιμοποιείς μια γεννήτρια όταν δεν χρειάζεται να παραγάγεις όλες τις τιμές με τη μία. Αυτό εξοικονομεί μνήμη και υπολογιστική ισχύ, αφού υπολογίζεται μόνο η τιμή με την οποία εργάζεσαι αυτή τη στιγμή.
Οι γεννήτριες μπορούν να χρησιμοποιηθούν στη θέση των περισσότερων iterables στην Python.
Αυτό περιλαμβάνει συναρτήσεις ή αντικείμενα που απαιτούν ένα iterable/iterator ως όρισμα.
Για να χρησιμοποιήσεις τη γεννήτρια 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__() ενός αντικειμένου επαναλήπτη γεννήτριας, "προχωρώντας" ή αξιολογώντας τον κώδικα μέχρι την έκφραση yield του, η οποία τότε "αποδίδει" ή επιστρέφει μια τιμή:
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
Όταν ένας generator-iterator καταναλωθεί πλήρως και δεν έχει άλλες τιμές να επιστρέψει, πετάει ένα σφάλμα StopIteration.
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Οι επαναλήπτες γεννήτριας είναι ένα ειδικό υποσύνολο των επαναληπτών.
Οι Iterators είναι ο μηχανισμός/το πρωτόκολλο που επιτρέπει την επανάληψη πάνω από επαναλήψιμα αντικείμενα.
Οι επαναλήπτες γεννήτριας και οι επαναλήπτες που επιστρέφουν τα κοινά iterables της Python συμπεριφέρονται με πολύ παρόμοιο τρόπο, αλλά υπάρχουν μερικές σημαντικές διαφορές που πρέπει να προσέξεις:
indexes.
Δεν μπορείς να αναφερθείς σε προηγούμενη ή μελλοντική τιμή χρησιμοποιώντας πρόσθεση ή αφαίρεση και δεν μπορείς να χρησιμοποιήσεις σημειογραφία αγκύλης ([]) ούτε τεμαχισμό.len(), αφού δεν έχουν μήκος.generator-iterator!Η έκφραση 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
Οι γεννήτριες είναι χρήσιμες σε πολλές εφαρμογές.
Όταν δουλεύεις με μια δυνητικά μεγάλη συλλογή τιμών, μπορεί να μη θέλεις να τις βάλεις όλες στη μνήμη. Μια γεννήτρια μπορεί να χρησιμοποιηθεί για να δουλέψεις με μεγαλύτερα δεδομένα κομμάτι-κομμάτι, εξοικονομώντας μνήμη και βελτιώνοντας την απόδοση.
Οι γεννήτριες είναι επίσης πολύ χρήσιμες όταν μια διαδικασία ή ένας υπολογισμός είναι πολύπλοκος, δαπανηρός ή άπειρος:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
Τώρα, όποτε καλείται η __next__() στο αντικείμενο infinite_sequence, θα επιστρέφει τον προηγούμενο αριθμό + 1.
Η Conda Airlines είναι η μεγαλύτερη αεροπορική εταιρεία στον κόσμο του προγραμματισμού, με πάνω από 10.000 πτήσεις την ημέρα!
Αυτή τη στιγμή αναθέτουν όλες τις θέσεις στους επιβάτες με το χέρι· αυτό θα πρέπει να αυτοματοποιηθεί.
Σου έχουν ζητήσει εσένα να δημιουργήσεις λογισμικό που θα αυτοματοποιεί την ανάθεση θέσεων στους επιβάτες. Απαιτούν το λογισμικό σου να είναι αποδοτικό στη μνήμη και στις επιδόσεις.
Η Conda θέλει να δημιουργεί γράμματα θέσεων για τα αεροπλάνα της.
Ένα αεροπλάνο αποτελείται από σειρές θέσεων.
Κάθε σειρά έχει 4 θέσεις.
Οι θέσεις σε κάθε σειρά ονομάζονται πάντα A, B, C και D.
Η πρώτη θέση στη σειρά είναι A, η δεύτερη θέση στη σειρά είναι B, και ούτω καθεξής.
Αφού φτάσει στο D, θα πρέπει να ξεκινά ξανά από το A.
Υλοποίησε μια συνάρτηση generate_seat_letters(<number>) που δέχεται ένα int που περιέχει πόσα γράμματα θέσεων θα δημιουργηθούν.
Η συνάρτηση θα πρέπει τότε να επιστρέφει ένα iterable με γράμματα θέσεων.
>>> letters = generate_seat_letters(4)
>>> next(letters)
"A"
>>> next(letters)
"B"
Η Conda θέλει ένα σύστημα που να μπορεί να δημιουργεί έναν δεδομένο αριθμό θέσεων για τα αεροπλάνα της.
Κάθε αεροπλάνο έχει 4 θέσεις σε κάθε σειρά.
Οι σειρές ορίζονται με αριθμούς, ξεκινώντας από το 1 και ανεβαίνοντας.
Οι θέσεις θα πρέπει να είναι διατεταγμένες, όπως: 1A, 1B, 1C, 1D, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, ...
Ακολουθεί ένα παράδειγμα:
| x | 1 | 2 |
|---|---|---|
| Σειρά | 5 | 21 |
| Γράμμα θέσης | A | D |
| Αποτέλεσμα | 5A | 21D |
Πολλές αεροπορικές εταιρείες δεν έχουν τη σειρά 13 στις πτήσεις τους, λόγω της προληπτικότητας των επιβατών. Η Conda Airlines ακολουθεί επίσης αυτή τη σύμβαση, οπότε φρόντισε να μην δημιουργείς θέσεις για τη σειρά 13.
Υλοποίησε μια συνάρτηση generate_seats(<number>) που δέχεται ένα int που περιέχει πόσες θέσεις θα δημιουργηθούν.
Η συνάρτηση θα πρέπει τότε να επιστρέφει ένα iterable με τις θέσεις.
>>> seats = generate_seats(10)
>>> next(seats)
"1A"
>>> next(seats)
"1B"
Τώρα που έχεις μια συνάρτηση που δημιουργεί θέσεις, μπορείς να τη χρησιμοποιήσεις για να αναθέσεις θέσεις στους επιβάτες.
Υλοποίησε μια συνάρτηση assign_seats(<passengers>) που δέχεται μια list με ονόματα επιβατών.
Η συνάρτηση θα πρέπει τότε να επιστρέφει ένα λεξικό με passenger ως κλειδί και seat_number ως τιμή.
>>> passengers = ['Jerimiah', 'Eric', 'Bethany', 'Byte', 'SqueekyBoots', 'Bob']
>>> assign_seats(passengers)
{'Jerimiah': '1A', 'Eric': '1B', 'Bethany': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'}
Η Conda Airlines θα ήθελε να έχει έναν μοναδικό κωδικό για κάθε εισιτήριο. Επειδή είναι μια μεγάλη αεροπορική εταιρεία, έχουν πολλές πτήσεις. Αυτό σημαίνει ότι υπάρχουν πολλές πτήσεις με τον ίδιο αριθμό θέσης. Θέλουν να δημιουργήσεις ένα σύστημα που συνθέτει ένα μοναδικό εισιτήριο που είναι μια συμβολοσειρά 12 χαρακτήρων.
Αυτός ο κωδικός ξεκινά με το assigned_seat ακολουθούμενο από το flight_id.
Το υπόλοιπο του κωδικού συμπληρώνεται με 0s.
Υλοποίησε μια συνάρτηση generate_codes(<seat_numbers>, <flight_id>) που δέχεται μια list με seat_numbers και μια string με τον αριθμό πτήσης.
Η συνάρτηση θα πρέπει τότε να επιστρέφει έναν generator που παράγει ένα ticket_number.
>>> seat_numbers = ['1A', '17D']
>>> flight_id = 'CO1234'
>>> ticket_ids = generate_codes(seat_numbers, flight_id)
>>> next(ticket_ids)
'1ACO12340000'
>>> next(ticket_ids)
'17DCO1234000'
Γράψου στο Exercism για να μάθεις και να κατακτήσεις Python με 17 έννοιες146 ασκήσεις και πραγματική καθοδήγηση από ανθρώπους, όλα δωρεάν.