Διάλεξη 24ης Οκτωβρίου 2016
Συναρτήσεις (functions)
Οι συναρτήσεις είναι ο σημαντικότερος μηχανισμός μιας γλώσσας προγραμματισμού ο οποίος
επιτρέπει την επαναχρησιμοποίηση κώδικα. Αν γράψουμε μια συνάρτηση τότε μπορούμε να την
χρησιμοποιήσουμε, να την "καλέσουμε" όπως λέμε, ξανά και ξανά, χωρις να χρειάζεται να
ξαναγράψουμε τις εντολές που υλοποιούν το σκοπό για τον οποίο γράφτηκε. Έχουμε ήδη
χρησιμοποιήσει συναρτήσεις: αν s είναι μια ακολουθία χαρακτήρων τότε len(s)
είναι το μήκος της ακολουθίας χαρακτήρων s. Λέμε ότι η συνάρτηση len
επιστρέφει (returns) το μήκος του ορίσματος (argument) s, το οποίο γράφεται
σε παρενθέσεις μετά το όνομα της συνάρτησης.
Έχουμε δει τη χρήση πολλών άλλων συναρτήσεων: η συνάρτηση abs
επιστρέφει την
απόλυτη τιμή του ορίσματός της και οι συναρτήσεις max
και min
επιστρέφουν
το μέγιστο, αντίστοιχα, ελάχιστο από τα ορίσματά τους:
>>> x = -3.21
>>> print('The absolute value of', x, 'is', abs(x))
The absolute value of -3.21 is 3.21
>>> y = 12.7
>>> z = 1.32
>>> print('The max of', x, y, z, 'is', max(x,y,z))
The max of -3.21 12.7 1.32 is 12.7
>>> print('The min of', x, y, z, 'is', min(x,y,z))
The min of -3.21 12.7 1.32 is -3.21
Οι συναρτήσεις της μαθηματικής βιβλιοθήκες math
είναι μερικά ακόμα παραδείγματα συναρτήσεων
τις οποίες παρέχει η υλοποίηση της Python στον υπολογιστή που χρησιμοποιούμε. Αυτές είναι οι λεγόμενες
ενσωματωμένες συναρτήσεις (built-in functions). Όλες οι γλώσσες προγραμματισμού παρέχουν μηχανισμούς
για τη συγγραφή νέων συναρτήσεων και τη χρήση τους σαν να ήταν ενσωματωμένες συναρτήσεις. Στην Python, το συντακτικό
του ορισμού μιας συνάρτησης είναι:
def name-of-function (list of formal parameters):
body of function
Η λέξη def
είναι δεσμευμένη λέξη η οποία ανακοινώνει τον ορισμό της συνάρτησης με το όνομα
name-of-function. Το όνομα μιας συνάρτησης ακολουθεί τους συνηθισμένους κανόνες της Python:
τα ονόματα συναρτήσεων μπορούν να περιέχουν πεζά και κεφαλαία γράμματα, ψηφία (εκτός του πρώτου χαρακτήρα)
και τον ειδικό χαρακτήρα '_'
. Έτσι, my_function
θα μπορούσε να είναι το όνομα μιας συνάρτησης,
διαφορετικής όμως της My_Function
. Η λίστα των τυπικών παραμέτρων είναι μια ακολουθία
ονομάτων χωρισμένα με κόμματα. Για παράδειγμα, θα μπορούσαμε να ορίσουμε τη συνάρτηση avg
η οποία
υπολογίζει το μέσο όρο των δύο ορισμάτων της x, y
ως εξής:
def avg(x, y):
return (x+y)/2
Μπορούμε να χρησιμοποιήσυμε αυτή τη συνάρτηση όπως και τις ενσωματωμένες συναρτήσεις:
mv = avg(3.12, 1.42)
print('The average of 3.12 and 1.42 is', mv)
Κατά την κλήση της συνάρτησης τα ορίσματά της αντιστοιχίζονται με τη λίστα των τυπικών ορισμάτων όπως ακριβώς
και κατά τη διάρκεια μιας εντολής ανάθεσης. Το σώμα της συνάρτησης μπορεί να περιέχει οποιαδήποτε εντολή της
Python και διακρίνεται από τις υπόλοιπες εντολές του προγράμματος χρησιμοποιώντας το μηχανισμό της εσοχής (tab)
όπως ακριβώς και στις εντολές ελέγχου και τις ανακυκλώσεις. Η ειδική εντολή return
, η οποία μπορεί
να χρησιμοποιηθεί μόνο μέσα στο σώμα μιας συνάρτησης ορίζει την τιμή την οποία επιστρέφει η συνάρτηση.
Στο παράδειγμα παραπάνω, η συνάρτηση avg
επιστρέφει την τιμή της έκφρασης (x+y)/2
.
Σημειώνουμε ακόμα ότι η εμφάνιση της εντολής return
σε οποιοδήποτε σημείο στο σώμα μιας συνάρτησης
τερματίζει την εκτέλεση των εντολών της συνάρτησης και επιστρέφει τη ροή του προγράμματος στο σημείο αμέσως μετά
την κλήση της. Η χρήση της εντολής return
είναι προαιρετική. Αν αυτή δεν εμφανίζεται ή η εμφάνισή της
δεν ακολουθείται από κάποια έκφραση, τότε η συνάρτηση επιστρέφει την τιμή None
. Θα μπορούσαμε λοιπόν
να γράψουμε
def sayHello():
print('Hello!')
για τον ορισμό μιας συνάρτησης η οποία απλά τυπώνει το μήνυμα Hello! όποτε κληθεί:
>>> sayHello()
Hello!
Παρατηρήστε όμως ότι αν είχαμε γράψει print(sayHello(), 'Maria')
θα βλέπαμε το μήνυμα None Maria
γιατί None
είναι η τιμή η οποία επιστρέφει η συνάρτηση sayHello
. Δείτε ακόμα ότι η συνάρτηση
αυτή δεν έχει τυπικά ορίσματα και γι'αυτό η λίστα των τυπικών ορισμάτων της είναι κενή, αλλά η κλήση της συνάρτηση διατηρεί
την κενή λίστα των ορισμάτων.
Ας γράψουμε τώρα μια συνάρτηση η οποία επιστρέφει σε μια ακολουθία χαρακτήρων την ημερομηνία στη μορφή
day-month-year
ή, εναλλακτικά, στη μορφή month-day-year
, όπως συνηθίζεται σε κάποιες χώρες
του κόσμου. Είναι λογικό να θεωρήσουμε ότι η λίστα των τυπικών ορισμάτων θα περιλαμβάνει τα day, month, year
,
καθώς και ένα τέταρτο όρισμα, ας το πούμε reverse
το οποίο ορίζει τον τρόπο σχηματισμού της ακολουθίας
χαρακτήρων. Αν συνάρτηση κληθεί με τιμή του ορίσματος reverse
ίσο με True
τότε θα σχηματίσει
την ακολουθία χαρακτήρων month-day-year
, διαφορετικά την day-month-year
.
def dateString(day, month, year, reverse):
if reverse:
return str(month) + '-' + str(day) + '-' + str(year)
else:
return str(day) + '-' + str(month) + '-' + str(year)
Κατά την κλήση dateString(12, 10, 2016, True)
το τυπικό όρισμα day
παίρνει την τιμή 12, το
όρισμα month
την τιμή 10, το όρισμα year
την τιμή 2016 και, τέλος, το τυπικό όρισμα reverse
την τιμή True
. Η αντιστοίχιση λοιπόν τυπικών ορισμάτων και γίνεται σύμφωνα με τη θέση τους. Η Python επιτρέπει
και ένα δεύτερο τρόπο αντιστοίχισης τυπικών ορισμάτων και ορισμάτων αναγράφοντας το όνομα του τυπικού ορίσματος, τον τελεστή '='
και την τιμή του τυπικού ορίσματος. Θα μπορούσαμε λοιπόν να γράψουμε dateString(day=12, month=10, year=2016, reverse=True)
αλλά
και να δώσουμε τιμές στα τυπικά ορίσματα με οποιαδήποτε σειρά θέλουμε:
dateString(year=2016, month=10, day=12, reverse=True)
dateString(day=12, year=2016, month=10, reverse=True)
dateString(reverse=True, day=12, year=2016, month=10)
Δείτε όμως ότι η κλήση dateString(12, month=10, 2016, False)
παράγει ένα μήνυμα σφάλματος:
>>> dateString(12, month=10, 2016, False)
SyntaxError: positional argument follows keyword argument
ενώ η κλήση dateString(12, 10, 2016, reverse=False)
επιστρέφει την ημερομηνία χωρίς μήνυμα σφάλματος:
>>> dateString(12, 10, 2016, reverse=False)
'12-10-2016'
Ο τελευταίος τρόπος κλήσης μιας συνάρτησης είναι ιδιαίτερα συνηθισμένος σε συνδιασμό με
τη χρήση των λεγόμενων προκαθορισμένων τιμών ορισμάτων (default parameter values). Στο παράδειγμα
της συνάρτησης dateString
θα μπορούσαμε να υποθέσουμε ότι η απουσία του ορίσματος reverse
είναι ισοδύναμη με την κλήση της συνάρτησης με το όρισμα reverse
να έχει την τιμή False
.
Μπορούμε να υλοποιήσουμε την ιδέα αυτή ως εξής:
def dateString(day, month, year, reverse=False):
if reverse:
return str(month) + '-' + str(day) + '-' + str(year)
else:
return str(day) + '-' + str(month) + '-' + str(year)
Έτσι, όλες οι παρακάτω κλήσεις της dateString
είναι επιτρεπτές:
dateString(12, 10, 2016)
dateString(12, 10, 2016, True)
dateString(12, 10, 2016, reverse=True)
Η πρώτη θα επιστρέψει την ακολουθία χαρακτήρων '12-10-2016' ενώ οι επόμενες δύο την ακολουθία χαρακτήρων '10-12-2016'.
Ας γράψουμε τώρα μια συνάρτηση η οποία δεδομένου του φυσικού αριθμού $n$ υπολογίζει το άθροισμα $1^2 + 2^2 + \cdots + n^2$.
def sumsq(n):
if n <= 0:
return 0
s = 0
for i in range(1, n+1):
s += i*i
return s
# See if it works
print('Sum of the squares of the first four integers =', sumsq(4))
Εύκολα διαπιστώνει κανείς ότι η κλήση sumsq(4)
επιστρέφει τον ακέραιο 30, το σωστό
δηλαδή αποτέλεσμα. Όπως είπαμε και νωρίτερα το σώμα μιας συνάρτησης μπορεί να περιέχει οποιαδήποτε
εντολή της Python, συμπεριλαμβανομένης, φυσικά, και της εντολής ανάθεσης. Εδώ, το σώμα της συνάρτησης
sumsq
κάνει χρήση δύο μεταβλητών, των i και s. Η πρώτη είναι η
μεταβλητή της ανακύκλωσης for
και η δεύτερη περιέχει το τρέχων άθροισμα. Είναι σημαντικό
να καταλάβουμε ότι αυτές οι μεταβλητές είναι "τοπικές" με την έννοια ότι είναι άγνωστες στο υπόλοιπο
μέρος του προγράμματός μας.
Αν, για παράδειγμα, προσπαθούσαμε να τυπώσουμε και την τιμή του μετρητή
της ανακύκλωσης, εκτός του σώματος της συνάρτησης, θα παίρναμε το μήνυμα σφάλματος
NameError: name 'i' is not defined. Είναι ακόμα απαραίτητο να πούμε ότι το ίδιο ακριβώς
συμβαίνει και με τα τυπικά ορίσματα μιας συνάρτησης: συμπεριφέρονται ως τοπικές μεταβλητές, είναι
άγνωστες δηλαδή εκτός του σώματος της συνάρτησης. Το γεγονός αυτό μας επιτρέπει να γράψουμε τον
κώδικα για τη συνάρτηση sumsq
ως εξής:
def sumsq(n):
if n <= 0: return 0
s = 0
while n > 0:
s += n*n
n -= 1
return s
# See if it works
print('Sum of the squares of the first four integers =', sumsq(4))
Επιπλέον, εκτός του σώματος της συνάρτησης sumsq
μπορούμε να χρησιμοποιήσουμε το όνομα n ως όνομα
μεταβλητής και η οποία δεν έχει καμμία απολύτως σχέση με το τυπικό όρισμα της sumsq
με το ίδιο όνομα. Ίσα-ίσα,
αυτό τονίζει τη σχέση με το τοπικό όρισμα της sumsq
.
def sumsq(n):
if n <= 0: return 0
s = 0
while n > 0:
s += n*n
n -= 1
return s
# See if it works
n = 7
print('Sum of the squares of the first', n, 'integers =', sumsq(n))