Home » Εβδομαδιαίες Στήλες » Προγραμματισμός » Μαθήματα Προγραμματισμού Python από το PCsteps: #5

Μαθήματα Προγραμματισμού Python από το PCsteps: #5

Αυτός ο οδηγός γράφτηκε πριν από περισσότερα από 2 χρόνια. Η τεχνολογία αλλάζει και εξελίσσεται. Αν οτιδήποτε δεν σας λειτουργεί, γράψτε μας στα σχόλια και θα κοιτάξουμε να τον ανανεώσουμε.

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

Δείτε τις ενότητες του οδηγού

Προτάσεις συνεργασίας

Διαφημίστε την επιχειρησή σας στο site του PCsteps, ή και στο κανάλι μας στο YouTube.

Επικοινωνία

Γίνε VIP μέλος στο PCSteps

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

Συμμετοχή

Για να δείτε όλα τα μαθήματα Python από την αρχή τους, αλλά και τα επόμενα, αρκεί να κάνετε κλικ εδώ.

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

Τοπική και καθολική εμβέλεια

Όταν καλούμε μια συνάρτηση, της περνάμε παραμέτρους και μεταβλητές, οι οποίες λέμε ότι υπάρχουν στην τοπική εμβέλεια (local scope) της συνάρτησης.

Αντίθετα, μεταβλητές στις οποίες γίνεται εκχώρηση έξω από οποιαδήποτε συνάρτηση, λέμε ότι βρίσκονται στην καθολική εμβέλεια (global scope).

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

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

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

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

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

Οι εμβέλειες είναι σημαντικές για πολλούς λόγους:

  • Ο κώδικας στην καθολική εμβέλεια δεν μπορεί να χρησιμοποιεί σε τοπικές μεταβλητές.
  • Την ίδια στιγμή, η τοπική εμβέλεια έχει πρόσβαση σε καθολικές μεταβλητές.
  • Ο κώδικας στην τοπική εμβέλεια μιας συνάρτησης δεν μπορεί να χρησιμοποιήσει μεταβλητές άλλης τοπικής εμβέλειας.
  • Μπορούμε να αξιοποιήσουμε το ίδιο όνομα για διαφορετικές μεταβλητές, αν αυτές βρίσκονται σε διαφορετικές εμβέλειες. Δηλαδή μπορεί να υπάρχει μια τοπική μεταβλητή “sum” και μια καθολική που λέγεται επίσης “sum”.

Γιατί όμως η Python να έχει διαφορετικές εμβέλειες, αντί να κάνει τα πάντα μια καθολική μεταβλητή?

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

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

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

Τοπικές μεταβλητές στην καθολική εμβέλεια

Ας εξετάσουμε τι θα γίνει αν χρησιμοποιήσουμε μια τοπική μεταβλητή στο global scope, με ένα παράδειγμα προγράμματος. Έστω λοιπόν το παρακάτω:

def fun():
    var = 123
fun()
print(var)

Αν το τρέξουμε, θα έχουμε το εξής αποτέλεσμα:

Το error συμβαίνει επειδή η μεταβλητή “var” υφίσταται μόνο στην τοπική εμβέλεια που δημιουργήθηκε με την κλήση της fun().

Μόλις η εκτέλεση του προγράμματος επιστρέψει από την fun(), αυτή η τοπική εμβέλεια καταστρέφεται και δεν υπάρχει πλέον μεταβλητή με το όνομα “var”.

Έτσι, όταν το πρόγραμμά μας πάει να εκτελέσει την εντολή…

print(var)

…μας δίνει ένα σφάλμα που λέει ότι η “var” δεν είναι ορισμένη.

Τοπικές μεταβλητές σε διαφορετικές τοπικές εμβέλειες

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

def spam():
    var = 99
    fun()
    print(var)

def fun()
    var_2 = 100
    var = 0

spam()

Στην αρχή του προγράμματος καλείται η συνάρτηση spam() και δημιουργείται μία τοπική εμβέλεια. Η τοπική μεταβλητή “var” παίρνει την τιμή 99.

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

Σε αυτήν την νέα τοπική εμβέλεια, η τοπική μεταβλητή “var_2” λαμβάνει την τιμή 100 και δημιουργείται μια τοπική μεταβλητή “var”, με την τιμή 0. Η “var” αυτή είναι διαφορετική από αυτήν που “ζει” στην τοπική εμβέλεια της spam().

Μόλις επιστρέψει η fun(),  η τοπική εμβέλεια για την κλήση της καταστρέφεται. Η εκτέλεση του προγράμματος συνεχίζεται στην spam() με την εκτύπωση της τιμής της “var”. Καθώς η τοπική εμβέλεια υπάρχει ακόμα εδώ, η “var” παίρνει την τιμή 99, και αυτό είναι που θα εκτυπωθεί.

Βλέπουμε λοιπόν ότι οι τοπικές μεταβλητές σε μία συνάρτηση είναι εντελώς ξεχωριστές από τις αντίστοιχες μιας άλλης συνάρτησης.

Καθολικές μεταβλητές στην τοπική εμβέλεια

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

Έστω το ακόλουθο πρόγραμμα:

def spam()
    print(var)
var = 1
spam()
print(var)

Μιας και δεν έχουμε κάποια παράμετρο με το όνομα “var” ή κάποια εντολή που να αναθέτει στην “var” τιμή μέσα στην spam(), η Python θεωρεί ότι αναφερόμαστε στην καθολική “var”. Για αυτό και το αποτέλεσμα και των δύο εκτυπώσεων θα είναι το 1, η τιμή της καθολικής μεταβλητής.

Τοπικές και καθολικές μεταβλητές με ίδιο όνομα

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

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

def spam()
    var = 'spam local'
    print(var)            #prints 'spam local'

def fun():
    var = 'fun local'
    print(var)            #prints 'fun local'
    spam()
    print(var)            #prints 'fun local'

var = 'global'
fun()
print(var)                #prints 'global'

Τρέχοντας το πρόγραμμα, θα έχουμε τις εξής εκτυπώσεις:

Παρατηρούμε ότι υπάρχουν τρεις διαφορετικές μεταβλητές στο πρόγραμμά μας, όλες με το όνομα “var”:

  • Μία στην τοπική εμβέλεια της κλήσης της spam().
  • Μία στην τοπική εμβέλεια της κλήσης της fun().
  • Μία στην καθολική εμβέλεια του προγράμματος.

Από τη στιγμή που έχουν και οι τρεις το ίδιο όνομα, μπορεί να προκληθεί σύγχυση στο να παρακολουθήσουμε ποια από όλες χρησιμοποιείται μια δεδομένη στιγμή.

Δήλωση global

Η Python μας δίνει τη δυνατότητα να τροποποιήσουμε μια μεταβλητή μέσα από μία συνάρτηση, με τη χρήση μιας δήλωσης global.

Γράφοντας μέσα στη συνάρτηση τη δήλωση “global var”, λέμε στην Python ότι η “var” αναφέρεται στην καθολική “var” του προγράμματος, οπότε δεν θα κατασκευάσει τοπική μεταβλητή “var”.

def spam():
    global var
    var = 'spam'

var = 'global'
spam()
print(var)

Η εκτύπωση που θα δούμε εκτελώντας το πρόγραμμα, θα είναι η εξης:

Η “var” έχει κηρυχθεί ως καθολική (global) στην αρχή της συνάρτησης spam(). Επομένως, η ανάθεση της τιμής “spam”, δεν γίνεται σε κάποια τοπική μεταβλητή “var”, αλλά στην καθολικής εμβέλειας “var”.

Ως αποτέλεσμα, η κλήση της spam() μεταβάλει την τιμή της καθολικής “var” και αντί να εκτυπωθεί “global”, όπως ίσως περίμενε κανείς, βλέπουμε το string “spam”.

Πώς ξεχωρίζω τοπικές από καθολικές μεταβλητές?

Υπάρχουν κάποιοι κανόνες που υποδεικνύουν πότε μια μεταβλητή ανήκει στην τοπική ή στην καθολική εμβέλεια σε ένα πρόγραμμα Python:

  1.  Αν μια μεταβλητή χρησιμοποιείται στην καθολική εμβέλεια (που σημαίνει, έξω από οποιαδήποτε συνάρτηση), τότε είναι πάντοτε καθολική.
  2.  Αν υπάρχει δήλωση global για την μεταβλητή αυτή μέσα σε μια συνάρτηση, τότε είναι καθολική.
  3. Σε διαφορετική περίπτωση, αν αναθέτουμε τιμή στην μεταβλητή (assignment) μέσα σε μια συνάρτηση, είναι τοπική.
  4. Αν όμως η μεταβλητή δεν χρησιμοποιείται σε κάποια ανάθεση, είναι καθολική.

Θα σχηματίσουμε μια καλύτερη εικόνα των κανόνων αυτών, μέσα από ένα παράδειγμα. Γράφουμε τον παρακάτω κώδικα:

def spam()
    global var
    var = 'spam'    #global var

def fun()
    var = 'fun'     #local var

def reg()
    print(var)      #global var

var = 5             #global var
spam()
print(var)
reg()

Στη συνάρτηση spam(), η “var” είναι η καθολική “var”, αφού υπάρχει κάποια δήλωση global για την “var” στην αρχή της συνάρτησης.

Στην fun(), η “var” είναι τοπική, αφού υπάρχει για αυτήν δήλωση ανάθεσης μέσα στην συνάρτηση.

Στην reg(), όμως, η “var” είναι και πάλι η καθολική “var”, διότι δεν έχουμε κάποια δήλωση ανάθεσης μέσα στη συνάρτηση.

Διαχείριση λαθών

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

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

Εξαιρέσεις

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

Ανάλογα με τον τύπο της εξαίρεσης, η Python θα μας εκτυπώσει και διαφορετικό όνομα στο μήνυμα του σφάλματος, όπως ZeroDivisionError, NameError καιTypeError. Η διαχείριση των εξαιρέσεων ονομάζεται στον προγραμματισμό exception handling.

Ας δούμε ένα παράδειγμα προγράμματος, στο οποίο υποπίπτουμε σε ένα σφάλμα διαίρεσης με το μηδέν. Γράφουμε στον editor:

def spam(divide):
    return 21 / divide

print(spam(2))
print(spam(10))
print(spam(0))
print(spam(1))

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

Το “ZeroDivisionError” συμβαίνει – όπως λέει και το όνομά του, όταν επιχειρήσουμε να διαιρέσουμε έναν αριθμό με το μηδέν. Από τον αριθμό γραμμής που αναφέρει το μήνυμα του error, γνωρίζουμε ότι το return της συνάρτησης spam() προκαλεί το πρόβλημα.

Δήλωση try και except

Ας δούμε λοιπόν πώς μπορούμε να χειριστούμε ένα τέτοιο error με τις δηλώσεις “try” και “except”.

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

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

def spam(divide):
    try:
        return 21 / divide
    except ZeroDivisionError:
        print('Error: Invalid argument.')

print(spam(2))
print(spam(10))
print(spam(0))
print(spam(1))

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

Καθώς το “except” πιάνει το ZeroDivisionError, η Python εκτυπώνει το μήνυμα που έχουμε ορίσει και επιστρέφει την τιμή “None”.

Σημειώνεται ότι οποιοδήποτε error λάβει χώρα σε ένα block “try” επίσης θα γίνει αντιληπτό. Ας γράψουμε το παρακάτω πρόγραμμα, στο οποίο έχουμε βάλει τις κλήσεις της spam() μέσα στο “try()”:

def spam(divide)
    return 21 / divide

try:
    print(spam(2))
    print(spam(10))
    print(spam(0))
    print(spam(1))
except ZeroDivisionError:
 print('Error: Bad argument.')

Αφού εκτελέσουμε το πρόγραμμα, θα τυπωθεί στον IDLE:

Διαπιστώνουμε εδώ ότι το print(spam(1)) δεν εκτελέστηκε ποτέ. Μόλις η εκτέλεση του προγράμματος πάει στο print(spam(0)), θα ανακατευθυνθεί αμέσως στο “except” και θα συνεχίσει έπειτα κανονικά, τελειώνοντας το πρόγραμμα, αφού δεν υπάρχει άλλη εντολή.

Παράδειγμα προγράμματος

Στο σημερινό μάθημα αντί να γράψουμε μαζί ένα πρόγραμμα, θα πούμε απλά ποια θέλουμε να είναι η λειτουργία του και τι αποτελέσματα πρέπει να περιμένουμε.

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

Το πρόγραμμα που θέλουμε να γράψουμε έχει να κάνει με την εικασία του Collatz και θα μας επιστρέφει μια ακολουθία Collatz.

Τι λέει η εικασία αυτή? Θεωρούμε έναν τυχαίο ακέραιο αριθμό, έστω τον “x”. Αν είναι ζυγός, τον διαιρούμε με δύο: “x/2”. Αν είναι μονός, παίρνουμε: “3*x + 1”.

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

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

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

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

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

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

Θα πρέπει ακόμη να δεχόμαστε ένα input από τον χρήστη, να το φέρουμε στην κατάλληλη μορφή προς χρήση, κι έπειτα να επαναλάβουμε πολλές φορές την εργασία που εκτελεί η συνάρτηση. Κάθε νέο αποτέλεσμα της συνάρτησης θα μας χρειάζεται για τον επόμενο υπολογισμό.

Τέλος, αν θέλουμε, μπορούμε να εφαρμόσουμε τα όσα λίγα μάθαμε περί exception handling, ελέγχοντας το string που εισάγει ο χρήστης, αυτή τη φορά για ValueError, εφόσον δεχόμαστε μόνον αριθμούς.

Στο επόμενο μάθημα για τον προγραμματισμό Python

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

Σας άρεσε το σημερινό μάθημα για τον προγραμματισμό Python?

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

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

Για να δείτε όλα τα μαθήματα Python από την αρχή τους, αλλά και τα επόμενα, αρκεί να κάνετε κλικ εδώ.
Τα σχόλια του PCsteps έχουν μεταφερθεί στο Questions.pcsteps.gr. Αν έχετε απορίες για τη δημοσίευση ή οποιαδήποτε τεχνολογική ερώτηση, από προτάσεις αγορών μέχρι τεχνικά προβλήματα, γράψτε μας εκεί. Απαντάμε το αργότερο εντός 48 ωρών.

Οι Στήλες του PCsteps

FEATURED SALE ALERT 2
Οδηγοί Αγοράς
QuickSteps#223 - Τιμή Βενζίνης Στους Χάρτες Google, Δωρεάν HEVC codec, Αναφορές Instagram
QuickSteps
GamingSteps#202205021
GamingSteps