Σήμερα θα δούμε ένα τρόπο (class
) με τον οποίο μπορούμε να ομαδοποιήσουμε δεδομένα και μεθόδους (συναρτήσεις) που συνδέονται εννοιολογικά μεταξύ τους ώστε να αναφερόμαστε σε αυτά με ένα κοινό όνομα, πρακτική που διευκολύνει πολύ το προγραμματισμό, ιδιαίτερα όταν μιλάμε για μεγάλα προγράμματα.
Ας ξεκινήσουμε με το παράδειγμα ενός σημείου στις δύο διαστάσεις. Ένα τέτοιο αντικείμενο έχει μια x και μια y συντεταγμένη.
Μπορούμε απλά να ορίσουμε ένα class Point
για να έχουμε μεταβλητές οι οποίες περιέχουν μέσα τους όλη την πληροφορία για την αναπαράσταση και επεξεργασία ενός τέτοιου σημείου.
Αυτό επιτυγχάνεται με τον κώδικα
class Point:
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y
που φαίνεται παρακάτω. Με την εντολή class Point:
δηλώνουμε ότι ορίζουμε μια νέα κλάση δεδομένων με το όνομα Point
. Στο class Point
υπάγεται η συνάρτηση __init__
που φαίνεται αμέσως από κάτω. Το ειδικό όνομα __init__
χρησιμοποιείται για να ορίσουμε τη μέθοδο με την οποία δημιουργείται ένα νέο αντικείμενο της κλάσης (αυτό γίνεται με την εντολή p = Point(3.0, 2)
παρακάτω).
Η πρώτη παράμετρος της __init__
με το ειδικό όνομα self
αναφέρεται στο αντικείμενο (τύπου class Point
) που δημιουργούμε. Την παράμετρο αυτή δεν την περνάμε στη μέθοδο αλλά υπονοείται.
Οι υπόλοιπες παράμετροι (x και y στην περίπτωσή μας) περνιούνται στη μέθοδο και είναι οι συντεταγμένες που θα πάρει το σημείο που δημιουργούμε. Οι συντεταγμένες αυτές αποθηκεύονται στα πεδία (attributes) self.x
και self.y
, τα οποία έχει μέσα της κάθε μεταβλητή που δημιουργείται ως class Point
. Οι προεπιλεγμένες τιμές των x και y είναι 0.0 και αν ο χρήστης δεν τις δώσει (όπως κάνουμε στην εντολή q=Point()
παρακάτω μπαίνουν αυτές οι τιμές στη θέση τους.
Στο παρακάτω πρόγραμμα λοιπόν, μετά τον ορισμό της class Point
δημιουργούμε ένα Point
με συντεταγμένες 3.0, 2 και βάζουμε τη μεταβλητή p
να δείχνει σε αυτό. Τυπώνουμε μετά τα πεδία x και y του p
, αναφερόμενοι σε αυτά με p.x
και p.y
. Τέλος δημιουργούμε και ένα class Point
q
με συνετατγμένες 0,0 και τυπώνουμε και αυτού τις συντεταγμένες του.
class Point:
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y
p = Point(3.0, 2)
print p.x, p.y
q = Point()
print q.x, q.y
Εδώ προσθέτουμε στην class Point
δύο μεθόδους, τις show
και xreflect
.
Με τη μέθοδο show
(που έχει ως μόνη παράμετρο το self
, και άρα καλείται χωρίς παραμέτρους) απλά τυπώνουμε τα στοιχεία (x και y) του σημείου.
Η μέθοδος xreflect
(επίσης καλείται χωρίς παραμέτρους) δημιουργεί (και επιστρέφει) ένα νέο class Point
που είναι συμμετρικό του αρχικού ως προς τον άξονα των y. Η δημιουργία αυτού του σημείου γίνειται με κλήση στη συνάρτηση <<κατασκευαστή>> Point
στην οποία περνάμε τις κατάλληλες παραμέτρους (το ίδιο x αλλά το αντίθετο y).
Με τις εντολές
p = Point(3.0, 2)
p.show()
Δημιουργούμε το σημείο p
και το τυπώνουμε καλώντας τη μέθοδο p.show()
, ενώ με τις εντολές
q = p.xreflect()
q.show()
Δημιουργούμε το νέο class Point
q
καλώντας τη μέθοδο p.xreflect()
. Έπειτα δείχνουμε τα στοιχεία του q
καλώντας τήν q.show()
.
class Point:
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y
def show(self):
print "Point with coordinates:",self.x,self.y
def xreflect(self):
return Point(self.x, -self.y)
p = Point(3.0, 2)
p.show()
q = p.xreflect()
q.show()
class
Είναι δυνατό να ορίσουμε μια νέα class
ως εξειδίκευση μιας ήδη υπάρχουσας class
. Η νέα class
κληρονομεί αυτόματα όλα τα δεδομένα και τις μεθόδους της μητρικής εκτός αν η ίδια η νέα class
ορίσει νέες μεθόδους με το ίδιο όνομα, οπότε χρησιμοποιούνται αυτές.
Στο παρακάτω παράδειγμα ορίζουμε πρώτα την class Point3D
(σημείο στις τρεις διαστάσεις) και ως εξειδίκευση αυτής ορίζουμε μετά την class Point2D
. Το ότι η class Point2D
έχει ως μητρική την class Point3D
φαίνεται στον ορισμό της
class Point2D(Point3D):
Στην class Point3D
ορίζουμε την __init__
να παίρνει τρεις παραμέτρους x, y, z με προεπιλεγμένες τιμές 0, και τις αποθηκεύει στα αντίστοιχα πεδία self.x, self.y, self.z
. Στην class Point2D
δεν ορίζουμε ιδιαίτερη __init__
οπότε χρησιμοποιείται η __init__
της μητρικής. Και στις δύο εντολές
p = Point3D(2, 3, 4)
q = Point2D(1, 2)
που φαίνονται παρακάτω η μέθοδος που καλείται για να κατασκευάσει τα δύο σημεία είναι η __init__
της class Point3D
(επειδή οι προεπιλεγμένες τιμές είναι 0 μπορούμε να καλέσουμε την __init__
με δύο παραμέτρους μόνο που θεωρούνται οι x, y ενώ z λαμβάνει την προεπιλεγμένη τιμή της 0.
Και οι δύο class
ορίζουν τη δικιά τους μέθοδο show
(καλείται χωρίς παραμέτρους) για να τυπώσουν τον εαυτό τους. Έτσι όταν καλούμε p.show()
καλείται η show
που έχει οριστεί μέσα στην class Point3D
ενώ όταν καλούμε q.show()
καλείται η __init__
που έχει οριστεί σην εξειδικευμένη class Point2D
. Έτσι πετυχαίνουμε όταν τυπώνουμε ένα διδιάστατο σημείο να μη τυπώνεται καθόλου η z συντεταγμένη.
class Point3D:
def __init__(self, x=0.0, y=0.0, z=0.0):
self.x = x; self.y = y; self.z = z
def show(self):
print "[{x},{y},{z}]".format(x=self.x, y=self.y,z=self.z)
class Point2D(Point3D):
def show(self):
print "[{x},{y}]".format(x=self.x, y=self.y)
p = Point3D(2, 3, 4)
q = Point2D(1, 2)
p.show()
q.show()
Σε σχέση με το προηγούμενο προσθέτουμε επιπλέον τη μέθοδο myname
σε κάθε μια από τις class Point3D
και class Point2D
(κάνει ότι έκαναν και οι show()
προηγουμένως αλλά επιστρέφει το string χωρίς να το τυπώνει.
Η μέθοδος show
τώρα έχει υλοποιηθεί στην class Point3D
απλά καλώντας τη μέθοδο myname
και έχει αφαιρεθεί τελείως από την class Point2D
. Ο λόγος είναι ότι με το νέο τρόπο που υλοποιούμε τη show
(καλώντας τη myname
, η οποία είναι διαφορετική για τη μητρική και τη θυγατρική class
) είναι ίδιος για τις δύο κλάσεις οπότε αρκεί η θυγατρική class
να χρησιμοποιήσει τη show
της μητρικής.
Τέλος έχουμε προσθέσει και μια μέθοδο distance
στη μητρική class
η οποία υπολογίζει την απόταση από την αρχή των αξόνων (0,0,0).
Στο κυρίς πρόγραμμα που ακολουθεί τους ορισμούς των class
:
p = Point3D(2, 3, 4)
q = Point2D(1, 2)
points = []
points.append(p)
points.append(q)
for x in points:
print "Distance of point {p} is {d}".format(p=x.myname(), d=x.distance())
Ορίζουμε δύο σημεία p, q
(το πρώτο είναι class Point3D
και το δεύτερο class Point2D
) και μια λίστα points
που περιέχει μέσα της όλα τα σημεία, διδιάστατα και τρισδιάστατα.
Το σημαντικό εδώ είναι ότι στο τελευταίο loop, όπου διανύουμε αυτή τη λίστα και τυπώνουμε για κάθε σημείο το όνομά του και την απόστασή του από την αρχή των αξόνων, δε χρειαζόμαστε να γνωρίζουμε το τι είδους σημείο είναι το x
μια και, όποιο και να είναι, η συνάρτηση x.myname()
θα τρέξει με το σωστό τρόπο (η συνάρτηση x.distance()
είναι η ίδια και στις δύο class
).
Παρατηρούμε εδώ ότι η χρήση του κώδικα που παρέχουν τα δύο class
είναι πολύ εύκολη για τον <<τελικό χρήστη>> (αυτόν που γράφει το κυρίως πρόγραμμα που χρησιμοποιεί τα class
). Αυτός ακριβώς είναι ο σκοπός: να μεταφέρουμε τον κόπο στον κατασκευαστή της class
(ή της όποιας συνάρτησης) από τον χρήστη της class
, μια και οι χρήστες είναι εν δυνάμει πάρα πολλοί ενώ ο κατασκευαστής ένας.
import math
class Point3D:
def __init__(self, x=0.0, y=0.0, z=0.0):
self.x = x; self.y = y; self.z = z
def myname(self):
return "[{x},{y},{z}]".format(x=self.x, y=self.y,z=self.z)
def show(self):
print self.myname()
def distance(self):
return math.sqrt(self.x**2+self.y**2+self.z**2)
class Point2D(Point3D):
def myname(self):
return "[{x},{y}]".format(x=self.x, y=self.y)
p = Point3D(2, 3, 4)
q = Point2D(1, 2)
points = []
points.append(p)
points.append(q)
for x in points:
print "Distance of point {p} is {d}".format(p=x.myname(), d=x.distance())
class
για να αναπαραστήσουμε τα άτομα που δουλεύουν σε μια εταιρεία.Φανταστείτε ότι έχουμε μια εταιρεία που έχει ένα αφεντικό, ο οποίος από κάτω έχει κάποιους managers και ο καθένας από τους managers έχει από κάτω κάποιους εργάτες (workers).
Αρχίζουμε δημιουργώντας μια γενική class Person
με σκοπό να την εξειδικεύσουμε σε δύο class
την class Manager
και την class Worker
ώστε κάθε άτομο που δουλεύει στην εταιρεία να είναι σε μια από τις δύο αυτές class
(και το αφεντικό το βάζουμε κι αυτό στην class Manager
).
Στην class Person
βάζουμε όλα τα κοινά χαρακτηριστικά όλων των ατόμων (όνομα, μισθός, τμήμα (dept)). Αυτά δε χρειάζεται να τα ξαναορίσουμε μετά στις εξειδικευμένες class
. Υπάρχει επίσης στον ορισμό της class Person
και μια μεταβλητή count
που παίρνει αρχική τιμή 0 κατά την ώρα ορισμού της class Person
και στην οποία μεταβλητή κρατάμε το συνολικό πλήθος των ατόμων που υπάρχουν. Η μεταβλητή αυτή είναι κομμάτι του ορισμού της class
(και όχι των αντικειμένων αυτής της class
) και άρα αναφερόμαστε σε αυτή ως Person.count
.
Η συνάρτηση __init__
για την class Person
, με παραμέτρους
__init__(self, name="None", salary=0.0, dept="None")
απλά καλεί τη συνάρτηση
dothisfirst(self, name="None", salary=0.0, dept="None")
με ίδιες ακριβώς παραμέτρους (το κάνουμε έτσι για να μπορούμε να καλούμε την dothisfirst
και από τις εξειδικευμένες class
). Πέρα από το να αποθηκεύει τις παραμέτρους της στα εσωτερικά πεδία η μέθοδος dothisfirst
ενημερώνει και το μετρητή count
αυξάνοντάς τον κατά 1.
Στην εξειδικευμένη class Manager
έχουμε δύο επιπλέον πεδία: το workers
που είναι μια λίστα (αρχικά κενή) από όλους τους εργάτες που είναι υπό τον manager και το boss
που είναι το αφεντικό του manager (ή το string "None" αν ο manager για τον οποίο μιλάμε είναι το μεγάλο αφεντικό της εταιρείας).
Η μέθοδος myworkers
της class Manager
επιστρέφει ένα string με τα ονόματα όλων των εργατών υπό τον manager.
Η μέθοδος mydescription
της class Manager
επιστρέφει και αυτή ένα string με την περιγραφή του Manager σε μορφή κειμένου. Το πρώτο κομμάτι της μεθόδου αυτής ελέγχει αν στο πεδίο boss
βρίσκεται άλλος manager ή το string "None" και φτιάχνει ανάλογα τη μεταβλητή nm
που χρησιμοποιείται για το αποτέλεσμα. Η μέθοδος mydescription
χρησιμοποιεί τη μέθοδο myworkers
για να υπολογίσει το string που επιστρέφει.
Σε σχέση με την class Person
η class Worker
έχει ένα επιπλέον πεδίο, το manager
το οποίο επιπλέον περνιέται ως παράμετρος στην ώρα κατασκευής του αντικειμένου (στη μέθοδο __init__
δηλ.). Η τελευταία εντολή της __init__
της class Worker
ενημερώνει το πεδίο workers
του αντίστοιχου manager προσθέτοντας στη λίστα αυτή το όνομα τον εργάτη που μόλις δημιουργήθηκε.
Στο class Worker
υπάρχει επίσης μια υλοποίηση της μεθόδου mydescription
η οποία είναι διαφορετική από αυτή για Manager
.
Στο κυρίως πρόγραμμα με τις γραμμές
b = Manager("Eftichis", 3, "all", "None")
q = Manager("Manolis", 2, "math", b)
w = Worker("Mitsos", 1, "math", q)
ww = Worker("Babis", 1, "math", q)
δημιουργούμε το μεγάλο αφεντικό (Eftichis) το manager (Manolis) και δύο εργάτες (Mitsos και Babis). Έπειτα βάζουμε όλα αυτά τα άτομα στη λίστα all
και τυπώνουμε το συνολικό αριθμό των ατόμων (μεταβλητή Person.count
).
Στο τελευταίο loop
for x in all:
print x.mydescription()
τυπώνουμε για όλα τα άτομα την περιγραφή τους καλώντας τη μέθοδο mydescription
η οποία τυπώνει σε διαφορετική μορφή για workers και σε διαφορετική για managers. Παρατηρείστε και πάλι το πόσο απλό στο να γραφεί είναι αυτό το τελευταίο loop όταν έχει γίνει η δουλειά υποδομής στο γράψιμο των class
. Παρατηρείστε ακόμη ότι το τελευταίο αυτό loop δε θα άλλαζε στο παραμικρό αν στο μέλλον αποφασίζαμε να ορίσουμε μια καινούργια εξειδίκευση είτε του class Person
είτε μιας από τις κλάσεις class Manager
ή class Worker
. Μπορεί για παράδειγμα να αποφασίσουμε ότι θα ορίσουμε μια νέα class Consulatant
για να χειριστούμε μια κατηγορία συνεργατών της επιχείρησης (σύμβουλοι) οι οποίοι όμως δεν εντάσσονται κάτω από τους managers αλλά κατευθείαν κάτω από το μεγάλο αφεντικό. Σε αυτή την περίπτωση θα πρέπει να φροντίσουμε για αυτή τη νέα class
να γράψουμε τις κατάλληλες μεθόδους __init__
και mydescription
τουλάχιστον. Το τελικό loop του προγράμματος δουλεύει όπως είναι.
class Person:
count = 0
def dothisfirst(self, name="None", salary=0.0, dept="None"):
self.name = name
self.salary = salary+0.0
self.dept = dept
Person.count += 1
def __init__(self, name="None", salary=0.0, dept="None"):
self.dothisfirst(name, salary, dept)
class Manager(Person):
def __init__(self, name="None", salary=0.0, dept="None", boss="None"):
self.dothisfirst(name, salary, dept)
self.workers = []
self.boss = boss
def myworkers(self):
s=""
if len(self.workers)==0:
return s
s = self.workers[0].name
for x in self.workers[1:]:
s = s+","+x.name
return s
def mydescription(self):
if isinstance(self.boss, Person):
nm = self.boss.name
else:
nm = self.boss
s = ( "{onoma}, dept={dept}, salary={salary}, boss={boss}, workers={w}".
format(onoma=self.name, dept=self.dept, salary=self.salary, boss=nm, w=self.myworkers()) )
return s
class Worker(Person):
def __init__(self, name="None", salary=0.0, dept="None", manager="None"):
self.dothisfirst(name, salary, dept)
self.manager = manager
manager.workers.append(self)
def mydescription(self):
s = ( "{o}, dept={d}, salary={s}, manager={m}".
format(o=self.name, d=self.dept, s=self.salary, m=self.manager.name) )
return s
b = Manager("Eftichis", 3, "all", "None")
q = Manager("Manolis", 2, "math", b)
w = Worker("Mitsos", 1, "math", q)
ww = Worker("Babis", 1, "math", q)
all = [b, q, w, ww]
print "Number of persons:", Person.count
for x in all:
print x.mydescription()