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" ή ":=<python-
  grammar:assignment_expression". (Αυτές οι εκφράσεις επιτρέπονται σε
  άλλα πεδία που περιέχονται μέσα στο πεδίο σημειογραφίας.)

* Τα ονόματα που ορίζονται σε πεδία σημειογραφίας δεν μπορούν να
  δεσμευτούν εκ νέου με δηλώσεις "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" στην ενότητα The try
statement και της δήλωσης "raise" στην ενότητα The raise statement.


4.4. Runtime Components
=======================


4.4.1. General Computing Model
------------------------------

Python's execution model does not operate in a vacuum.  It runs on a
host machine and through that host's runtime environment, including
its operating system (OS), if there is one.  When a program runs, the
conceptual layers of how it runs on the host look something like this:

      **host machine**
         **process** (global resources)
            **thread** (runs machine code)

Each process represents a program running on the host.  Think of each
process itself as the data part of its program.  Think of the process'
threads as the execution part of the program.  This distinction will
be important to understand the conceptual Python runtime.

The process, as the data part, is the execution context in which the
program runs.  It mostly consists of the set of resources assigned to
the program by the host, including memory, signals, file handles,
sockets, and environment variables.

Processes are isolated and independent from one another.  (The same is
true for hosts.)  The host manages the process' access to its assigned
resources, in addition to coordinating between processes.

Each thread represents the actual execution of the program's machine
code, running relative to the resources assigned to the program's
process.  It's strictly up to the host how and when that execution
takes place.

From the point of view of Python, a program always starts with exactly
one thread.  However, the program may grow to run in multiple
simultaneous threads.  Not all hosts support multiple threads per
process, but most do.  Unlike processes, threads in a process are not
isolated and independent from one another.  Specifically, all threads
in a process share all of the process' resources.

The fundamental point of threads is that each one does *run*
independently, at the same time as the others.  That may be only
conceptually at the same time ("concurrently") or physically ("in
parallel").  Either way, the threads effectively run at a non-
synchronized rate.

Σημείωση:

  That non-synchronized rate means none of the process' memory is
  guaranteed to stay consistent for the code running in any given
  thread.  Thus multi-threaded programs must take care to coordinate
  access to intentionally shared resources.  Likewise, they must take
  care to be absolutely diligent about not accessing any *other*
  resources in multiple threads; otherwise two threads running at the
  same time might accidentally interfere with each other's use of some
  shared data.  All this is true for both Python programs and the
  Python runtime.The cost of this broad, unstructured requirement is
  the tradeoff for the kind of raw concurrency that threads provide.
  The alternative to the required discipline generally means dealing
  with non-deterministic bugs and data corruption.


4.4.2. Python Runtime Model
---------------------------

The same conceptual layers apply to each Python program, with some
extra data layers specific to Python:

      **host machine**
         **process** (global resources)
            Python global runtime (*state*)
               Python interpreter (*state*)
                  **thread** (runs Python bytecode and "C-API")
                     Python thread *state*

At the conceptual level: when a Python program starts, it looks
exactly like that diagram, with one of each.  The runtime may grow to
include multiple interpreters, and each interpreter may grow to
include multiple thread states.

Σημείωση:

  A Python implementation won't necessarily implement the runtime
  layers distinctly or even concretely.  The only exception is places
  where distinct layers are directly specified or exposed to users,
  like through the "threading" module.

Σημείωση:

  The initial interpreter is typically called the "main" interpreter.
  Some Python implementations, like CPython, assign special roles to
  the main interpreter.Likewise, the host thread where the runtime was
  initialized is known as the "main" thread.  It may be different from
  the process' initial thread, though they are often the same.  In
  some cases "main thread" may be even more specific and refer to the
  initial thread state. A Python runtime might assign specific
  responsibilities to the main thread, such as handling signals.

As a whole, the Python runtime consists of the global runtime state,
interpreters, and thread states.  The runtime ensures all that state
stays consistent over its lifetime, particularly when used with
multiple host threads.

The global runtime, at the conceptual level, is just a set of
interpreters.  While those interpreters are otherwise isolated and
independent from one another, they may share some data or other
resources.  The runtime is responsible for managing these global
resources safely.  The actual nature and management of these resources
is implementation-specific.  Ultimately, the external utility of the
global runtime is limited to managing interpreters.

In contrast, an "interpreter" is conceptually what we would normally
think of as the (full-featured) "Python runtime".  When machine code
executing in a host thread interacts with the Python runtime, it calls
into Python in the context of a specific interpreter.

Σημείωση:

  The term "interpreter" here is not the same as the "bytecode
  interpreter", which is what regularly runs in threads, executing
  compiled Python code.In an ideal world, "Python runtime" would refer
  to what we currently call "interpreter".  However, it's been called
  "interpreter" at least since introduced in 1997 (CPython:a027efa5b).

Each interpreter completely encapsulates all of the non-process-
global, non-thread-specific state needed for the Python runtime to
work. Notably, the interpreter's state persists between uses.  It
includes fundamental data like "sys.modules".  The runtime ensures
multiple threads using the same interpreter will safely share it
between them.

A Python implementation may support using multiple interpreters at the
same time in the same process.  They are independent and isolated from
one another.  For example, each interpreter has its own "sys.modules".

For thread-specific runtime state, each interpreter has a set of
thread states, which it manages, in the same way the global runtime
contains a set of interpreters.  It can have thread states for as many
host threads as it needs.  It may even have multiple thread states for
the same host thread, though that isn't as common.

Each thread state, conceptually, has all the thread-specific runtime
data an interpreter needs to operate in one host thread.  The thread
state includes the current raised exception and the thread's Python
call stack.  It may include other thread-specific resources.

Σημείωση:

  The term "Python thread" can sometimes refer to a thread state, but
  normally it means a thread created using the "threading" module.

Each thread state, over its lifetime, is always tied to exactly one
interpreter and exactly one host thread.  It will only ever be used in
that thread and with that interpreter.

Multiple thread states may be tied to the same host thread, whether
for different interpreters or even the same interpreter.  However, for
any given host thread, only one of the thread states tied to it can be
used by the thread at a time.

Thread states are isolated and independent from one another and don't
share any data, except for possibly sharing an interpreter and objects
or other resources belonging to that interpreter.

Once a program is running, new Python threads can be created using the
"threading" module (on platforms and Python implementations that
support threads).  Additional processes can be created using the "os",
"subprocess", and "multiprocessing" modules. Interpreters can be
created and used with the "interpreters" module.  Coroutines (async)
can be run using "asyncio" in each interpreter, typically only in a
single thread (often the main thread).

-[ Υποσημειώσεις ]-

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