Αντικειμενοστραφής προγραμματισμός

Διαχείριση μεγαλύτερων προγραμμάτων

Στην αρχή αυτού του βιβλίου, καταλήξαμε σε τέσσερα βασικά μοτίβα προγραμματισμού τα οποία χρησιμοποιούμε για την κατασκευή προγραμμάτων:

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

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

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

Κατά κάποιον τρόπο, ο αντικειμενοστραφής προγραμματισμός είναι ένας τρόπος για να τακτοποιήσετε τον κώδικά σας έτσι ώστε να μπορείτε να εστιάσετε σε 50 γραμμές του κώδικα και να τον κατανοήσετε, ενώ αγνοείτε τις άλλες 999.950 γραμμές, προς το παρόν.

Ξεκινώντας

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

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

Χρήση αντικειμένων

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

stuff = list()
stuff.append('python')
stuff.append('chuck')
stuff.sort()
print (stuff[0])
print (stuff.__getitem__(0))
print (list.__getitem__(stuff,0))

# Code: http://www.py4e.com/code3/party1.py

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

Η πρώτη γραμμή κατασκευάζει ένα αντικείμενο τύπου list (λίστα), η δεύτερη και η τρίτη γραμμή καλούν τη μέθοδο append(), η τέταρτη γραμμή καλεί τη μέθοδο sort() και η πέμπτη γραμμή ανακτά το στοιχείο στη θέση 0.

Η έκτη γραμμή καλεί τη μέθοδο __getitem__() στη λίστα stuff με παράμετρο μηδέν.

print (stuff.__getitem__(0))

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

print (list.__getitem__(stuff,0))

Σε αυτόν τον κώδικα, καλούμε τη μέθοδο __getitem__ στην κλάση list και περνάμε τη λίστα και το στοιχείο, που θέλουμε να ανακτηθεί από τη λίστα, ως παραμέτρους.

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

Μπορούμε να ρίξουμε μια ματιά στις δυνατότητες ενός αντικειμένου κοιτάζοντας την έξοδο της συνάρτησης dir():

    >>> stuff = list()
    >>> dir(stuff)
    ['__add__', '__class__', '__contains__', '__delattr__',
    '__delitem__', '__dir__', '__doc__', '__eq__',
    '__format__', '__ge__', '__getattribute__', '__getitem__',
    '__gt__', '__hash__', '__iadd__', '__imul__', '__init__',
    '__iter__', '__le__', '__len__', '__lt__', '__mul__',
    '__ne__', '__new__', '__reduce__', '__reduce_ex__',
    '__repr__', '__reversed__', '__rmul__', '__setattr__',
    '__setitem__', '__sizeof__', '__str__', '__subclasshook__',
    'append', 'clear', 'copy', 'count', 'extend', 'index',
    'insert', 'pop', 'remove', 'reverse', 'sort']
    >>>

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

Ξεκινώντας με προγράμματα

Ένα πρόγραμμα στην πιο βασική του μορφή παίρνει κάποια είσοδο, κάνει κάποια επεξεργασία και παράγει κάποια έξοδο. Το πρόγραμμα μετατροπής ανελκυστήρα αποτελεί ένα πολύ σύντομο, αλλά πλήρες πρόγραμμα, που δείχνει και τα τρία αυτά βήματα.

usf = input('Enter the US Floor Number: ')
wf = int(usf) - 1
print('Non-US Floor Number is',wf)

# Code: http://www.py4e.com/code3/elev.py

Αν σκεφτούμε λίγο περισσότερο αυτό το πρόγραμμα, υπάρχει ο “έξω κόσμος” και το πρόγραμμα. Οι πτυχές εισόδου και εξόδου είναι εκεί όπου το πρόγραμμα αλληλεπιδρά με τον έξω κόσμο. Μέσα στο πρόγραμμα έχουμε κώδικα και δεδομένα για να ολοκληρώσουμε την εργασία, που έχει σχεδιαστεί για να λύσει το πρόγραμμα.

Ένα Πρόγραμμα
Ένα Πρόγραμμα

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

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

# Για να το εκτελέσετε, κάντε λήψη του αρχείου zip BeautifulSoup
# από  http://www.py4e.com/code3/bs4.zip
# και αποσυμπιέστε το στον ίδιο κατάλογο με αυτό το αρχείο

import urllib.request, urllib.parse, urllib.error
from bs4 import BeautifulSoup
import ssl

# Αγνόηση των σφαλμάτων πιστοποιητικού SSL
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

url = input('Εισάγετε - ')
html = urllib.request.urlopen(url, context=ctx).read()
soup = BeautifulSoup(html, 'html.parser')

# Ανάκτηση όλων των ετικετών αγκύρωσης
tags = soup('a')
for tag in tags:
    print(tag.get('href', None))

# Code: http://www.py4e.com/code3/urllinks.py

Διαβάζουμε τη διεύθυνση URL σε μια συμβολοσειρά και στη συνέχεια τη περνάμε στο “urllib” για να ανακτήσουμε τα δεδομένα από τον ιστό. Η βιβλιοθήκη urllib χρησιμοποιεί τη βιβλιοθήκη socket για να πραγματοποιήσει την σύνδεση δικτύου, για την ανάκτηση των δεδομένων. Παίρνουμε τη συμβολοσειρά που επιστρέφει το urllib και τη δίνουμε στη BeautifulSoup για ανάλυση. Η BeautifulSoup χρησιμοποιεί το αντικείμενο html.parser1 και επιστρέφει ένα αντικείμενο. Καλούμε τη μέθοδο tags() στο επιστρεφόμενο αντικείμενο, που επιστρέφει ένα λεξικό αντικειμένων ετικετών. Με βρόχο διατρέχουμε τις ετικέτες και καλούμε τη μέθοδο get() για κάθε ετικέτα, για να εκτυπώσουμε το χαρακτηριστικό href.

Μπορούμε να σχεδιάσουμε μια εικόνα αυτού του προγράμματος και πώς συνεργάζονται τα αντικείμενα.

Ένα πρόγραμμα ως Δίκτυο Αντικειμένων
Ένα πρόγραμμα ως Δίκτυο Αντικειμένων

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

Υποδιαιρώντας ένα πρόβλημα

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

Παράβλεψη Λεπτομερειών Κατά τη Χρήση Αντικειμένου
Παράβλεψη Λεπτομερειών Κατά τη Χρήση Αντικειμένου

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

Αγνοώντας τις Λεπτομέρειες Κατά την Κατασκευή ενός Αντικειμένου
Αγνοώντας τις Λεπτομέρειες Κατά την Κατασκευή ενός Αντικειμένου

Το πρώτο μας αντικείμενο Python

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

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

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

class PartyAnimal:
   x = 0

   def party(self) :
     self.x = self.x + 1
     print("So far",self.x)

an = PartyAnimal()
an.party()
an.party()
an.party()
PartyAnimal.party(an)

# Code: http://www.py4e.com/code3/party2.py

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

Ακριβώς όπως η δεσμευμένη λέξη def δεν προκαλεί την εκτέλεση του κώδικα συνάρτησης, έτσι και η δεσμευμένη λέξη class δεν δημιουργεί κάποιο αντικείμενο. Αντίθετα, η δεσμευμένη λέξη class ορίζει ένα πρότυπο, που υποδεικνύει ποια δεδομένα και κώδικας θα περιέχονται σε κάθε αντικείμενο τύπου PartyAnimal. Η κλάση είναι σαν κόφτης (κουπάτ) μπισκότων και τα αντικείμενα που δημιουργούνται χρησιμοποιώντας την κλάση είναι τα μπισκότα2. Δεν βάζετε γλάσο στον κόφτη μπισκότων, βάζετε γλάσο στα μπισκότα και μπορείτε να βάλετε διαφορετικό γλάσο σε κάθε μπισκότο.

Μια Κλάση και Δύο Αντικείμενα
Μια Κλάση και Δύο Αντικείμενα

Εάν διατρέξουμε αυτό το δείγμα προγράμματος, εντοπίζουμε την πρώτη εκτελέσιμη γραμμή κώδικα:

an = PartyAnimal()

Εδώ δίνουμε εντολή στην Python να κατασκευάσει (δηλαδή, να δημιουργήσει) ένα αντικείμενο ή στιγμιότυπο της κλάσης PartyAnimal. Μοιάζει με μια κλήση συνάρτησης, προς την ίδια την κλάση. Η Python κατασκευάζει το αντικείμενο με τα σωστά δεδομένα και μεθόδους και επιστρέφει το αντικείμενο το οποίο στη συνέχεια εκχωρείται στη μεταβλητή an. Κατά κάποιο τρόπο αυτό μοιάζει αρκετά με την ακόλουθη γραμμή που χρησιμοποιούσαμε όλο το προηγούμενο διάστημα:

πλήθη = dict()

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

Όταν η κλάση PartyAnimal χρησιμοποιήθηκε για την κατασκευή ενός αντικειμένου, η μεταβλητή an χρησιμοποιήθηκε για να δείξει σε αυτό το αντικείμενο. Χρησιμοποιούμε το an για πρόσβαση στον κώδικα και τα δεδομένα του συγκεκριμένου στιγμιότυπου της κλάσης PartyAnimal.

Κάθε αντικείμενο/στιγμιότυπο Partyanimal περιέχει μέσα του μια μεταβλητή x και μια μέθοδο/συνάρτηση με το όνομα party. Καλούμε τη μέθοδο party σε αυτή τη γραμμή:

an.party()

Όταν καλείται η μέθοδος party, η πρώτη παράμετρος (την οποία ονομάζουμε κατά σύμβαση self) δείχνει τη συγκεκριμένη περίπτωση του αντικειμένου PartyAnimal για την οποία καλείται το party. Στη μέθοδο party, βλέπουμε τη γραμμή:

self.x = self.x + 1

Αυτή η σύνταξη, που χρησιμοποιεί τον τελεστή dot λέει ‘το x μέσα στο self’. Κάθε φορά που καλείται η party(), η εσωτερική τιμή του x αυξάνεται κατά 1 και η τιμή εκτυπώνεται.

Η ακόλουθη γραμμή είναι ένας άλλος τρόπος για να καλέσετε τη μέθοδο party στο αντικείμενο an:

PartyAnimal.party(an)

Σε αυτήν την παραλλαγή, έχουμε πρόσβαση στον κώδικα μέσα από την κλάση και μεταβιβάζουμε ρητά τον δείκτη αντικειμένου an ως πρώτη παράμετρο (δηλαδή, self στη μέθοδο). Μπορείτε να σκεφτείτε το an.party() ως συντομογραφία για την παραπάνω γραμμή.

Όταν το πρόγραμμα εκτελείται, παράγει την ακόλουθη έξοδο:

So far 1
So far 2
So far 3
So far 4

Το αντικείμενο κατασκευάζεται και η μέθοδος party καλείται τέσσερις φορές, αυξάνοντας και εκτυπώνοντας την τιμή του x μέσα στο αντικείμενο an.

Οι κλάσεις ως τύποι

Όπως είδαμε, στην Python όλες οι μεταβλητές έχουν έναν τύπο. Μπορούμε να χρησιμοποιήσουμε την ενσωματωμένη συνάρτηση dir για να εξετάσουμε τις δυνατότητες μιας μεταβλητής. Μπορούμε επίσης να χρησιμοποιήσουμε τις type και dir με τις κλάσεις που δημιουργούμε.

class PartyAnimal:
   x = 0

   def party(self) :
     self.x = self.x + 1
     print("So far",self.x)

an = PartyAnimal()
print ("Type", type(an))
print ("Dir ", dir(an))
print ("Type", type(an.x))
print ("Type", type(an.party))

# Code: http://www.py4e.com/code3/party3.py

Όταν εκτελείται αυτό το πρόγραμμα, παράγει την ακόλουθη έξοδο:

Type <class '__main__.PartyAnimal'>
Dir  ['__class__', '__delattr__', ...
'__sizeof__', '__str__', '__subclasshook__',
'__weakref__', 'party', 'x']
Type <class 'int'>
Type <class 'method'>

Μπορείτε να δείτε ότι χρησιμοποιώντας τη δεσμευμένη λέξη class, δημιουργήσαμε έναν νέο τύπο. Από την έξοδο της dir, μπορείτε να δείτε ότι και το χαρακτηριστικό ακεραίου x και η μέθοδος party είναι διαθέσιμα στο αντικείμενο.

Κύκλος ζωής αντικειμένου

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

Εάν θέλουμε το αντικείμενό μας “προετοιμασμένο” για αυτές τις στιγμές κατασκευής και καταστροφής, προσθέτουμε στο αντικείμενο μας ειδικές μεθόδους:

class PartyAnimal:
   x = 0

   def __init__(self):
     print('I am constructed')

   def party(self) :
     self.x = self.x + 1
     print('So far',self.x)

   def __del__(self):
     print('I am destructed', self.x)

an = PartyAnimal()
an.party()
an.party()
an = 42
print('an contains',an)

# Code: http://www.py4e.com/code3/party4.py

Όταν εκτελείται αυτό το πρόγραμμα, παράγει την ακόλουθη έξοδο:

I am constructed
So far 1
So far 2
I am destructed 2
an contains 42

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

an = 42

Στην πραγματικότητα “πετάει το αντικείμενό μας”, ώστε να μπορέσει να χρησιμοποιήσει ξανά τη μεταβλητή an για να αποθηκεύσει την τιμή 42. Ακριβώς τη στιγμή που το αντικείμενό μας an “καταστρέφεται” καλείται ο κωδικός του καταστροφέα μας (__del__). Δεν μπορούμε να σταματήσουμε την καταστροφή της μεταβλητής μας, αλλά μπορούμε να κάνουμε οποιονδήποτε απαραίτητο καθαρισμό πριν το αντικείμενό μας πάψει να υπάρχει πλέον.

Κατά την ανάπτυξη αντικειμένων, είναι αρκετά συνηθισμένο να προσθέτουμε έναν κατασκευαστή σε ένα αντικείμενο, για να ορίσουμε αρχικές τιμές για το αντικείμενο. Είναι σχετικά σπάνιο να χρειαστείτε καταστροφέα για ένα αντικείμενο.

Πολλαπλά στιγμιότυπα

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

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

class PartyAnimal:
   x = 0
   name = ''
   def __init__(self, nam):
     self.name = nam
     print(self.name,'constructed')

   def party(self) :
     self.x = self.x + 1
     print(self.name,'party count',self.x)

s = PartyAnimal('Sally')
j = PartyAnimal('Jim')

s.party()
j.party()
s.party()

# Code: http://www.py4e.com/code3/party5.py

Ο κατασκευαστής έχει και μια παράμετρο self, που δείχνει στο στιγμιότυπο του αντικειμένου, και πρόσθετες παραμέτρους, που μεταβιβάζονται στον κατασκευαστή καθώς κατασκευάζεται το αντικείμενο:

s = PartyAnimal('Sally')

Εντός του κατασκευαστή, η δεύτερη γραμμή αντιγράφει την παράμετρο (nam), που μεταβιβάζεται στο χαρακτηριστικό name στο στιγμιότυπο του αντικειμένου.

self.name = nam

Η έξοδος του προγράμματος δείχνει ότι καθένα από τα αντικείμενα (s και j) περιέχει τα δικά του ανεξάρτητα αντίγραφα των x και nam:

Sally constructed
Jim constructed
Sally party count 1
Jim party count 1
Sally party count 2

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

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

Για αυτό το παράδειγμα, μετακινούμε την κλάση PartyAnimal στο δικό της αρχείο. Στη συνέχεια, μπορούμε να ‘εισάγουμε - import’ την κλάση PartyAnimal σε ένα νέο αρχείο και να την επεκτείνουμε, ως εξής:

from party import PartyAnimal

class CricketFan(PartyAnimal):
   points = 0
   def six(self):
      self.points = self.points + 6
      self.party()
      print(self.name,"points",self.points)

s = PartyAnimal("Sally")
s.party()
j = CricketFan("Jim")
j.party()
j.six()
print(dir(j))

# Code: http://www.py4e.com/code3/party6.py

Όταν ορίζουμε την κλάση CricketFan, υποδεικνύουμε ότι επεκτείνουμε την κλάση PartyAnimal. Αυτό σημαίνει ότι όλες οι μεταβλητές (x) και οι μέθοδοι (party) της κλάσης PartyAnimal κληρονομούνται από την κλάση CricketFan. Για παράδειγμα, στη μέθοδο six, της κλάσης CricketFan, καλούμε τη μέθοδο party, από την κλάση PartyAnimal.

Καθώς εκτελείται το πρόγραμμα, δημιουργούμε τα s και j, ως ανεξάρτητα στιγμιότυπα των PartyAnimal και CricketFan. Το αντικείμενο j έχει πρόσθετες δυνατότητες πέρα από αυτές του αντικειμένου s.

Sally constructed
Sally party count 1
Jim constructed
Jim party count 1
Jim party count 2
Jim points 6
['__class__', '__delattr__', ... '__weakref__',
'name', 'party', 'points', 'six', 'x']

Στην έξοδο dir, για το αντικείμενο j (στιγμιότυπο της κλάσης CricketFan), βλέπουμε ότι έχει τα χαρακτηριστικά και τις μεθόδους της γονικής κλάσης, καθώς και τα χαρακτηριστικά και τις μεθόδους που προστέθηκαν όταν η κλάση επεκτάθηκε, για να δημιουργηθεί η κλάση CricketFan.

Περίληψη

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

stuff = list()
stuff.append('python')
stuff.append('chuck')
stuff.sort()
print (stuff[0])
print (stuff.__getitem__(0))
print (list.__getitem__(stuff,0))

# Code: http://www.py4e.com/code3/party1.py

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

Η δεύτερη και η τρίτη γραμμή καλούν τη μέθοδο append με μία παράμετρο, για να προσθέσουν ένα νέο στοιχείο στο τέλος της λίστας, ενημερώνοντας τα χαρακτηριστικά μέσα στο stuff. Στη συνέχεια, στην τέταρτη γραμμή, καλούμε τη μέθοδο sort χωρίς παραμέτρους, για να ταξινομήσουμε τα δεδομένα μέσα στο αντικείμενο stuff.

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

Στο τέλος του προγράμματος, το αντικείμενο stuff απορρίπτεται, αλλά όχι πριν καλέσετε τον καταστροφέα (με το όνομα __del__), έτσι ώστε το αντικείμενο να μπορεί να τακτοποιήσει τυχόν εκκρεμή ζητήματα όπως απαιτείται.

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

Γλωσσάριο

attribute - χαρακτηριστικό ή ιδιότητα
Μια μεταβλητή που είναι μέρος μιας κλάσης.
αντικείμενο
Ένα κατασκευασμένο στιγμιότυπο μιας κλάσης. Ένα αντικείμενο περιέχει όλα τα χαρακτηριστικά και τις μεθόδους, που ορίστηκαν από την κλάση. Κάποια αντικειμενοστραφή τεκμηρίωση χρησιμοποιεί τον όρο ‘στιγμιότυπο’ εναλλακτικά του ‘αντικείμενο’.
κατασκευστής
Μια προαιρετική μέθοδος με ειδική ονομασία (__init__), που καλείται τη στιγμή που μια κλάση χρησιμοποιείται για την κατασκευή ενός αντικειμένου. Συνήθως χρησιμοποιείται για τη ρύθμιση αρχικών τιμών του αντικειμένου.
καταστροφέας
Μια προαιρετική μέθοδος με ειδική ονομασία (__del__), που καλείται τη στιγμή ακριβώς πριν από την καταστροφή ενός αντικειμένου. Οι καταστροφείς χρησιμοποιούνται σπάνια.
κλάση
Ένα πρότυπο, που μπορεί να χρησιμοποιηθεί για την κατασκευή ενός αντικειμένου. Καθορίζει τα χαρακτηριστικά και τις μεθόδους που θα αποτελέσουν το αντικείμενο.
κλάση γονέας
Η κλάση που επεκτείνεται για τη δημιουργία μιας νέας θυγατρικής κλάσης. Η γονεϊκή κλάση συνεισφέρει όλες τις μεθόδους και τα χαρακτηριστικά της στη νέα θυγατρική κλάση.
κλάση παιδί
Μια νέα κλάση που δημιουργείται όταν επεκτείνεται μια γονεϊκή κλάση. Η κλάση παιδί κληρονομεί όλα τα χαρακτηριστικά και τις μεθόδους της γονεϊκής κλάσης.
κληρονομικότητα
Όταν δημιουργούμε μια νέα κλάση (παιδί) επεκτείνοντας μια υπάρχουσα κλάση (γονέας). Η θυγατρική κλάση έχει όλα τα χαρακτηριστικά και τις μεθόδους της γονεϊκής κλάσης συν επιπλέον χαρακτηριστικά και μεθόδους που ορίζονται από τη θυγατρική κλάση.
μέθοδος
Μια συνάρτηση που περιέχεται σε μια κλάση και στα αντικείμενα που κατασκευάζονται από αυτή την κλάση. Ορισμένα αντικειμενοστραφή μοτίβα χρησιμοποιούν τον όρο ‘μήνυμα’ αντί για ‘μέθοδο’ για να περιγράψουν αυτήν την έννοια.

  1. https://docs.python.org/3/library/html.parser.html

  2. Cookie image copyright CC-BY https://www.flickr.com/photos/dinnerseries/23570475099

  3. Εάν είστε περίεργοι για το πού ορίζεται η κλάση list, ρίξτε μια ματιά στο (ελπίζουμε ότι η διεύθυνση URL δεν θα αλλάξει) https://github.com/python/cpython/blob/master/Objects/listobject.c - η κλάση λίστας είναι γραμμένη σε μια γλώσσα που ονομάζεται “C”. Αν ρίξετε μια ματιά σε αυτόν τον πηγαίο κώδικα και τον βρείτε περίεργο, ίσως θέλετε να εξερευνήσετε μερικά μαθήματα Επιστήμης Υπολογιστών.


Αν εντοπίσετε κάποιο λάθος σε αυτό το βιβλίο μην διστάσετε να μου στείλετε τη διόρθωση στο Github.