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
. Για παράδειγμα,delmodname.the_answer
θα αφαιρέσει το attributethe_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:
defscope_test():defdo_local():spam="local spam"defdo_nonlocal():nonlocalspamspam="nonlocal spam"defdo_global():globalspamspam="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 spamAfter nonlocal assignment: nonlocal spamAfter global assignment: nonlocal spamIn global scope: global spam
Σημειώστε πώς η εκχώρησητοπική (η οποία είναι προεπιλεγμένη) δεν άλλαξε την δέσμευσηscope_test's τουspam. Η εκχώρησηnonlocal
άλλαξε την δέσμευση τουscope_test's τουspam και η εκχώρηση τουglobal
άλλαξε τη δέσμευση σε επίπεδο module.
Μπορείτε επίσης να δείτε ότι δεν υπήρχε προηγούμενη δέσμευση γιαspam πριν από την εκχώρησηglobal
..
9.3.Μια πρώτη ματιά στις Κλάσεις¶
Οι Κλάσεις εισάγουν λίγη νέα σύνταξη, τρεις νέους τύπους αντικειμένων και κάποια νέα σημασιολογία.
9.3.1.Σύνταξη Ορισμού Κλάσης¶
Η απλούστερη μορφή ορισμού κλάσης μοιάζει με αυτό:
classClassName:<statement-1>...<statement-N>
Ορισμοί κλάσεων, όπως ορισμοί συναρτήσεων (def
δηλώσεις) πρέπει να εκτελεστούν προτού έχουν οποιοδήποτε αποτέλεσμα. (Θα μπορούσατε να τοποθετήσετε έναν ορισμό κλάσης σε έναν κλάδο μιας δήλωσηςif
ή μέσα σε μια συνάρτηση.)
Στην πράξη, οι δηλώσεις μέσα σε έναν ορισμό κλάσης συνήθως θα είναι ορισμοί συναρτήσεων, αλλά επιτρέπονται άλλες δηλώσεις και μερικές φορές χρήσιμες — θα επανέλθουμε σε αυτό αργότερα. Οι ορισμοί συναρτήσεων μέσα σε μια κλάση συνήθως έχουν μια περίεργη μορφή λίστας ορισμάτων, που υπαγορεύεται από τις συμβάσεις κλήσης για μεθόδους — και πάλι, αυτό εξηγείται αργότερα.
Όταν εισάγεται ένας ορισμός κλάσης, δημιουργείται ένας νέος χώρος ονομάτων και χρησιμοποιείται ως τοπική εμβέλεια — επομένως, όλες οι εκχωρήσεις σε τοπικές μεταβλητές πηγαίνουν σε αυτόν τον νέο χώρο ονομάτων. Συγκεκριμένα, οι ορισμοί συναρτήσεων δεσμεύουν το όνομα της νέας συνάρτησης εδώ.
Όταν ένας ορισμός κλάσης αφήνεται κανονικά (μέσω του τέλους),δημιουργείται ένααντικείμενο κλάσης. Αυτό είναι βασικά ένα περιτύλιγμα γύρω από τα περιεχόμενα του χώρου ονομάτων που δημιουργήθηκε από τον ορισμό κλάσης. θα μάθουμε περισσότερα για τα αντικείμενα κλάσης στην επόμενη ενότητα.Η αρχική τοπική εμβέλεια(αυτό που ίσχυε λίγο πριν από την εισαγωγή του ορισμού της κλάσης) αποκαθίσταται και το αντικείμενο κλάσης δεσμεύεται εδώ στο όνομα κλάσης που δίνεται στην κεφαλίδα ορισμού κλάσης (ClassName
στο παράδειγμα).
9.3.2.Αντικείμενα Κλάσης¶
Τα αντικείμενα κλάσης υποστηρίζουν δύο είδη πράξεων: αναφορές χαρακτηριστικών και στιγμιότυπο.
ΟιΑναφορές χαρακτηριστικών χρησιμοποιούν την τυπική σύνταξη που χρησιμοποιείται για όλα τις αναφορές χαρακτηριστικών στην Python:obj.name
. Τα έγκυρα ονόματα χαρακτηριστικών είναι όλα τα ονόματα που βρίσκονταν στον χώρο ονομάτων της κλάσης όταν δημιουργήθηκε το αντικείμενο της κλάσης. Έτσι, αν ο ορισμός της κλάσης έμοιαζε ως εξής:
classMyClass:"""A simple example class"""i=12345deff(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__()
. Για παράδειγμα,
>>>classComplex:...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 και μέθοδοι δεδομένων.
Τα χαρακτηριστικά δεδομένων αντιστοιχούν στις «μεταβλητές στιγμιότυπου» στο Smalltalk και στα «μέλη δεδομένων» στη C++. Τα χαρακτηριστικά δεδομένων δεν χρειάζεται να δηλωθούν, όπως και οι τοπικές μεταβλητές, εμφανίζονται όταν εκχωρούνται για πρώτη φορά. Για παράδειγμα, εάν τοx
είναι στιγμιότυπο τηςMyClass
που δημιουργήθηκε παραπάνω, το ακόλουθο κομμάτι κώδικα θα εκτυπώσει την τιμή16
, χωρίς να αφήσει ίχνος:
x.counter=1whilex.counter<10:x.counter=x.counter*2print(x.counter)delx.counter
Το άλλο είδος αναφοράς χαρακτηριστικού στιγμιότυπου είναι μιαμέθοδος. Μια μέθοδος είναι μια συνάρτηση που «ανήκει» σε ένα αντικείμενο.
Τα έγκυρα ονόματα μεθόδων ενός αντικειμένου στιγμιότυπου εξαρτώνται από την κλάση του. Εξ ορισμού, όλα τα χαρακτηριστικά μιας κλάσης που είναι αντικείμενα συνάρτησης ορίζουν τις αντίστοιχες μεθόδους των στιγμιοτύπων της. Έτσι στο παράδειγμά μας, τοx.f είναι μια έγκυρη αναφορά μεθόδου, αφού τοMyClass.f είναι συνάρτηση, αλλά τοx.i δεν είναι αφού τοMyClass.i δεν είναι. Αλλά τοx.f δεν είναι το ίδιο πράγμα με τοMyClass.f — είναι ένααντικείμενο μεθόδου, όχι ένα αντικείμενο συνάρτησης.
9.3.4.Αντικείμενα Μεθόδου¶
Συνήθως, μια μέθοδος καλείται αμέσως μετά τη δέσμευσή της:
x.f()
Στο παράδειγμαMyClass
, αυτό θα επιστρέψει τη συμβολοσειρά'helloworld'
. Ωστόσο, δεν είναι απαραίτητο να καλέσετε μια μέθοδο αμέσως: τοx.f
είναι ένα αντικείμενο μεθόδου και μπορεί να αποθηκευτεί και να κληθεί αργότερα. Για παράδειγμα:
xf=x.fwhileTrue:print(xf())
θα συνεχίσει να εκτυπώνει τοhelloworld
μέχρι το τέλος του χρόνου.
Τι ακριβώς συμβαίνει όταν καλείται μια μέθοδος; Ίσως έχετε παρατηρήσει ότιx.f()
κλήθηκε χωρίς όρισμα παραπάνω, παρόλο που ο ορισμός της συνάρτησης γιαf()
καθόριζε ένα όρισμα. Τι συνέβη με το όρισμα; Σίγουρα η Python δημιουργεί μια εξαίρεση όταν μια συνάρτηση που απαιτεί όρισμα καλείται χωρίς — ακόμα κι αν το όρισμα δεν χρησιμοποιείται στην πραγματικότητα..
Στην πραγματικότητα, μπορεί να έχετε μαντέψει την απάντηση: το ιδιαίτερο με τις μεθόδους είναι ότι το αντικείμενο του στιγμιότυπου μεταβιβάζεται ως το πρώτο όρισμα της συνάρτησης. Στο παράδειγμά μας, η κλήσηx.f()
είναι ακριβώς ισοδύναμη με τοMyClass.f(x)
. Γενικά, η κλήση μιας μεθόδου με μια λίστα απόn ορίσματα ισοδυναμεί με την κλήση της αντίστοιχης συνάρτησης με μια λίστα ορισμάτων που δημιουργείται με την εισαγωγή του αντικειμένου στιγμιότυπου της μεθόδου πριν από το πρώτο όρισμα.
Γενικά, οι μέθοδοι λειτουργούν ως εξής. Όταν γίνεται αναφορά σε ένα χαρακτηριστικό μη δεδομένων ενός στιγμιότυπου, γίνεται η αναζήτηση της κλάσης του στιγμιότυπου. Εάν το όνομα υποδηλώνει ένα έγκυρο χαρακτηριστικό κλάσης που είναι αντικείμενο συνάρτησης, οι αναφορές τόσο στο αντικείμενο στιγμιότυπου όσο και στο αντικείμενο συνάρτησης συσκευάζονται σε ένα αντικείμενο μεθόδου. Όταν το αντικείμενο της μεθόδου καλείται με μια λίστα ορισμάτων,δημιουργείται μια νέα λίστα ορισμάτων από το αντικείμενο στιγμιότυπου και τη λίστα ορισμάτων, και το αντικείμενο συνάρτησης καλείται με αυτήν τη νέα λίστα ορισμάτων.
9.3.5.Μεταβλητές Κλάσης και Στιγμιότυπου¶
Σε γενικές γραμμές, οι μεταβλητές στιγμιότυπου προορίζονται για δεδομένα μοναδικά για κάθε στιγμιότυπο και οι μεταβλητές κλάσης είναι για χαρακτηριστικά και μεθόδους που μοιράζονται όλα τα στιγμιότυπα της κλάσης:
classDog:kind='canine'# class variable shared by all instancesdef__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:
classDog:tricks=[]# mistaken use of a class variabledef__init__(self,name):self.name=namedefadd_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']
Ο σωστός σχεδιασμός της κλάσης θα πρέπει να χρησιμοποιεί μια μεταβλητή στιγμιότυπου αντί:
classDog:def__init__(self,name):self.name=nameself.tricks=[]# creates a new empty list for each dogdefadd_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.Τυχαίες Παρατηρήσεις¶
Αν το ίδιο όνομα χαρακτηριστικού εμφανίζεται και σε ένα στιγμιότυπο και σε μια κλάση, τότε η αναζήτηση χαρακτηριστικών δίνει προτεραιότητα στο στιγμιότυπο:
>>>classWarehouse:...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 classdeff1(self,x,y):returnmin(x,x+y)classC:f=f1defg(self):return'hello world'h=g
Τώρα ταf
,g
καιh
είναι όλα χαρακτηριστικά της κλάσηςC
που αναφέρονται σε αντικείμενα συνάρτησης, και κατά συνέπεια είναι όλες μέθοδοι στιγμιοτύπων τουC
— Τοh
είναι ακριβώς ισοδύναμο με τοg
. Σημειώστε ότι αυτή η πρακτική συνήθως χρησιμεύει μόνο για να μπερδέψει τον αναγνώστη ενός προγράμματος.
Οι μέθοδοι μπορούν να καλούν άλλες μεθόδους χρησιμοποιώντας χαρακτηριστικά μεθόδου του argumentself
:
classBag:def__init__(self):self.data=[]defadd(self,x):self.data.append(x)defaddtwice(self,x):self.add(x)self.add(x)
Οι μέθοδοι μπορεί να αναφέρονται σε καθολικά ονόματα με τον ίδιο τρόπο όπως οι συνηθισμένες συναρτήσεις.Η καθολική εμβέλεια που σχετίζεται με μια μέθοδο είναι το module που περιέχει τον ορισμό της. (Μια κλάση δεν χρησιμοποιείται ποτέ ως καθολική εμβέλεια.) Αν και σπάνια συναντά κανείς έναν καλό λόγο για τη χρήση καθολικών δεδομένων σε μια μέθοδο, υπάρχουν πολλές Νόμιμες χρήσεις της καθολικής εμβέλειας: για ένα πράγμα, οι λειτουργίες και οι λειτουργικές μονάδες που εισάγονται στην καθολική εμβέλεια μπορούν να χρησιμοποιηθούν από μεθόδους, καθώς και συναρτήσεις και κλάσεις που ορίζονται σε αυτό. Συνήθως, η κλάση που περιέχει τη μέθοδο ορίζεται από μόνη της σε αυτή την καθολική εμβέλεια, και στην επόμενη ενότητα θα βρούμε μερικούς καλούς λόγους για τους οποίους μια μέθοδος θα ήθελε να αναφέρει τη δική της κλάση.
Κάθε τιμή είναι ένα αντικείμενο και επομένως έχει μιακλάση (ονομάζεται επίσηςτύπος της). Αποθηκεύεται ωςobject.__class__
.
9.5.Κληρονομικότητα¶
Φυσικά, ένα χαρακτηριστικό γλώσσας δεν θα ήταν αντάξιο του ονόματος «class» χωρίς την υποστήριξη της κληρονομικότητας. Η σύνταξη για έναν παραγόμενο ορισμό κλάσης μοιάζει με αυτό:
classDerivedClassName(BaseClassName):<statement-1>...<statement-N>
Το όνομαBaseClassName
πρέπει να οριστεί σε έναν χώρο ονομάτων προσβάσιμο από το πεδίο που περιέχει τον παραγόμενο ορισμό κλάσης. Στη θέση ενός ονόματος βασικής κλάσης, επιτρέπονται και άλλες αυθαίρετες εκφράσεις. Αυτό μπορεί να είναι χρήσιμο, για παράδειγμα, όταν η βασική κλάση ορίζεται σε άλλη module:
classDerivedClassName(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 υποστηρίζει επίσης μια μορφή πολλαπλής κληρονομικότητας. Ένας ορισμός κλάσης με πολλαπλές βασικές κλάσεις μοιάζει με αυτό:
classDerivedClassName(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 γίνεται χωρίς να λαμβάνεται υπόψη η συντακτική θέση του του αναγνωριστικού, αρκεί να εμφανίζεται εντός του ορισμού μιας κλάσης.
Δείτε επίσης
Οιπροδιαγραφές παραποίησης ιδιωτικού ονόματος για λεπτομέρειες και ειδικές περιπτώσεις.
Η παραβίαση ονομάτων είναι χρήσιμη για να επιτρέπεται στις υποκλάσεις να παρακάμπτουν μεθόδους χωρίς να διακόπτουν τις κλήσεις μεθόδων ενδοκλάσεων. Για παράδειγμα:
classMapping:def__init__(self,iterable):self.items_list=[]self.__update(iterable)defupdate(self,iterable):foriteminiterable:self.items_list.append(item)__update=update# private copy of original update() methodclassMappingSubclass(Mapping):defupdate(self,keys,values):# provides new signature for update()# but does not break __init__()foriteminzip(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
για αυτόν τον σκοπό:
fromdataclassesimportdataclass@dataclassclassEmployee:name:strdept:strsalary:int
>>>john=Employee('john','computer lab',1000)>>>john.dept'computer lab'>>>john.salary1000
Ένα κομμάτι κώδικα Python που αναμένει έναν συγκεκριμένο αφηρημένο τύπο δεδομένων μπορεί συχνά να περάσει σε μια κλάση που μιμείται τις μεθόδους αυτού του τύπου δεδομένων. Για παράδειγμα, εάν έχετε μια συνάρτηση που μορφοποιεί ορισμένα δεδομένα από ένα αντικείμενο αρχείου, μπορείτε να ορίσετε μια κλάση με μεθόδουςread()
καιreadline()
που λαμβάνουν τα δεδομένα από ένα buffer συμβολοσειρών αντ” αυτού, και περάστε το ως όρισμα.
Instance method objects έχουν επίσης χαρακτηριστικά:m.__self__
είναι το αντικείμενο παρουσίας με τη μέθοδοm()
, καιm.__func__
είναι τοαντικείμενο συνάρτησης που αντιστοιχεί στη μέθοδο.
9.8.Επαναλήπτες¶
Μέχρι τώρα πιθανότατα έχετε παρατηρήσει ότι τα περισσότερα αντικείμενα container μπορούν να επαναληφθούν χρησιμοποιώντας μια δήλωσηfor
:
forelementin[1,2,3]:print(element)forelementin(1,2,3):print(element)forkeyin{'one':1,'two':2}:print(key)forcharin"123":print(char)forlineinopen("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>", line1, in<module>next(it)StopIteration
Έχοντας δει τους μηχανισμούς πίσω από το πρωτόκολλο iterator, είναι εύκολο να προσθέσετε συμπεριφορά iterator στις κλάσεις σας. Ορίστε μια μέθοδο__iter__()
που επιστρέφει ένα αντικείμενο με μια μέθοδο__next__()
. Εάν η κλάση ορίζει__next__()
, τότε το__iter__()
μπορεί απλώς να επιστρέψειself
:
classReverse:"""Iterator for looping over a sequence backwards."""def__init__(self,data):self.data=dataself.index=len(data)def__iter__(self):returnselfdef__next__(self):ifself.index==0:raiseStopIterationself.index=self.index-1returnself.data[self.index]
>>>rev=Reverse('spam')>>>iter(rev)<__main__.Reverse object at 0x00A1DB50>>>>forcharinrev:...print(char)...maps
9.9.Γεννήτορες (Generators)¶
Generators είναι ένα απλό και ισχυρό εργαλείο για τη δημιουργία iterators. Είναι γραμμένες σαν κανονικές συναρτήσεις αλλά χρησιμοποιούν τηyield
όποτε θέλουν να επιστρέψουν δεδομένα. Κάθε φορά που καλείταιnext()
σε αυτό, ο generator συνεχίζει από εκεί που σταμάτησε (θυμάται όλες τις τιμές δεδομένων και ποια δήλωση εκτελέστηκε τελευταία). Ένα παράδειγμα δείχνει ότι οι generators μπορεί να είναι ασήμαντα εύκολο να δημιουργηθούν:
defreverse(data):forindexinrange(len(data)-1,-1,-1):yielddata[index]
>>>forcharinreverse('golf'):...print(char)...flog
Οτιδήποτε μπορεί να γίνει με generators μπορεί να γίνει και με iterators που βασίζονται σε κλάσεις, όπως περιγράφεται στην προηγούμενη ενότητα. Αυτό που κάνει τους generators τόσο συμπαγείς είναι ότι οι μέθοδοι__iter__()
καιgenerator__next__()
δημιουργούνται αυτόματα.
Ένα άλλο βασικό χαρακτηριστικό είναι ότι οι τοπικές μεταβλητές και η κατάσταση εκτέλεσης αποθηκεύονται αυτόματα μεταξύ των κλήσεων. Αυτό έκανε τη συνάρτηση πιο εύκολη στην γραφή και πολύ πιο ξεκάθαρη από μια προσέγγιση που χρησιμοποιεί μεταβλητές παράδειγμα όπωςself.index
καιself.data
.
Εκτός από την αυτόματη δημιουργία μεθόδου και την αποθήκευση της κατάστασης του προγράμματος, όταν οι generators τερματίζονται, εγείρουν αυτόματα την εξαίρεσηStopIteration
. Σε συνδυασμό, αυτά τα χαρακτηριστικά καθιστούν εύκολη τη δημιουργία επαναλήψεων χωρίς περισσότερη προσπάθεια από τη σύνταξη μιας κανονικής συνάρτησης.
9.10.Εκφράσεις Γεννήτορων¶
Ορισμένοι απλοί generators μπορούν να κωδικοποιηθούν συνοπτικά ως εκφράσεις χρησιμοποιώντας μια σύνταξη παρόμοια με τις list comprehensions, αλλά με παρενθέσεις αντί για αγκύλες. Αυτές οι εκφράσεις έχουν σχεδιαστεί για καταστάσεις όπου ο generator χρησιμοποιείται αμέσως από μια περικλείουσα συνάρτηση. Οι εκφράσεις generator είναι πιο συμπαγείς αλλά λιγότερο ευέλικτες από τους ορισμούς πλήρους generator και τείνουν να είναι περισσότερο φιλικό προς τη μνήμη από αντίστοιχα list comprehensions.
Παραδείγματα:
>>>sum(i*iforiinrange(10))# sum of squares285>>>xvec=[10,20,30]>>>yvec=[7,5,3]>>>sum(x*yforx,yinzip(xvec,yvec))# dot product260>>>unique_words=set(wordforlineinpageforwordinline.split())>>>valedictorian=max((student.gpa,student.name)forstudentingraduates)>>>data='golf'>>>list(data[i]foriinrange(len(data)-1,-1,-1))['f', 'l', 'o', 'g']
Υποσημειώσεις
[1]Εκτός από ένα πράγμα. Τα αντικείμενα του module έχουν ένα μυστικό χαρακτηριστικό μόνο για ανάγνωση που καλείται__dict__
το οποίο επιστρέφει το λεξικό που χρησιμοποιείται για την υλοποίηση του χώρου ονομάτων του module.Το όνομα__dict__
είναι ένα χαρακτηριστικό αλλά όχι καθολικό όνομα. Προφανώς, η χρήση αυτού παραβιάζει την αφαίρεση υλοποίησης χώρου ονομάτων και θα πρέπει να περιορίζεται σε πράγματα όπως τα ύστερα προγράμματα εντοπισμού σφαλμάτων.