9. Κλάσεις

Οι κλάσεις παρέχουν ένα μέσο ομαδοποίησης δεδομένων και λειτουργικότητας. Η δημιουργία μιας νέας κλάσης δημιουργεί έναν νέο τύπο αντικειμένου, επιτρέποντας νέα στιγμιότυπα αυτού του τύπου που πρόκειται να γίνουν. Κάθε στιγμιότυπο κλάσης μπορεί να έχει χαρακτηριστικά που συνδέονται με αυτό για τη διατήρηση της κατάστασής του. Τα στιγμιότυπα κλάσης μπορούν να έχουν επίσης μεθόδους (που ορίζονται από την κλάση του) για την τροποποίηση της κατάστασής του.

Σε σύγκριση με άλλες γλώσσες προγραμματισμού, ο μηχανισμός κλάσης της Python προσθέτει κλάσεις με ελάχιστο νέο συντακτικό και σημασιολογία. Είναι ένα μείγμα των μηχανισμών κλάσεων που βρέθηκαν στη C++ και στο Modula-3. Οι κλάσεις της Python παρέχουν όλα τα standard χαρακτηριστικά του Αντικειμενοστραφούς Προγραμματισμού: ο μηχανισμός της κληρονομικότητας της κλάσης επιτρέπει την ύπαρξη πολλαπλών βασικών κλάσεων, μια παραγόμενη κλάση να μπορεί να παρακάμψει οποιεσδήποτε μεθόδους της βασικής κλάσης ή κλάσεων, και μια μέθοδος να μπορεί να καλέσει τη μέθοδο μίας βασικής κλάσης με το ίδιο όνομα. Τα αντικείμενα μπορούν να περιέχουν αυθαίρετα ποσά και είδη δεδομένων. Όπως ισχύει για τα modules, οι κλάσεις συμμετέχουν στη δυναμική φύση της Python: δημιουργούνται κατά το χρόνο εκτέλεσης και μπορούν να τροποποιηθούν περαιτέρω μετά τη δημιουργία.

Στην ορολογία της C++, συνήθως τα μέλη της κλάσης (συμπεριλαμβανομένων των μελών δεδομένων) είναι δημόσια (εκτός από βλέπε παρακάτω Ιδιωτικές Μεταβλητές), και όλες οι συμμετέχουσες συναρτήσεις είναι εικονικές. Όπως και στο Modula-3, δεν υπάρχουν συντομογραφίες για την αναφορά στα μέλη του αντικειμένου από τις μεθόδους του: η μέθοδος δηλώνεται με ρητό πρώτο όρισμα που αντιπροσωπεύει το αντικείμενο, το οποίο παρέχεται έμμεσα από την κλήση. Όπως και στο Smalltalk, οι ίδιες οι κλάσεις είναι αντικείμενα.Αυτό παρέχει σημασιολογία για εισαγωγή και μετονομασία. Σε αντίθεση με τις γλώσσες C++ και Modula-3, οι built-in τύποι μπορούν να χρησιμοποιηθούν ως βασικές κλάσεις για επέκταση από τον χρήστη. Επίσης, όπως στην C++, οι περισσότεροι built-in τελεστές με ειδική σύνταξη (αριθμητικοί τελεστές, εγγραφή κ.λπ.) μπορούν να επαναπροσδιοριστούν για τα στιγμιότυπα κλάσης.

(Ελλείψει καθολικής αποδεκτής ορολογίας για να μιλήσω για τις κλάσεις, θα κάνω περιστασιακή χρήση όρων από τη Smalltalk και τη C++. Θα χρησιμοποιούσα όρους από τη Modula-3, καθώς η αντικειμενοστραφής σημασιολογία του είναι πιο κοντά σε αυτήν της Python από ότι της C++, Αλλά πιστεύω ότι λίγοι αναγνώστες το έχουν ακούσει.)

9.1. Λίγα λόγια για Ονόματα και Αντικείμενα

Τα αντικείμενα έχουν μοναδικότητα και πολλά ονόματα (σε πολλαπλά πεδία) μπορούν να συνδεθούν στο ίδιο αντικείμενο. Αυτό είναι γνωστό ως ψευδώνυμο σε άλλες γλώσσες.Αυτό συνήθως δεν εκτιμάται με μια πρώτη ματιά στην Python και μπορεί να αγνοείται με ασφάλεια όταν ασχολείται με αμετάβλητους βασικούς τύπους (αριθμοί, συμβολοσειρές, πλειάδες (tuples)). Ωστόσο, το ψευδώνυμο έχει μια πιθανώς εκπληκτική επίδραση στη σημασιολογία του κώδικα της Python που περιλαμβάνει ευμετάβλητα αντικείμενα όπως λίστες, λεξικά, και τους περισσότερους άλλους τύπους. Αυτό χρησιμοποιείται συνήθως προς όφελος του προγράμματος, δεδομένου ότι τα ψευδώνυμα συμπεριφέρονται σαν δείκτες από ορισμένες απόψεις. Για παράδειγμα, η μετάδοση ενός αντικειμένου είναι ανέξοδη αφού μόνο ένας δείκτης περνά από την υλοποίηση, και αν μια συνάρτηση τροποποιεί ένα αντικείμενο που έχει περάσει ως όρισμα, ο καλών θα δει την αλλαγή — αυτό εξαλείφει την ανάγκη για δύο διαφορετικούς μηχανισμούς μετάδοσης ορισμάτων όπως στην Pascal.

9.2. Εμβέλεια και Πεδία Ονομάτων στην Python

Πριν από την εισαγωγή των κλάσεων, πρέπει πρώτα να σας πω κάτι για τους κανόνες εμβέλειας της Python. Οι ορισμοί των κλάσεων παίζουν μερικά ξεκάθαρα κόλπα με τα πεδία ονομάτων και πρέπει να γνωρίζετε πώς λειτουργούν πλήρως τα πεδία ονομάτων και η εμβέλεια για να κατανοήσετε πλήρως τι συμβαίνει. Παρεμπιπτόντως, η γνώση για αυτό το θέμα είναι χρήσιμη για κάθε προχωρημένο προγραμματιστή της Python.

Ας ξεκινήσουμε με ορισμένους ορισμούς.

Ένας πεδίο ονομάτων (namespace) είναι μια αντιστοίχιση από ονόματα σε αντικείμενα. Τα περισσότερα πεδία ονομάτων υλοποιούνται επί του παρόντος ως λεξικά Python, αλλά αυτό συνήθως δεν γίνεται αντιληπτό με κανέναν τρόπο (εκτός από την απόδοση) και μπορεί να αλλάξει στο μέλλον. Παραδείγματα πεδίων ονομάτων είναι: το σύνολο των ενσωματωμένων ονομάτων (που περιέχει συναρτήσεις όπως abs() και ενσωματωμένα ονόματα εξαιρέσεων)∙ τα καθολικά ονόματα σε ένα module και τα τοπικά ονόματα σε μια επίκληση συνάρτησης. Κατά μία έννοια το σύνολο των χαρακτηριστικών ενός αντικειμένου σχηματίζει επίσης ένα πεδίο ονομάτων. Το σημαντικό πράγμα που πρέπει να γνωρίζετε για τα πεδία ονομάτων είναι ότι δεν υπάρχει καμία απολύτως σχέση μεταξύ ονομάτων σε διαφορετικά πεδία ονομάτων, για παράδειγμα, δύο διαφορετικά modules μπορεί και τα δύο να ορίσουν μια συνάρτηση maximize χωρίς σύγχυση — χρήστες των modules πρέπει να την προσθέσουν με το όνομα του module.

Παρεμπιπτόντως, χρησιμοποιώ τη λέξη attribute για οποιοδήποτε όνομα που ακολουθεί μια τελεία — για παράδειγμα, στην έκφραση z.real, το real είναι ένα attribute του αντικειμένου z. Αυστηρά μιλώντας, οι αναφορές σε ονόματα των modules είναι αναφορές σε attributes: στην έκφραση modname.funcname, το modname είναι ένα module αντικείμενο και το funcname είναι ένα attribute του αντικειμένου. Σε αυτήν την περίπτωση συμβαίνει να υπάρχει μια απλή αντιστοίχιση μεταξύ των attributes των modules και των καθολικών ονομάτων που ορίζονται στο module: μοιράζονται τον ίδιο χώρο ονομάτων! [1]

Τα attributes μπορεί να είναι μόνο για ανάγνωση ή εγγράψιμα. Στην τελευταία περίπτωση, είναι δυνατή η ανάθεση attributes. Τα attributes των modules είναι εγγράψιμα: μπορείτε να γράψετε modname.the_answer = 42. Τα εγγράψιμα attributes μπορούν επίσης να διαγραφούν με την δήλωση del. Για παράδειγμα, del modname.the_answer θα αφαιρέσει το attribute the_answer από το αντικείμενο που ονομάστηκε από το modname.

Οι χώροι ονομάτων δημιουργούνται σε διαφορετικές στιγμές και έχουν διαφορετική διάρκεια ζωής.Ο χώρος ονομάτων που περιέχει τα built-in ονόματα δημιουργείται κατά την εκκίνηση του διερμηνέα της Python και δεν διαγράφεται ποτέ. Ο καθολικός χώρος ονομάτων για ένα module δημιουργείται όταν διαβάζεται ο ορισμός του module. Κανονικά, οι χώροι ονομάτων των modules διαρκούν επίσης μέχρι να τερματιστεί ο διερμηνέας. Οι δηλώσεις που εκτελούνται από την επίκληση ανώτατου επιπέδου του διερμηνέα, είτε διαβάζονται από ένα script είτε διαδραστικά, θεωρούνται μέρος ενός module που ονομάζεται __main__, επομένως έχουν τον δικό τους καθολικό χώρο ονομάτων. (Τα ενσωματωμένα ονόματα στην πραγματικότητα υπάρχουν επίσης σε ένα module,αυτό ονομάζεται builtins.)

Ο τοπικός χώρος ονομάτων για μια συνάρτηση δημιουργείται όταν καλείται η συνάρτηση, και διαγράφεται όταν η συνάρτηση επιστρέφει ή δημιουργεί μια εξαίρεση που δεν αντιμετωπίζεται στην συνάρτηση. (Στην πραγματικότητα, η λήθη θα ήταν καλύτερος τρόπος για να περιγράψουμε τι πραγματικά συμβαίνει.) Φυσικά, οι επαναλαμβανόμενες επικλήσεις έχουν το δικό τους τοπικό χώρο ονομάτων.

Η εμβέλεια είναι μια περιοχή κειμένου ενός προγράμματος Python όπου ένας χώρος ονομάτων είναι άμεσα προσβάσιμος. Το «Άμεση πρόσβαση» εδώ σημαίνει ότι μια ανεπιφύλακτη αναφορά σε ένα όνομα προσπαθεί να βρει το όνομα στον χώρο ονομάτων.

Παρόλο που τα πεδία προσδιορίζονται στατικά, χρησιμοποιούνται δυναμικά. Σε οποιοδήποτε χρόνο κατά την διάρκεια της εκτέλεσης, υπάρχουν 3 ή 4 ένθετα πεδία των οποίων οι χώροι ονομάτων είναι άμεσα προσβάσιμοι:

  • η ενδότερη εμβέλεια, η οποία αναζητείται πρώτα, περιέχει τα τοπικά ονόματα

  • η εμβέλεια οποιωνδήποτε εσωκλειόμενων συναρτήσεων, τα οποία αναζητούνται ξεκινώντας από την πλησιέστερη εσωκλειόμενη εμβέλεια, περιέχει μη τοπικά, αλλά και μη καθολικά ονόματα

  • η επόμενη προς την τελευταία εμβέλεια περιέχει τα τρέχοντα καθολικά ονόματα του module

  • η πιο απομακρυσμένη εμβέλεια (που έγινε τελευταία αναζήτηση) είναι ο χώρος ονομάτων που περιέχει built-in ονόματα

Εάν ένα όνομα έχει δηλωθεί ως καθολικό, τότε όλες οι αναφορές και οι εκχωρήσεις πηγαίνουν απευθείας στο επόμενο προς το τελευταίο πεδίο που περιέχει τα καθολικά ονόματα του module. Για την επανασύνδεση μεταβλητών που βρίσκονται εκτός της ενδότερης εμβέλειας ,μπορεί να χρησιμοποιηθεί η δήλωση του nonlocal . Εάν δεν δηλωθούν ως μη τοπικές, αυτές οι μεταβλητές είναι μόνο για ανάγνωση (μια προσπάθεια εγγραφής σε μια τέτοια μεταβλητή θα δημιουργήσει απλώς μια νέα τοπική μεταβλητή στην ενδότερη εμβέλεια, αφήνοντας αμετάβλητη την εξωτερική μεταβλητή με το ίδιο όνομα ).

Συνήθως, η τοπική εμβέλεια παραπέμπει στα τοπικά ονόματα της (κείμενης) τρέχουσας συνάρτησης. Εκτός συναρτήσεων, η τοπική εμβέλεια αναφέρεται στον ίδιο χώρο ονομάτων με την καθολική εμβέλεια: τον χώρο ονομάτων του module.Οι ορισμοί κλάσεων τοποθετούν έναν ακόμη χώρο ονομάτων στην τοπική εμβέλεια.

Είναι σημαντικό να συνειδητοποιήσουμε ότι οι εμβέλειες καθορίζονται με κείμενο: η καθολική εμβέλεια μιας συνάρτησης που ορίζεται σε ένα module είναι ο χώρος ονομάτων αυτού του module ανεξάρτητα από το πού ή με ποιο ψευδώνυμο καλείται η συνάρτηση. Από την άλλη πλευρά, η πραγματική αναζήτηση ονομάτων γίνεται δυναμικά, κατά το χρόνο εκτέλεσης — ωστόσο, ο ορισμός της γλώσσας εξελίσσεται προς τη στατική ανάλυση ονομάτων, την ώρα της «μεταγλώττισης», επομένως μην βασίζεστε σε δυναμική ανάλυση ονόματος! (Στην πραγματικότητα, οι τοπικές μεταβλητές έχουν ήδη καθοριστεί στατικά.)

Μια ιδιαίτερη ιδιορρυθμία της Python είναι ότι – αν οι δηλώσεις global ή nonlocal δεν είναι σε ισχύ – οι εκχωρήσεις στα ονόματα πηγαίνουν πάντα στην ενδότερη εμβέλεια. Οι εκχωρήσεις δεν αντιγράφουν δεδομένα — απλώς δεσμεύουν ονόματα σε αντικείμενα. Το ίδιο ισχύει και για τις διαγραφές: η δήλωση del x αφαιρεί την σύνδεση του x από τον χώρο ονομάτων που αναφέρεται από την τοπική εμβέλεια. Στην πραγματικότητα, όλες οι λειτουργίες που εισάγουν νέα ονόματα χρησιμοποιούν την τοπική εμβέλεια: συγκεκριμένα οι δηλώσεις, import και οι ορισμοί συναρτήσεων δεσμεύουν το όνομα του module ή της συνάρτησης στην τοπική εμβέλεια.

Η δήλωση global μπορεί να χρησιμοποιηθεί για να υποδείξει ότι συγκεκριμένες μεταβλητές ζουν στην καθολική εμβέλεια και θα πρέπει να ανακάμψουν εκεί. Η δήλωση nonlocal υποδηλώνει ότι συγκεκριμένες μεταβλητές ζουν σε μια εσώκλειστη εμβέλεια και θα πρέπει να ανακάμψουν εκεί.

9.2.1. Παράδειγμα Εμβέλειας και Χώρων Ονομάτων

Αυτό είναι ένα παράδειγμα που δείχνει τον τρόπο αναφοράς στα διαφορετικά πεδία και χώρους ονομάτων και πώς τα global και nonlocal επηρεάζουν τα variable binding:

def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

Το αποτέλεσμα του κώδικα στο παράδειγμα είναι:

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

Σημειώστε πώς η εκχώρηση τοπική (η οποία είναι προεπιλεγμένη) δεν άλλαξε την δέσμευση scope_test's του spam. Η εκχώρηση nonlocal άλλαξε την δέσμευση του scope_test's του spam και η εκχώρηση του global άλλαξε τη δέσμευση σε επίπεδο module.

Μπορείτε επίσης να δείτε ότι δεν υπήρχε προηγούμενη δέσμευση για spam πριν από την εκχώρηση global..

9.3. Μια πρώτη ματιά στις Κλάσεις

Οι Κλάσεις εισάγουν λίγη νέα σύνταξη, τρεις νέους τύπους αντικειμένων και κάποια νέα σημασιολογία.

9.3.1. Σύνταξη Ορισμού Κλάσης

Η απλούστερη μορφή ορισμού κλάσης μοιάζει με αυτό:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

Ορισμοί κλάσεων, όπως ορισμοί συναρτήσεων (def δηλώσεις) πρέπει να εκτελεστούν προτού έχουν οποιοδήποτε αποτέλεσμα. (Θα μπορούσατε να τοποθετήσετε έναν ορισμό κλάσης σε έναν κλάδο μιας δήλωσης if ή μέσα σε μια συνάρτηση.)

Στην πράξη, οι δηλώσεις μέσα σε έναν ορισμό κλάσης συνήθως θα είναι ορισμοί συναρτήσεων, αλλά επιτρέπονται άλλες δηλώσεις και μερικές φορές χρήσιμες — θα επανέλθουμε σε αυτό αργότερα. Οι ορισμοί συναρτήσεων μέσα σε μια κλάση συνήθως έχουν μια περίεργη μορφή λίστας ορισμάτων, που υπαγορεύεται από τις συμβάσεις κλήσης για μεθόδους — και πάλι, αυτό εξηγείται αργότερα.

Όταν εισάγεται ένας ορισμός κλάσης, δημιουργείται ένας νέος χώρος ονομάτων και χρησιμοποιείται ως τοπική εμβέλεια — επομένως, όλες οι εκχωρήσεις σε τοπικές μεταβλητές πηγαίνουν σε αυτόν τον νέο χώρο ονομάτων. Συγκεκριμένα, οι ορισμοί συναρτήσεων δεσμεύουν το όνομα της νέας συνάρτησης εδώ.

Όταν ένας ορισμός κλάσης αφήνεται κανονικά (μέσω του τέλους),δημιουργείται ένα αντικείμενο κλάσης. Αυτό είναι βασικά ένα περιτύλιγμα γύρω από τα περιεχόμενα του χώρου ονομάτων που δημιουργήθηκε από τον ορισμό κλάσης. θα μάθουμε περισσότερα για τα αντικείμενα κλάσης στην επόμενη ενότητα.Η αρχική τοπική εμβέλεια(αυτό που ίσχυε λίγο πριν από την εισαγωγή του ορισμού της κλάσης) αποκαθίσταται και το αντικείμενο κλάσης δεσμεύεται εδώ στο όνομα κλάσης που δίνεται στην κεφαλίδα ορισμού κλάσης (ClassName στο παράδειγμα).

9.3.2. Αντικείμενα Κλάσης

Τα αντικείμενα κλάσης υποστηρίζουν δύο είδη πράξεων: αναφορές χαρακτηριστικών και στιγμιότυπο.

Οι Αναφορές χαρακτηριστικών χρησιμοποιούν την τυπική σύνταξη που χρησιμοποιείται για όλα τις αναφορές χαρακτηριστικών στην Python: obj.name. Τα έγκυρα ονόματα χαρακτηριστικών είναι όλα τα ονόματα που βρίσκονταν στον χώρο ονομάτων της κλάσης όταν δημιουργήθηκε το αντικείμενο της κλάσης. Έτσι, αν ο ορισμός της κλάσης έμοιαζε ως εξής:

class MyClass:
    """A simple example class"""
    i = 12345

    def f(self):
        return 'hello world'

τότε τα MyClass.i και MyClass.f είναι έγκυρες αναφορές χαρακτηριστικών, επιστρέφοντας έναν ακέραιο και ένα αντικείμενο συνάρτησης, αντίστοιχα.Τα attributes κλάσης μπορούν επίσης να εκχωρηθούν, ώστε να μπορείτε να αλλάξετε την τιμή του MyClass.i ανά ανάθεση. Το __doc__ είναι επίσης ένα έγκυρο χαρακτηριστικό, επιστρέφοντας το docstring που ανήκει στην κλάση: "Ένα απλό παράδειγμα κλάσης".

Η κλάση στιγμιότυπο χρησιμοποιεί σημειογραφία συνάρτησης. Απλώς προσποιηθείτε ότι το αντικείμενο της κλάσης είναι μια συνάρτηση χωρίς παραμέτρους που επιστρέφει ένα νέο στιγμιότυπο της κλάσης.Για παράδειγμα (υποθέτοντας την παραπάνω κλάση):

x = MyClass()

δημιουργεί ένα νέο στιγμιότυπο της κλάσης και εκχωρεί αυτό το αντικείμενο στην τοπική μεταβλητή x.

Η λειτουργία του στιγμιότυπου («calling» ένα αντικείμενο κλάσης) δημιουργεί ένα κενό αντικείμενο. Σε πολλές κλάσεις αρέσει να δημιουργούν αντικείμενα με στιγμιότυπα προσαρμοσμένα σε μια συγκεκριμένη αρχική κατάσταση. Επομένως μια κλάση μπορεί να ορίσει μια ειδική μέθοδο με το όνομα __init__(), όπως αυτό:

def __init__(self):
    self.data = []

Όταν μια κλάση ορίζει μια μέθοδο __init__(), το στιγμιότυπο κλάσης καλεί αυτόματα __init__() για το στιγμιότυπο κλάσης που δημιουργήθηκε πρόσφατα. Έτσι σε αυτό το παράδειγμα,ένα νέο, αρχικοποιημένο στιγμιότυπο μπορεί να ληφθεί από:

x = MyClass()

Φυσικά, η μέθοδος __init__() μπορεί να έχει ορίσματα για μεγαλύτερη ευελιξία. Σε αυτήν την περίπτωση, τα ορίσματα που δίνονται στον τελεστή στιγμιότυπου κλάσης μεταβιβάζονται σε __init__(). Για παράδειγμα,

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

9.3.3. Αντικείμενα Στιγμιοτύπων

Τώρα τι μπορούμε να κάνουμε με τα αντικείμενα στιγμιοτύπων; Οι μόνες λειτουργίες που γίνονται κατανοητές από τα αντικείμενα στιγμιοτύπων είναι οι αναφορές χαρακτηριστικών. Υπάρχουν δύο είδη έγκυρων ονομάτων attributes: attributes και μέθοδοι δεδομένων.

τα attributes δεδομένων αντιστοιχούν στις «μεταβλητές στιγμιότυπου» στο Smalltalk και στα «μέλη δεδομένων» στη C++. Τα attributes δεδομένων δεν χρειάζεται να δηλωθούν, όπως και οι τοπικές μεταβλητές, εμφανίζονται όταν εκχωρούνται για πρώτη φορά. Για παράδειγμα, εάν το x είναι στιγμιότυπο του MyClass που δημιουργήθηκε παραπάνω, το ακόλουθο κομμάτι κώδικα θα εκτυπώσει την τιμή 16, χωρίς να αφήσει ίχνος:

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter)
del x.counter

Το άλλο είδος αναφοράς χαρακτηριστικού στιγμιότυπου είναι μια μέθοδος. Μια μέθοδος είναι μια συνάρτηση που «ανήκει» σε ένα αντικείμενο.

Τα έγκυρα ονόματα μεθόδων ενός αντικειμένου στιγμιότυπου εξαρτώνται από την κλάση του. Εξ ορισμού, όλα τα χαρακτηριστικά μιας κλάσης που είναι αντικείμενα συνάρτησης ορίζουν τις αντίστοιχες μεθόδους των στιγμιοτύπων της. Έτσι στο παράδειγμά μας, το x.f είναι μια έγκυρη αναφορά μεθόδου, αφού το MyClass.f είναι συνάρτηση, αλλά το x.i δεν είναι αφού το MyClass.i δεν είναι. Αλλά το x.f δεν είναι το ίδιο πράγμα με το MyClass.f — είναι ένα αντικείμενο μεθόδου, όχι ένα αντικείμενο συνάρτησης.

9.3.4. Αντικείμενα Μεθόδου

Συνήθως, μια μέθοδος καλείται αμέσως μετά τη δέσμευσή της:

x.f()

Στο παράδειγμα MyClass, αυτό θα επιστρέψει τη συμβολοσειρά 'hello world'. Ωστόσο, δεν είναι απαραίτητο να καλέσετε μια μέθοδο αμέσως: το x.f είναι ένα αντικείμενο μεθόδου και μπορεί να αποθηκευτεί και να κληθεί αργότερα. Για παράδειγμα:

xf = x.f
while True:
    print(xf())

θα συνεχίσει να εκτυπώνει το hello world μέχρι το τέλος του χρόνου.

Τι ακριβώς συμβαίνει όταν καλείται μια μέθοδος; Ίσως έχετε παρατηρήσει ότι x.f() κλήθηκε χωρίς όρισμα παραπάνω, παρόλο που ο ορισμός της συνάρτησης για f() καθόριζε ένα όρισμα. Τι συνέβη με το όρισμα; Σίγουρα η Python δημιουργεί μια εξαίρεση όταν μια συνάρτηση που απαιτεί όρισμα καλείται χωρίς — ακόμα κι αν το όρισμα δεν χρησιμοποιείται στην πραγματικότητα..

Στην πραγματικότητα, μπορεί να έχετε μαντέψει την απάντηση: το ιδιαίτερο με τις μεθόδους είναι ότι το αντικείμενο του στιγμιότυπου μεταβιβάζεται ως το πρώτο όρισμα της συνάρτησης. Στο παράδειγμά μας, η κλήση x.f() είναι ακριβώς ισοδύναμη με το MyClass.f(x). Γενικά, η κλήση μιας μεθόδου με μια λίστα από n ορίσματα ισοδυναμεί με την κλήση της αντίστοιχης συνάρτησης με μια λίστα ορισμάτων που δημιουργείται με την εισαγωγή του αντικειμένου στιγμιότυπου της μεθόδου πριν από το πρώτο όρισμα.

Γενικά, οι μέθοδοι λειτουργούν ως εξής. Όταν γίνεται αναφορά σε ένα χαρακτηριστικό μη δεδομένων ενός στιγμιότυπου, γίνεται η αναζήτηση της κλάσης του στιγμιότυπου. Εάν το όνομα υποδηλώνει ένα έγκυρο χαρακτηριστικό κλάσης που είναι αντικείμενο συνάρτησης, οι αναφορές τόσο στο αντικείμενο στιγμιότυπου όσο και στο αντικείμενο συνάρτησης συσκευάζονται σε ένα αντικείμενο μεθόδου. Όταν το αντικείμενο της μεθόδου καλείται με μια λίστα ορισμάτων,δημιουργείται μια νέα λίστα ορισμάτων από το αντικείμενο στιγμιότυπου και τη λίστα ορισμάτων, και το αντικείμενο συνάρτησης καλείται με αυτήν τη νέα λίστα ορισμάτων.

9.3.5. Μεταβλητές Κλάσης και Στιγμιότυπου

Σε γενικές γραμμές, οι μεταβλητές στιγμιότυπου προορίζονται για δεδομένα μοναδικά για κάθε στιγμιότυπο και οι μεταβλητές κλάσης είναι για χαρακτηριστικά και μεθόδους που μοιράζονται όλα τα στιγμιότυπα της κλάσης:

class Dog:

    kind = 'canine'         # class variable shared by all instances

    def __init__(self, name):
        self.name = name    # instance variable unique to each instance

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind                  # shared by all dogs
'canine'
>>> e.kind                  # shared by all dogs
'canine'
>>> d.name                  # unique to d
'Fido'
>>> e.name                  # unique to e
'Buddy'

Όπως συζητήθηκε στο Λίγα λόγια για Ονόματα και Αντικείμενα, τα κοινά δεδομένα μπορεί να έχουν πιθανώς εκπληκτικά αποτελέσματα με τη συμμετοχή αντικειμένων mutable όπως λίστες και λεξικά. Για παράδειγμα, η λίστα tricks στον παρακάτω κώδικα δεν θα πρέπει να χρησιμοποιείται ως μεταβλητή κλάσης επειδή μόνο μία λίστα θα μπορούσε να είναι κοινή σε όλα τα στιγμιότυπα Dog:

class Dog:

    tricks = []             # mistaken use of a class variable

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # unexpectedly shared by all dogs
['roll over', 'play dead']

Ο σωστός σχεδιασμός της κλάσης θα πρέπει να χρησιμοποιεί μια μεταβλητή στιγμιότυπου αντί:

class Dog:

    def __init__(self, name):
        self.name = name
        self.tricks = []    # creates a new empty list for each dog

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']

9.4. Τυχαίες Παρατηρήσεις

Αν το ίδιο όνομα χαρακτηριστικού εμφανίζεται και σε ένα στιγμιότυπο και σε μια κλάση, τότε η αναζήτηση χαρακτηριστικών δίνει προτεραιότητα στο στιγμιότυπο:

>>> class Warehouse:
...    purpose = 'storage'
...    region = 'west'
...
>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east

Τα χαρακτηριστικά δεδομένων μπορούν να αναφέρονται με μεθόδους καθώς και από απλούς χρήστες («πελάτες») ενός αντικειμένου. Με άλλα λόγια, οι κλάσεις δεν μπορούν να χρησιμοποιηθούν για την υλοποίηση καθαρών αφηρημένων τύπων δεδομένων. Στην πραγματικότητα, τίποτα στην Python δεν καθιστά δυνατή την επιβολή της απόκρυψης δεδομένων — όλα βασίζονται σε σύμβαση. (από την άλλη πλευρά, η εφαρμογή Python, γραμμένη σε C, μπορεί να αποκρύψει εντελώς τις λεπτομέρειες υλοποίησης και να ελέγξει την πρόσβαση σε ένα αντικείμενο εάν είναι απαραίτητο αυτό μπορεί να χρησιμοποιηθεί από επεκτάσεις στην Python γραμμένες σε C.)

Οι χρήστες θα πρέπει να χρησιμοποιούν τα χαρακτηριστικά δεδομένων με προσοχή — οι χρήστες ενδέχεται να μπερδέψουν τα αμετάβλητα που διατηρούνται από τις μεθόδους σφραγίζοντας τα χαρακτηριστικά των δεδομένων τους. Λάβετε υπόψη ότι οι χρήστες μπορούν να προσθέσουν δικά τους χαρακτηριστικά δεδομένων σε ένα αντικείμενο στιγμιότυπου χωρίς να επηρεάσουν την εγκυρότητα των μεθόδων, εφόσον αποφεύγονται οι συγκρούσεις ονομάτων — και πάλι, μια σύμβαση ονομασίας μπορεί να σώσει πολλούς πονοκεφάλους εδώ.

Δεν υπάρχει συντομογραφία για την αναφορά χαρακτηριστικών δεδομένων (ή άλλων μεθόδων!) μέσα από τις μεθόδους. Διαπιστώνω ότι αυτό στην πραγματικότητα αυξάνει την αναγνωσιμότητα των μεθόδων: δεν υπάρχει καμία πιθανότητα σύγχυσης τοπικών μεταβλητών και των μεταβλητών παραδείγματος όταν εξετάζουμε μια μέθοδο.

Συχνά, το πρώτο όρισμα μιας μεθόδου ονομάζεται self. Αυτό δεν είναι τίποτα περισσότερο από μια σύμβαση: το όνομα self δεν έχει καμία απολύτως ιδιαίτερη σημασία για την Python. Σημειώστε, ωστόσο, ότι αν δεν ακολουθήσετε τη σύμβαση ο κώδικάς σας μπορεί να είναι λιγότερο ευανάγνωστος σε άλλους προγραμματιστές Python, και είναι επίσης κατανοητό ότι μπορεί να γραφτεί ένα πρόγραμμα class browser που να βασίζεται σε μια τέτοια σύμβαση.

Κάθε αντικείμενο συνάρτησης που είναι χαρακτηριστικό κλάσης ορίζει μια μέθοδο για στιγμιότυπα αυτής της κλάσης. Δεν είναι απαραίτητο ο ορισμός της συνάρτησης να περικλείεται με κείμενο στον ορισμό της κλάσης: η αντιστοίχηση ενός αντικειμένου συνάρτησης σε μια τοπική μεταβλητή της κλάσης είναι επίσης εντάξει. Για παράδειγμα:

# Function defined outside the class
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1

    def g(self):
        return 'hello world'

    h = g

Τώρα τα f, g και h είναι όλα χαρακτηριστικά της κλάσης C που αναφέρονται σε αντικείμενα συνάρτησης, και κατά συνέπεια είναι όλες μέθοδοι στιγμιοτύπων του C — Το h είναι ακριβώς ισοδύναμο με το g. Σημειώστε ότι αυτή η πρακτική συνήθως χρησιμεύει μόνο για να μπερδέψει τον αναγνώστη ενός προγράμματος.

Οι μέθοδοι μπορούν να καλούν άλλες μεθόδους χρησιμοποιώντας χαρακτηριστικά μεθόδου του argument self:

class Bag:
    def __init__(self):
        self.data = []

    def add(self, x):
        self.data.append(x)

    def addtwice(self, x):
        self.add(x)
        self.add(x)

Οι μέθοδοι μπορεί να αναφέρονται σε καθολικά ονόματα με τον ίδιο τρόπο όπως οι συνηθισμένες συναρτήσεις.Η καθολική εμβέλεια που σχετίζεται με μια μέθοδο είναι το module που περιέχει τον ορισμό της. (Μια κλάση δεν χρησιμοποιείται ποτέ ως καθολική εμβέλεια.) Αν και σπάνια συναντά κανείς έναν καλό λόγο για τη χρήση καθολικών δεδομένων σε μια μέθοδο, υπάρχουν πολλές Νόμιμες χρήσεις της καθολικής εμβέλειας: για ένα πράγμα, οι λειτουργίες και οι λειτουργικές μονάδες που εισάγονται στην καθολική εμβέλεια μπορούν να χρησιμοποιηθούν από μεθόδους, καθώς και συναρτήσεις και κλάσεις που ορίζονται σε αυτό. Συνήθως, η κλάση που περιέχει τη μέθοδο ορίζεται από μόνη της σε αυτή την καθολική εμβέλεια, και στην επόμενη ενότητα θα βρούμε μερικούς καλούς λόγους για τους οποίους μια μέθοδος θα ήθελε να αναφέρει τη δική της κλάση.

Κάθε τιμή είναι ένα αντικείμενο και επομένως έχει μια κλάση (ονομάζεται επίσης τύπος της). Αποθηκεύεται ως object.__class__.

9.5. Κληρονομικότητα

Φυσικά, ένα χαρακτηριστικό γλώσσας δεν θα ήταν αντάξιο του ονόματος «class» χωρίς την υποστήριξη της κληρονομικότητας. Η σύνταξη για έναν παραγόμενο ορισμό κλάσης μοιάζει με αυτό:

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

Το όνομα BaseClassName πρέπει να οριστεί σε έναν χώρο ονομάτων προσβάσιμο από το πεδίο που περιέχει τον παραγόμενο ορισμό κλάσης. Στη θέση ενός ονόματος βασικής κλάσης, επιτρέπονται και άλλες αυθαίρετες εκφράσεις. Αυτό μπορεί να είναι χρήσιμο, για παράδειγμα, όταν η βασική κλάση ορίζεται σε άλλη module:

class DerivedClassName(modname.BaseClassName):

Η εκτέλεση ενός παραγόμενου ορισμού κλάσης προχωρά το ίδιο όπως για μια βασική κλάση. Όταν το αντικείμενο της κλάσης κατασκευάζεται, η βασική κλάση απομνημονεύεται.Αυτό χρησιμοποιείται για την επίλυση αναφορών χαρακτηριστικών: εάν ένα ζητούμενο χαρακτηριστικό δεν βρεθεί στην κλάση, η αναζήτηση προχωρά στην αναζήτηση στη βασική κλάση. Αυτός ο κανόνας εφαρμόζεται αναδρομικά εάν η ίδια η βασική κλάση προέρχεται από κάποια άλλη κλάση.

Δεν υπάρχει τίποτα το ιδιαίτερο σχετικά με την δημιουργία στιγμιότυπου παραγόμενων κλάσεων: DerivedClassName() δημιουργεί ένα νέο στιγμιότυπο της κλάσης. Οι αναφορές μεθόδων επιλύονται ως εξής: γίνεται αναζήτηση του αντίστοιχου χαρακτηριστικού κλάσης, κατεβαίνοντας προς τα κάτω στην αλυσίδα των βασικών κλάσεων εάν είναι απαραίτητο, και η αναφορά της μεθόδου είναι έγκυρη εάν αυτό αποδίδει ένα αντικείμενο συνάρτησης.

Οι παράγωγες κλάσεις ενδέχεται να παρακάμπτουν τις μεθόδους των βασικών τους κλάσεων. Επειδή οι μέθοδοι δεν έχουν ειδικά προνόμια όταν καλούν άλλες μεθόδους του ίδιου αντικειμένου, μια μέθοδος μιας βασικής κλάσης που καλεί μια άλλη μέθοδο που ορίζεται στην ίδια βασική κλάση μπορεί να καταλήξει να καλεί μια μέθοδο μιας παραγόμενης κλάσης που την αντικαθιστά.(Για προγραμματιστές C++: όλες οι μέθοδοι στην Python είναι ουσιαστικά «εικονικές».)

Μια υπερισχύουσα μέθοδος σε μια παραγόμενη κλάση μπορεί στην πραγματικότητα να θέλει να επεκτείνει αντί να αντικαταστήσει απλώς τη μέθοδο βασικής κλάσης με το ίδιο όνομα. Υπάρχει ένας απλός τρόπος για να καλέσετε τη μέθοδο βασικής κλάσης απευθείας: απλώς καλέστε το BaseClassName.methodname(self, arguments)`. Αυτό είναι περιστασιακά χρήσιμο στους χρήστες (Λάβετε υπόψη ότι αυτό λειτουργεί μόνο εάν η βασική κλάση είναι προσβάσιμη ως ``BaseClassName στην καθολική εμβέλεια.)

Η Python έχει δύο (ενσωματωμένες) built-in συναρτήσεις που λειτουργούν με κληρονομικότητα:

  • Χρησιμοποιήστε το isinstance() για να ελέγξετε τον τύπο ενός στιγμιότυπου: το isinstance(obj, int) θα είναι True μόνο εάν το obj.__class__ είναι int ή προέρχεται από κάποια κλάση από int.

  • Χρησιμοποιήστε το issubclass() για να ελέγξετε την κληρονομικότητα κλάσης: Το issubclass(bool, int) είναι `` True`` αφού το bool είναι υποκλάση του int. Ωστόσο, το issubclass(float, int) είναι False αφού το float δεν είναι υποκλάση του int.

9.5.1. Πολλαπλή Κληρονομικότητα

Η Python υποστηρίζει επίσης μια μορφή πολλαπλής κληρονομικότητας. Ένας ορισμός κλάσης με πολλαπλές βασικές κλάσεις μοιάζει με αυτό:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

Για τους περισσότερους σκοπούς, στις πιο απλές περιπτώσεις, μπορείτε να σκεφτείτε την αναζήτηση χαρακτηριστικών που κληρονομήθηκαν από μια γονική κλάση ως depth-first, από αριστερά προς τα δεξιά, χωρίς αναζήτηση δύο φορές στην ίδια κλάση όπου υπάρχει επικάλυψη στην ιεραρχία. Έτσι, εάν δεν βρεθεί ένα χαρακτηριστικό στο DerivedClassName, αναζητείται στο Base1 και μετά (αναδρομικά) στις βασικές κλάσεις του Base1, και αν δεν βρέθηκε εκεί,αναζητήθηκε στο Base2, και ούτω καθεξής.

Στην πραγματικότητα, είναι λίγο πιο περίπλοκο από αυτό. Η σειρά ανάλυσης της μεθόδου αλλάζει δυναμικά για να υποστηρίξει συνεργατικές κλήσεις σε super(). Αυτή η προσέγγιση είναι γνωστή σε ορισμένες άλλες γλώσσες πολλαπλής κληρονομικότητας ως call-next-method και είναι πιο ισχυρή από τη σούπερ κλήση που βρίσκεται σε γλώσσες μεμονωμένης κληρονομικότητας.

Η δυναμική σειρά είναι απαραίτητη επειδή όλες οι περιπτώσεις πολλαπλής κληρονομικότητας εμφανίζουν μία ή περισσότερες σχέσεις διαμαντιών (όπου τουλάχιστον μια από τις γονικές κλάσεις μπορεί να προσπελαστεί μέσω πολλαπλών διαδρομών από την κατώτατη κλάση). Για παράδειγμα, όλες οι κλάσεις κληρονομούν από object, επομένως κάθε περίπτωση πολλαπλής κληρονομικότητας παρέχει περισσότερες από μία διαδρομές για να φτάσετε στο object. Για να μην υπάρχει πρόσβαση στις βασικές κλάσεις περισσότερες από μία φορές, ο δυναμικός αλγόριθμος γραμμικοποιεί τη σειρά αναζήτησης με τρόπο που διατηρεί τη σειρά από αριστερά προς τα δεξιά-που καθορίζεται σε κάθε κλάση, που καλεί κάθε γονέα μόνο μία φορά, και που είναι μονότονος (που σημαίνει ότι μια κλάση μπορεί να γίνει υπό-κλάση χωρίς να επηρεαστεί η σειρά προτεραιότητας των γονέων της). Συνολικά, αυτές οι ιδιότητες καθιστούν δυνατό τον σχεδιασμό αξιόπιστων και επεκτάσιμων κλάσεων με πολλαπλή κληρονομικότητα. Για περισσότερες λεπτομέρειες, δείτε The Python 2.3 Method Resolution Order.

9.6. Ιδιωτικές Μεταβλητές

Οι μεταβλητές στιγμιοτύπου «Private» στις οποίες δεν είναι δυνατή η πρόσβαση εκτός από το εσωτερικό ενός αντικειμένου,δεν υπάρχουν στην Python. Ωστόσο, υπάρχει μια σύμβαση που ακολουθείται από τον περισσότερο Python κώδικα: ένα όνομα με πρόθεμα κάτω παύλα (π.χ. _spam) θα πρέπει να αντιμετωπίζεται ως μη δημόσιο μέρος του API (είτε πρόκειται για συνάρτηση,μέθοδο ή μέλος δεδομένων). Θα πρέπει να θεωρείται ως λεπτομέρεια υλοποίησης και υπόκειται σε αλλαγές χωρίς προειδοποίηση.

Δεδομένου ότι υπάρχει μια έγκυρη περίπτωση χρήσης για ιδιωτικά μέλη της κλάσης (δηλαδή για να αποφευχθούν συγκρούσεις ονομάτων με ονόματα που ορίζονται από υποκλάσεις), υπάρχει περιορισμένη υποστήριξη για έναν τέτοιο μηχανισμό, που ονομάζεται name mangling. Οποιοδήποτε αναγνωριστικό της φόρμας __spam (τουλάχιστον δύο προπορευόμενες κάτω παύλες, το πολύ μια στη συνέχεια κάτω παύλα) αντικαθίσταται με κείμενο με το _classname__spam, όπου το classname είναι το όνομα της τρέχουσας τάξης με την πρώτη υπογράμμιση stripped. Αυτό το mangling γίνεται χωρίς να λαμβάνεται υπόψη η συντακτική θέση του του αναγνωριστικού, αρκεί να εμφανίζεται εντός του ορισμού μιας κλάσης.

Δείτε επίσης

Οι προδιαγραφές παραποίησης ιδιωτικού ονόματος για λεπτομέρειες και ειδικές περιπτώσεις.

Η παραβίαση ονομάτων είναι χρήσιμη για να επιτρέπεται στις υποκλάσεις να παρακάμπτουν μεθόδους χωρίς να διακόπτουν τις κλήσεις μεθόδων ενδοκλάσεων. Για παράδειγμα:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

Το παραπάνω παράδειγμα θα λειτουργούσε ακόμα και αν το MappingSubclass εισήγαγε ένα αναγνωριστικό __update αφού αντικαταστάθηκε με το _Mapping__update στην κλάση Mapping και με το _MappingSubclass__update στη κλάση MappingSubclass αντίστοιχα.

Λάβετε υπόψη ότι οι κανόνες παραβίασης έχουν σχεδιαστεί κυρίως για την αποφυγή ατυχημάτων.Εξακολουθεί να είναι δυνατή η πρόσβαση ή η τροποποίηση μιας μεταβλητής που θεωρείται ιδιωτική. Αυτό μπορεί να είναι χρήσιμο ακόμη και σε ειδικές περιπτώσεις, όπως στο πρόγραμμα εντοπισμού σφαλμάτων(debugger).

Σημειώστε ότι ο κώδικας που μεταβιβάστηκε στο exec() ή στο eval() δεν θεωρεί ότι το το όνομα κλάσης της κλάσης επίκλησης να είναι η τρέχουσα κλάση. Αυτό είναι παρόμοιο με το αποτέλεσμα της καθολικής δήλωσης , το αποτέλεσμα της οποίας επίσης περιορίζεται στον κώδικα που έχει μεταγλωττιστεί μαζί(byte-compiled). Ο ίδιος περιορισμός ισχύει για τα getattr(), setattr() και delattr(), καθώς και όταν γίνεται αναφορά απευθείας στο __dict__.

9.7. Μικροπράγματα

Μερικές φορές είναι χρήσιμο να έχετε έναν τύπο δεδομένων παρόμοιο με τον Pascal «record» ή C «struct», ομαδοποιώντας μερικά επώνυμα στοιχεία δεδομένων. Η ιδιωματική προσέγγιση είναι η χρήση dataclasses για αυτόν τον σκοπό:

from dataclasses import dataclass

@dataclass
class Employee:
    name: str
    dept: str
    salary: int
>>> john = Employee('john', 'computer lab', 1000)
>>> john.dept
'computer lab'
>>> john.salary
1000

Ένα κομμάτι κώδικα Python που αναμένει έναν συγκεκριμένο αφηρημένο τύπο δεδομένων μπορεί συχνά να περάσει σε μια κλάση που μιμείται τις μεθόδους αυτού του τύπου δεδομένων. Για παράδειγμα, εάν έχετε μια συνάρτηση που μορφοποιεί ορισμένα δεδομένα από ένα αντικείμενο αρχείου, μπορείτε να ορίσετε μια κλάση με μεθόδους read() και readline() που λαμβάνουν τα δεδομένα από ένα buffer συμβολοσειρών αντ” αυτού, και περάστε το ως όρισμα.

Instance method objects έχουν επίσης χαρακτηριστικά: m.__self__ είναι το αντικείμενο παρουσίας με τη μέθοδο m(), και m.__func__ είναι το αντικείμενο συνάρτησης που αντιστοιχεί στη μέθοδο.

9.8. Επαναλήπτες

Μέχρι τώρα πιθανότατα έχετε παρατηρήσει ότι τα περισσότερα αντικείμενα container μπορούν να επαναληφθούν χρησιμοποιώντας μια δήλωση for:

for element in [1, 2, 3]:
    print(element)
for element in (1, 2, 3):
    print(element)
for key in {'one':1, 'two':2}:
    print(key)
for char in "123":
    print(char)
for line in open("myfile.txt"):
    print(line, end='')

Αυτό το στυλ πρόσβασης είναι σαφές, συνοπτικό και βολικό. Η χρήση των Iterators διαπερνά και ενοποιεί την Python. Στο παρασκήνιο, η δήλωση for καλεί iter() στο αντικείμενο container. Η συνάρτηση επιστρέφει ένα αντικείμενο iterator που ορίζει τη μέθοδο __next__() η οποία έχει πρόσβαση σε στοιχεία στο container ένα κάθε φορά. Όταν δεν υπάρχουν άλλα στοιχεία, το __next__() δημιουργεί μια StopIteration εξαίρεση που λέει τον βρόχο for να τερματιστεί. Μπορείτε να καλέσετε τη μέθοδο __next__() χρησιμοποιώντας την ενσωματωμένη συνάρτηση next(). Αυτό το παράδειγμα δείχνει πώς λειτουργούν όλα:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x10c90e650>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    next(it)
StopIteration

Έχοντας δει τους μηχανισμούς πίσω από το πρωτόκολλο iterator, είναι εύκολο να προσθέσετε συμπεριφορά iterator στις κλάσεις σας. Ορίστε μια μέθοδο __iter__() που επιστρέφει ένα αντικείμενο με μια μέθοδο __next__(). Εάν η κλάση ορίζει __next__(), τότε το __iter__() μπορεί απλώς να επιστρέψει self:

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print(char)
...
m
a
p
s

9.9. Γεννήτορες (Generators)

Generators είναι ένα απλό και ισχυρό εργαλείο για τη δημιουργία iterators. Είναι γραμμένες σαν κανονικές συναρτήσεις αλλά χρησιμοποιούν τη yield όποτε θέλουν να επιστρέψουν δεδομένα. Κάθε φορά που καλείται next() σε αυτό, ο generator συνεχίζει από εκεί που σταμάτησε (θυμάται όλες τις τιμές δεδομένων και ποια δήλωση εκτελέστηκε τελευταία). Ένα παράδειγμα δείχνει ότι οι generators μπορεί να είναι ασήμαντα εύκολο να δημιουργηθούν:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]
>>> for char in reverse('golf'):
...     print(char)
...
f
l
o
g

Οτιδήποτε μπορεί να γίνει με generators μπορεί να γίνει και με iterators που βασίζονται σε κλάσεις, όπως περιγράφεται στην προηγούμενη ενότητα. Αυτό που κάνει τους generators τόσο συμπαγείς είναι ότι οι μέθοδοι __iter__() και generator__next__() δημιουργούνται αυτόματα.

Ένα άλλο βασικό χαρακτηριστικό είναι ότι οι τοπικές μεταβλητές και η κατάσταση εκτέλεσης αποθηκεύονται αυτόματα μεταξύ των κλήσεων. Αυτό έκανε τη συνάρτηση πιο εύκολη στην γραφή και πολύ πιο ξεκάθαρη από μια προσέγγιση που χρησιμοποιεί μεταβλητές παράδειγμα όπως self.index και self.data.

Εκτός από την αυτόματη δημιουργία μεθόδου και την αποθήκευση της κατάστασης του προγράμματος, όταν οι generators τερματίζονται, εγείρουν αυτόματα την εξαίρεση StopIteration. Σε συνδυασμό, αυτά τα χαρακτηριστικά καθιστούν εύκολη τη δημιουργία επαναλήψεων χωρίς περισσότερη προσπάθεια από τη σύνταξη μιας κανονικής συνάρτησης.

9.10. Εκφράσεις Γεννήτορων

Ορισμένοι απλοί generators μπορούν να κωδικοποιηθούν συνοπτικά ως εκφράσεις χρησιμοποιώντας μια σύνταξη παρόμοια με τις list comprehensions, αλλά με παρενθέσεις αντί για αγκύλες. Αυτές οι εκφράσεις έχουν σχεδιαστεί για καταστάσεις όπου ο generator χρησιμοποιείται αμέσως από μια περικλείουσα συνάρτηση. Οι εκφράσεις generator είναι πιο συμπαγείς αλλά λιγότερο ευέλικτες από τους ορισμούς πλήρους generator και τείνουν να είναι περισσότερο φιλικό προς τη μνήμη από αντίστοιχα list comprehensions.

Παραδείγματα:

>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
260

>>> unique_words = set(word for line in page  for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

Υποσημειώσεις