# 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. Σημειογραφία πεδία

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

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

- Function annotations.

- Variable annotations.

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

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

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

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

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

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

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

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

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

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

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

Άλλαξε στην έκδοση 3.13: Οι περιοχές σχολίων τύπου χρησιμοποιούνται επίσης για τις προεπιλεγμένες τιμές παραμέτρων τύπου, όπως εισάγεται από το PEP 696.

Άλλαξε στην έκδοση 3.14: Οι περιοχές σχολίων τύπου χρησιμοποιούνται επίσης για τα annotations, όπως εισάγεται από το PEP 649 and PEP 749.

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

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

Παράδειγμα:

    >>> 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` στην ενότητα Η δήλωση try και της δήλωσης `raise` στην ενότητα Η εντολή raise.

## 4.4. Συστατικά χρόνου εκτέλεσης

### 4.4.1. Γενικό μοντέλο υπολογισμού

Το μοντέλο εκτέλεσης της Python δεν λειτουργεί σε κενό. Εκτελείται σε μια μηχανή υποδοχής και μέσω του περιβάλλοντος χρόνου εκτέλεσης αυτής της υποδοχής, συμπεριλαμβανομένου του λειτουργικού της συστήματος (OS), αν υπάρχει. Όταν εκτελείται ένα πρόγραμμα, τα εννοιολογικά επίπεδα του τρόπου με τον οποίο εκτελείται στην υποδοχή μοιάζουν κάπως έτσι:

> μηχανή υποδοχής
> διαδικασία (παγκόσμιοι πόροι)
> νήμα (εκτελεί κώδικα μηχανής)

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

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

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

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

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

Το θεμελιώδες σημείο των νημάτων είναι ότι το καθένα εκτελείται ανεξάρτητα, ταυτόχρονα με τα άλλα. Αυτό μπορεί να είναι μόνο εννοιολογικά ταυτόχρονα («concurrently») ή φυσικά («in parallel»). Σε κάθε περίπτωση, τα νήματα εκτελούνται αποτελεσματικά με μη συγχρονισμένο ρυθμό.

Σημείωση

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

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

### 4.4.2. Μοντέλο χρόνου εκτέλεσης της Python

Τα ίδια εννοιολογικά επίπεδα ισχύουν για κάθε πρόγραμμα Python, με μερικά επιπλέον επίπεδα δεδομένων συγκεκριμένα για την Python:

> μηχανή υποδοχής
> διαδικασία (παγκόσμιοι πόροι)
> Παγκόσμιος χρόνος εκτέλεσης της Python (κατάσταση)
> Διερμηνέας Python (κατάσταση)
> thread (εκτελεί bytecode Python και «C-API»)
> Κατάσταση νήματος Python

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

Σημείωση

Μια υλοποίηση της Python δεν θα υλοποιήσει απαραίτητα τα επίπεδα χρόνου εκτέλεσης με διακριτό ή ακόμη και συγκεκριμένο τρόπο. Η μόνη εξαίρεση είναι τα σημεία όπου τα διακριτά επίπεδα καθορίζονται άμεσα ή εκτίθενται στους χρήστες, όπως μέσω του module `threading`.

Σημείωση

Ο αρχικός διερμηνέας ονομάζεται συνήθως ο «κύριος» διερμηνέας. Ορισμένες υλοποιήσεις της Python, όπως η CPython, αναθέτουν ειδικούς ρόλους στον κύριο διερμηνέα.

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

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

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

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

Σημείωση

Ο όρος «διερμηνέας» εδώ δεν είναι ο ίδιος με τον «διερμηνέα bytecode», που είναι αυτός που εκτελείται τακτικά σε νήματα, εκτελώντας μεταγλωττισμένο κώδικα Python.

Σε έναν ιδανικό κόσμο, ο όρος «χρόνος εκτέλεσης της Python» θα αναφερόταν σε αυτό που τώρα ονομάζουμε «διερμηνέα». Ωστόσο, έχει ονομαστεί «διερμηνέας» τουλάχιστον από την εισαγωγή του το 1997 (CPython:a027efa5b).

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

Μια υλοποίηση της Python μπορεί να υποστηρίζει τη χρήση πολλαπλών διερμηνέων ταυτόχρονα στην ίδια διαδικασία. Είναι ανεξάρτητοι και απομονωμένοι ο ένας από τον άλλον. Για παράδειγμα, κάθε διερμηνέας έχει το δικό του `sys.modules`.

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

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

Σημείωση

Ο όρος «νήμα Python» μπορεί μερικές φορές να αναφέρεται σε μια κατάσταση νήματος, αλλά συνήθως σημαίνει ένα νήμα που δημιουργείται χρησιμοποιώντας το module `threading`.

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

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

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

Μόλις εκτελείται ένα πρόγραμμα, μπορούν να δημιουργηθούν νέα νήματα Python χρησιμοποιώντας το module `threading` (σε πλατφόρμες και υλοποιήσεις Python που υποστηρίζουν νήματα). Μπορούν να δημιουργηθούν επιπλέον διαδικασίες χρησιμοποιώντας τα modules `os`, `subprocess` και `multiprocessing`. Οι διερμηνείς μπορούν να δημιουργηθούν και να χρησιμοποιηθούν με το module `interpreters`. Οι συναρτήσεις-συνεργάτες (async) μπορούν να εκτελεστούν χρησιμοποιώντας το `asyncio` σε κάθε διερμηνέα, συνήθως μόνο σε ένα νήμα (συχνά το κύριο νήμα).

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

[1]

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