# 4. Μοντέλο εκτέλεσης

## 4.1. Δομή ενός προγράμματος

Ένα πρόγραμμα Python αποτελείται από μπλοκ κώδικα. Ένα μπλοκ είναι ένα κομμάτι κειμένου προγράμματος Python που εκτελείται ως μια μονάδα. Τα παρακάτω είναι μπλοκ: ένα module, το σώμα μιας συνάρτησης, ο ένας ορισμός κλάσης. Κάθε εντολή που πληκτρολογείται διαδραστικά αποτελεί μπλοκ. Ένα αρχείο δέσμης ενεργειών (ένα αρχείο που δίνεται ως τυπική είσοδος στο διερμηνέα ή καθορίζεται ως όρισμα γραμμής εντολών στον διερμηνέα) είναι ένα μπλοκ κώδικα. Μια script εντολή (μια εντολή που καθορίζεται στο διερμηνέα με την επιλογή `-c`) είναι ένα μπλοκ κώδικα. Μια ενότητα που εκτελείται ως ανωτέρου επιπέδου script (ως module `__main__`) από τη γραμμή εντολών χρησιμοποιώντας ένα όρισμα `-m` όρισμα είναι επίσης ένα μπλοκ κώδικα. Το όρισμα συμβολοσειράς που περνάει στις ενσωματωμένες συναρτήσεις `eval()` και `exec()` είναι ένα μπλοκ κώδικα.

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

## 4.2. Ονομασία και σύνδεση

### 4.2.1. Σύνδεση ονομάτων

Names αναφέρονται σε αντικείμενα. Τα ονόματα εισάγονται μέσω λειτουργιών δέσμευσης ονομάτων.

Οι παρακάτω δομές δεσμεύουν ονόματα:

- τυπικές παράμετροι συναρτήσεων,

- ορισμοί κλάσεων,

- ορισμοί συναρτήσεων

- εκφράσεις ανάθεσης

- targets που είναι αναγνωριστικά αν εμφανίζονται σε μια ανάθεση:

  - επικεφαλίδα βρόχου `for`,

  - μετά το `as` σε μια δήλωση `with`, σε ρήτρα `except`, σε ρήτρα `except*` ή στο as-pattern κατά τη δομική αντιστοίχησης μοτίβων,

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

- δηλώσεις `import`.

- δηλώσεις `type`.

- λίστες παραμέτρων τύπου.

Η δήλωση `import` της μορφής `from ... import *` συνδέει όλα τα ονόματα που ορίζονται στο εισαγόμενο module, εκτός από αυτά που ξεκινούν με μια κάτω παύλα. Αυτή η μορφή μπορεί να χρησιμοποιηθεί μόνο στο επίπεδο του module.

Ένας στόχος που εμφανίζεται σε μια δήλωση `del` θεωρείται επίσης δεσμευμένος για αυτό τον σκοπό (αν και η πραγματική σημασιολογία είναι να αποσυνδέσει το όνομα).

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

Αν ένα όνομα δεσμεύεται σε ένα μπλοκ, είναι μια τοπική μεταβλητή αυτού του μπλοκ, εκτός αν δηλωθεί ως `nonlocal` ή `global`. Αν ένα όνομα δεσμεύεται στο επίπεδο του module, είναι μια καθολική μεταβλητή. (Οι μεταβλητές του μπλοκ του module είναι ταυτόχρονα τοπικές και καθολικές.) Αν μια μεταβλητή χρησιμοποιείται σε ένα μπλοκ κώδικα αλλά δεν ορίζεται εκεί, είναι μια free variable.

Κάθε εμφάνιση ενός ονόματος στο κείμενο του προγράμματος αναφέρεται στη binding αυτού του ονόματος που καθορίζεται από τους παρακάτω κανόνες επίλυσης ονομάτων.

### 4.2.2. Επίλυση ονομάτων

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

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

Όταν ένα όνομα δεν βρίσκεται καθόλου, γίνεται raise μια εξαίρεση `NameError`. Αν το τρέχον πεδίο είναι πεδίο συνάρτησης και το όνομα αναφέρεται σε μια τοπική μεταβλητή που δεν έχει ακόμα δεσμευτεί σε κάποια τιμή στο σημείο που χρησιμοποιείται το όνομα, γίνεται raise μια εξαίρεση `UnboundLocalError`. Η `UnboundLocalError` είναι μια υποκλάση της `NameError`.

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

Αν η δήλωση `global` εμφανιστεί μέσα σε ένα μπλοκ, όλες οι χρήσεις των ονομάτων που καθορίζονται στη δήλωση αναφέρονται στις συνδέσεις αυτών των ονομάτων στον χώρο ονομάτων ανώτατου επιπέδου. Τα ονόματα επιλύονται στον χώρο ανώτατου επιπέδου αναζητώντας πρώτα στον καθολικό χώρο ονομάτων, δηλαδή τον χώρο ονομάτων του module που περιέχει το μπλοκ κώδικα, και στη συνέχεια στο χώρο ονομάτων των builtins, τον χώρο ονομάτων του module `builtins`. Ο καθολικός χώρος ονομάτων αναζητείται πρώτος. Αν τα ονόματα δεν βρεθούν εκεί, γίνεται αναζήτηση του ενσωματωμένου χώρου ονομάτων. Εάν τα ονόματα δεν βρίσκονται επίσης στον ενσωματωμένο χώρο ονομάτων, δημιουργούνται νέες μεταβλητές στον καθολικό χώρο ονομάτων. Η καθολική δήλωση πρέπει να προηγείται όλων των χρήσεων των ονομάτων που αναφέρονται.

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

Η δήλωση `nonlocal` προκαλεί τα αντίστοιχα ονόματα να αναφέρονται σε προηγουμένως δεσμευμένες μεταβλητές στο πλησιέστερο περιβάλλον πεδίου συνάρτησης. Μια εξαίρεση `SyntaxError` εγείρεται κατά το χρόνο μεταγλώττισης αν το συγκεκριμένο δεν υπάρχει σε κανένα περιβάλλον πεδίου συνάρτησης. Οι παράμετροι τύπου δεν μπορούν να δεσμευτούν εκ νέου με τη δήλωση `nonlocal`.

Ο χώρος ονομάτων για ένα module δημιουργείται αυτόματα την πρώτη φορά που το module εισάγεται Το κύριο module για ένα script ονομάζεται πάντα `__main__`.

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

    class A:
        a = 42
        b = list(a + i for i in range(10))

Ωστόσο, το παρακάτω θα επιτύχει:

    class A:
        type Alias = Nested
        class Nested: pass

    print(A.Alias.__value__)  # <type 'A.Nested'>

### 4.2.3. Σημειογραφία πεδία

Οι λίστες παραμέτρων τύπου και οι δηλώσεις `type` εισάγουν πεδία σημειογραφίας, τα οποία συμπεριφέρονται κυρίως όπως τα πεδία συναρτήσεων, αλλά με κάποιες εξαιρέσεις που συζητούνται παρακάτω. Annotations προς το παρόν δεν χρησιμοποιούν πεδία σημειώσεων, αλλά αναμένεται να τα χρησιμοποιήσουν στην Python 3.13, όταν υλοποιηθεί το PEP 649.

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

- Λίστες παραμέτρων τύπου για generic type aliases.

- Λίστες τύπου παραμέτρου για generic functions. Οι σημειογραφίες μιας γενικής συνάρτησης εκτελούνται μέσα στο πεδίο σημειώσεων, αλλά οι προεπιλογές και οι διακοσμητές της όχι.

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

- Τα όρια και οι περιορισμοί για μεταβλητές τύπου (lazily evaluated).

- Η τιμή των ψευδωνύμων τύπου (lazily evaluated).

Τα πεδία σημειογραφίας διαφέρουν από τα πεδία συναρτήσεων με τους εξής τρόπους:

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

- Οι εκφράσεις σε πεδία σημειογραφίας δεν μπορούν να περιέχουν τις εκφράσεις `yield`, `yield from`, `await` ή `:=<python-grammar:assignment_expression`. (Αυτές οι εκφράσεις επιτρέπονται σε άλλα πεδία που περιέχονται μέσα στο πεδίο σημειογραφίας.)

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

- Ενώ τα πεδία σημειογραφίας έχουν ένα εσωτερικό όνομα, αυτό το όνομα δεν αντικατοπτρίζεται στο qualified name των αντικειμένων που ορίζονται μέσα στο πεδίο.Αντίθετα, το `__qualname__` αυτών των αντικειμένων είναι σαν το αντικείμενο να είχε οριστεί στο περιβάλλων πεδίο.

Added in version 3.12: Τα πεδία σημειώσεων εισήχθησαν στην Python 3.12 ως μέρος του PEP 695.

### 4.2.4. Καθυστερημένη εκτίμηση

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

Παράδειγμα:

    >>> type Alias = 1/0
    >>> Alias.__value__
    Traceback (most recent call last):
      ...
    ZeroDivisionError: division by zero
    >>> def func[T: 1/0](): pass
    >>> T = func.__type_params__[0]
    >>> T.__bound__
    Traceback (most recent call last):
      ...
    ZeroDivisionError: division by zero

Εδώ η εξαίρεση εγείρεται μόνο όταν γίνει πρόσβαση στο χαρακτηριστικό `__value__` του ψευδωνύμου τύπου ή στο χαρακτηριστικό `__bound__` της μεταβλητής τύπου.

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

    from typing import Literal

    type SimpleExpr = int | Parenthesized
    type Parenthesized = tuple[Literal["("], Expr, Literal[")"]]
    type Expr = SimpleExpr | tuple[SimpleExpr, Literal["+", "-"], Expr]

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

Added in version 3.12.

### 4.2.5. Ενσωματωμένες συναρτήσεις και περιορισμένη εκτέλεση

Οι χρήστες δεν θα πρέπει να τροποποιούν το `__builtins__`· είναι αυστηρά μια λεπτομέρεια υλοποίησης. Οι χρήστες που θέλουν να παρακάμψουν τιμές στον χώρο ονομάτων των ενσωματωμένων συναρτήσεων θα πρέπει να κάνουν `import` το module `builtins` και να τροποποιούν τα χαρακτηριστικά του κατάλληλα.

Ο χώρος ονομάτων των ενσωματωμένων συναρτήσεων που σχετίζεται με την εκτέλεση ενός μπλοκ κώδικα βρίσκεται στην πραγματικότητα μέσω αναζήτησης του ονόματος `__builtins__` στον καθολικό του χώρο ονομάτων· αυτό θα πρέπει να είναι ένα λεξικό ή ένα module (στη δεύτερη περίπτωση χρησιμοποιείται το λεξικό του module). Από προεπιλογή, όταν βρισκόμαστε στο module `__main__`, το `__builtins__` είναι το ενσωματωμένο module `builtins`· όταν βρισκόμαστε σε οποιοδήποτε άλλο module, το `__builtins__` είναι ένα ψευδώνυμο για το λεξικό του ίδιου του module `builtins`.

### 4.2.6. Αλληλεπίδραση με δυναμικές λειτουργίες

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

    i = 10
    def f():
        print(i)
    i = 42
    f()

Οι συναρτήσεις `eval()` και `exec()` δεν έχουν πρόσβαση στο πλήρες περιβάλλον για την επίλυση ονομάτων. Τα ονόματα μπορεί να επιλύονται στους τοπικούς και καθολικούς χώρους ονομάτων του καλούντος. Οι ελεύθερες μεταβλητές δεν επιλύονται στο πλησιέστερο περιβάλλον πεδίου, αλλά στον καθολικό χώρο ονομάτων. [1] Οι συναρτήσεις `exec()` και `eval()` έχουν προαιρετικά ορίσματα για να παρακάμψουν τους καθολικούς και τοπικούς χώρους ονομάτων. Αν καθοριστεί μόνο ένας χώρος ονομάτων, χρησιμοποιείται και για τους δύο.

## 4.3. Εξαιρέσεις

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

Ο διερμηνέας της Python εγείρει μια εξαίρεση όταν εντοπίσει ένα σφάλμα κατά την εκτέλεση(όπως η διαίρεση με το μηδέν). Ένα πρόγραμμα Python μπορεί επίσης να εγείρει ρητά μια εξαίρεση με τη δήλωση `raise`. Οι διαχειριστές εξαιρέσεων καθορίζονται με τη δήλωση `try` … `except`. Η ρήτρα `finally` μιας τέτοιας δήλωσης μπορεί να χρησιμοποιηθεί για να καθοριστεί κώδικας καθαρισμού, ο οποίος δεν διαχειρίζεται την εξαίρεση αλλά εκτελείται ανεξάρτητα από το αν προηγήθηκε εξαίρεση ή όχι στον προηγούμενο κώδικα.

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

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

Οι εξαιρέσεις αναγνωρίζονται από στιγμιότυπα κλάσεων. Η ρήτρα `except` επιλέγεται ανάλογα με την κλάση του στιγμιότυπου: πρέπει να αναφέρεται στην κλάση του στιγμιότυπου ή σε μια μη εικονική βασική κλάση αυτής. Το στιγμιότυπο μπορεί να παραληφθεί από τον διαχειριστή και να μεταφέρει πρόσθετες πληροφορίες σχετικά με την εξαιρετική συνθήκη.

Σημείωση

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

Δείτε επίσης την περιγραφή της δήλωσης `try` στην ενότητα The try statement και της δήλωσης `raise` στην ενότητα The raise statement.

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

[1]

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