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 may be read-only or writable. In the latter case, assignment to attributes is possible. Module attributes are writable: you can write modname.the_answer = 42. Writable attributes may also be deleted with the del statement. For example, del modname.the_answer will remove the attribute the_answer from the object named by modname.

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

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

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

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

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

  • the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contains non-local, but also non-global names

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

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

If a name is declared global, then all references and assignments go directly to the middle scope containing the module’s global names. To rebind variables found outside of the innermost scope, the nonlocal statement can be used; if not declared nonlocal, those variables are read-only (an attempt to write to such a variable will simply create a new local variable in the innermost scope, leaving the identically named outer variable unchanged).

Συνήθως, η τοπική εμβέλεια παραπέμπει στα τοπικά ονόματα της (κείμενης) τρέχουσας συνάρτησης. Εκτός συναρτήσεων, η τοπική εμβέλεια αναφέρεται στον ίδιο χώρο ονομάτων με την καθολική εμβέλεια: τον χώρο ονομάτων του 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 ή μέσα σε μια συνάρτηση.)

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

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

When a class definition is left normally (via the end), a class object is created. This is basically a wrapper around the contents of the namespace created by the class definition; we’ll learn more about class objects in the next section. The original local scope (the one in effect just before the class definition was entered) is reinstated, and the class object is bound here to the class name given in the class definition header (ClassName in the example).

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

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

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

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

    def f(self):
        return 'hello world'

then MyClass.i and MyClass.f are valid attribute references, returning an integer and a function object, respectively. Class attributes can also be assigned to, so you can change the value of MyClass.i by assignment. __doc__ is also a valid attribute, returning the docstring belonging to the class: "A simple example class".

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

x = MyClass()

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

The instantiation operation («calling» a class object) creates an empty object. Many classes like to create objects with instances customized to a specific initial state. Therefore a class may define a special method named __init__(), like this:

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

When a class defines an __init__() method, class instantiation automatically invokes __init__() for the newly-created class instance. So in this example, a new, initialized instance can be obtained by:

x = MyClass()

Of course, the __init__() method may have arguments for greater flexibility. In that case, arguments given to the class instantiation operator are passed on to __init__(). For example,

>>> 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 και μέθοδοι δεδομένων.

Data attributes correspond to «instance variables» in Smalltalk, and to «data members» in C++. Data attributes need not be declared; like local variables, they spring into existence when they are first assigned to. For example, if x is the instance of MyClass created above, the following piece of code will print the value 16, without leaving a trace:

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

The other kind of instance attribute reference is a method. A method is a function that «belongs to» an object. (In Python, the term method is not unique to class instances: other object types can have methods as well. For example, list objects have methods called append, insert, remove, sort, and so on. However, in the following discussion, we’ll use the term method exclusively to mean methods of class instance objects, unless explicitly stated otherwise.)

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

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

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

x.f()

In the MyClass example, this will return the string 'hello world'. However, it is not necessary to call a method right away: x.f is a method object, and can be stored away and called at a later time. For example:

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

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

What exactly happens when a method is called? You may have noticed that x.f() was called without an argument above, even though the function definition for f() specified an argument. What happened to the argument? Surely Python raises an exception when a function that requires an argument is called without any — even if the argument isn’t actually used…

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

If you still don’t understand how methods work, a look at the implementation can perhaps clarify matters. When a non-data attribute of an instance is referenced, the instance’s class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.

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

Now f, g and h are all attributes of class C that refer to function objects, and consequently they are all methods of instances of Ch being exactly equivalent to g. Note that this practice usually only serves to confuse the reader of a program.

Οι μέθοδοι μπορούν να καλούν άλλες μεθόδους χρησιμοποιώντας χαρακτηριστικά μεθόδου του 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>

The name BaseClassName must be defined in a scope containing the derived class definition. In place of a base class name, other arbitrary expressions are also allowed. This can be useful, for example, when the base class is defined in another 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>

For most purposes, in the simplest cases, you can think of the search for attributes inherited from a parent class as depth-first, left-to-right, not searching twice in the same class where there is an overlap in the hierarchy. Thus, if an attribute is not found in DerivedClassName, it is searched for in Base1, then (recursively) in the base classes of Base1, and if it was not found there, it was searched for in Base2, and so on.

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

Dynamic ordering is necessary because all cases of multiple inheritance exhibit one or more diamond relationships (where at least one of the parent classes can be accessed through multiple paths from the bottommost class). For example, all classes inherit from object, so any case of multiple inheritance provides more than one path to reach object. To keep the base classes from being accessed more than once, the dynamic algorithm linearizes the search order in a way that preserves the left-to-right ordering specified in each class, that calls each parent only once, and that is monotonic (meaning that a class can be subclassed without affecting the precedence order of its parents). Taken together, these properties make it possible to design reliable and extensible classes with multiple inheritance. For more detail, see https://www.python.org/download/releases/2.3/mro/.

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. Μικροπράγματα

Sometimes it is useful to have a data type similar to the Pascal «record» or C «struct», bundling together a few named data items. An empty class definition will do nicely:

class Employee:
    pass

john = Employee()  # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

A piece of Python code that expects a particular abstract data type can often be passed a class that emulates the methods of that data type instead. For instance, if you have a function that formats some data from a file object, you can define a class with methods read() and readline() that get the data from a string buffer instead, and pass it as an argument.

Instance method objects have attributes, too: m.__self__ is the instance object with the method m(), and m.__func__ is the function object corresponding to the method.

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

Having seen the mechanics behind the iterator protocol, it is easy to add iterator behavior to your classes. Define an __iter__() method which returns an object with a __next__() method. If the class defines __next__(), then __iter__() can just return 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

Anything that can be done with generators can also be done with class-based iterators as described in the previous section. What makes generators so compact is that the __iter__() and __next__() methods are created automatically.

Ένα άλλο βασικό χαρακτηριστικό είναι ότι οι τοπικές μεταβλητές και η κατάσταση εκτέλεσης αποθηκεύονται αυτόματα μεταξύ των κλήσεων. Αυτό έκανε τη συνάρτηση πιο εύκολη στην γραφή και πολύ πιο ξεκάθαρη από μια προσέγγιση που χρησιμοποιεί μεταβλητές παράδειγμα όπως 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']

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

1

Except for one thing. Module objects have a secret read-only attribute called __dict__ which returns the dictionary used to implement the module’s namespace; the name __dict__ is an attribute but not a global name. Obviously, using this violates the abstraction of namespace implementation, and should be restricted to things like post-mortem debuggers.