"__main__" --- Περιβάλλον κώδικα ανωτέρου επιπέδου
**************************************************

======================================================================

Στην Python, το ειδικό όνομα "__main__" χρησιμοποιείται για δύο
σημαντικές κατασκευές:

1. το όνομα του πεδίου ονομάτων ανωτέρου επιπέδου του προγράμματος, το
   οποίο μπορεί να ελεγχθεί χρησιμοποιώντας την έκφραση "__name__ ==
   '__main__'"· και

2. το αρχείο "__main__.py" στα πακέτα της Python.

Και οι δύο αυτοί μηχανισμοί σχετίζονται με τα modules της Python· τον
τρόπο με τον οποίο οι χρήστες αλληλεπιδρούν με αυτά και τον τρόπο με
τον οποίο αλληλεπιδρούν μεταξύ τους.  Εξηγούνται λεπτομερώς παρακάτω.
Εάν είστε νέος/α στα Python modules, δείτε την ενότητα του σεμιναρίου
Modules για μια εισαγωγή.


"__name__ == '__main__'"
========================

Όταν ένα Python module ή πακέτο εισάγεται, το "__name__" ορίζεται στο
όνομα του module.  Συνήθως, αυτό είναι το όνομα του αρχείου Python
χωρίς την επέκταση ".py":

   >>> import configparser
   >>> configparser.__name__
   'configparser'

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

   >>> from concurrent.futures import process
   >>> process.__name__
   'concurrent.futures.process'

Ωστόσο, εάν το module εκτελείται στο περιβάλλον κώδικα ανωτέρου
επιπέδου, το "__name__" ορίζεται στη συμβολοσειρά "'__main__'".


Τι είναι το "πεδίο ονομάτων ανωτέρου επιπέδου";
-----------------------------------------------

Το "__main__" είναι το όνομα του πεδίου ονομάτων όπου εκτελείται ο
κώδικας ανωτέρου επιπέδου. Ο "κώδικας ανωτέρου επιπέδου" είναι η πρώτη
μονάδα Python που καθορίζεται από τον χρήστη και αρχίζει να
εκτελείται. Είναι "ανωτέρου επιπέδου" επειδή εισάγει όλα τα άλλα
modules που χρειάζεται το πρόγραμμα. Μερικές φορές ο "κώδικας ανωτέρου
επιπέδου" ονομάζεται *σημείο εισόδου* στην εφαρμογή.

Το πεδίο ονομάτων ανωτέρου επιπέδου μπορεί να είναι:

* το πεδίο ονομάτων μιας διαδραστικής γραμμής εντολών:

     >>> __name__
     '__main__'

* το Python module που δόθηκε στον διερμηνέα Python ως όρισμα αρχείου:

     $ python helloworld.py
     Hello, world!

* το Python module ή πακέτο που δόθηκε στον διερμηνέα Python με το
  όρισμα "-m":

     $ python -m tarfile
     usage: tarfile.py [-h] [-v] (...)

* Κώδικας Python που διαβάζεται από τον διερμηνέα Python από την
  τυπική είσοδο:

     $ echo "import this" | python
     The Zen of Python, by Tim Peters

     Beautiful is better than ugly.
     Explicit is better than implicit.
     ...

* Κώδικας Python που δόθηκε στον διερμηνέα Python με το όρισμα "-c":

     $ python -c "import this"
     The Zen of Python, by Tim Peters

     Beautiful is better than ugly.
     Explicit is better than implicit.
     ...

Σε κάθε μία από αυτές τις περιπτώσεις, το "__name__" του module
ανωτέρου επιπέδου ορίζεται σε "'__main__'".

Ως αποτέλεσμα, ένα module μπορεί να ανακαλύψει εάν εκτελείται στο
πεδίο ονομάτων ανωτέρου επιπέδου ελέγχοντας το δικό του "__name__", το
οποίο επιτρέπει μια κοινή ιδιωματική έκφραση για την εκτέλεση κώδικα
υπό όρους όταν το module δεν έχει αρχικοποιηθεί από μια δήλωση
εισαγωγής:

   if __name__ == '__main__':
       # Execute when the module is not initialized from an import statement.
       ...

Δείτε επίσης:

  Για μια πιο λεπτομερή ματιά στο πώς ορίζεται το "__name__" σε όλες
  τις περιπτώσεις, δείτε την ενότητα του εγχειριδίου Modules.


Χρήσεις με ιδιωματισμό
----------------------

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

Εδώ είναι που γίνεται χρήσιμο το μπλοκ κώδικα "if __name__ ==
'__main__'". Ο κώδικας μέσα σε αυτό το μπλοκ δεν θα εκτελεστεί εκτός
εάν το module εκτελεστεί στο πεδίο ονομάτων ανωτέρου επιπέδου.

Η τοποθέτηση όσο το δυνατόν λιγότερων δηλώσεων στο μπλοκ κάτω από το
"if __name__ == '__main__'" μπορεί να βελτιώσει την σαφήνεια και την
ορθότητα του κώδικα. Στις περισσότερες περιπτώσεις, μια συνάρτηση με
το όνομα "main" περικλείει την κύρια συμπεριφορά του προγράμματος:

   # echo.py

   import shlex
   import sys

   def echo(phrase: str) -> None:
      """A dummy wrapper around print."""
      # for demonstration purposes, you can imagine that there is some
      # valuable and reusable logic inside this function
      print(phrase)

   def main() -> int:
       """Echo the input arguments to standard output"""
       phrase = shlex.join(sys.argv)
       echo(phrase)
       return 0

   if __name__ == '__main__':
       sys.exit(main())  # next section explains the use of sys.exit

Σημειώστε ότι εάν το module δεν περικλείει τον κώδικα μέσα στη
συνάρτηση "main" αλλά τον τοποθετεί απευθείας μέσα στο μπλοκ "if
__name__ == '__main__'", η μεταβλητή "phrase" θα είναι παγκόσμια για
ολόκληρο το module.  Αυτό είναι ευαίσθητο σε σφάλματα καθώς άλλες
συναρτήσεις μέσα στο module θα μπορούσαν να χρησιμοποιούν κατά λάθος
την καθολική μεταβλητή αντί για ένα τοπικό όνομα.  Μια συνάρτηση
"main" λύνει αυτό το πρόβλημα.

Χρησιμοποιώντας μια συνάρτηση "main" έχει το πρόσθετο όφελος ότι η
συνάρτηση "echo" είναι απομονωμένη και μπορεί να εισαχθεί αλλού. Όταν
το "echo.py" εισάγεται, οι συναρτήσεις "echo" και "main" θα οριστούν,
αλλά καμία από αυτές δεν θα κληθεί, επειδή "__name__ != '__main__'".


Παρατηρήσεις συσκευασίας
------------------------

Οι συναρτήσεις "main" χρησιμοποιούνται συχνά για τη δημιουργία
εργαλείων γραμμής εντολών καθορίζοντάς τες ως σημεία εισόδου για
σενάρια κονσόλας.  Όταν γίνεται αυτό, το pip εισάγει την κλήση της
συνάρτησης σε ένα πρότυπο σενάριο, όπου η τιμή επιστροφής της "main"
περνάει στη "sys.exit()". Για παράδειγμα:

   sys.exit(main())

Εφόσον η κλήση της "main" είναι περιτυλιγμένη στη "sys.exit()", η
προσδοκία είναι ότι η συνάρτησή σας θα επιστρέψει κάποια τιμή αποδεκτή
ως είσοδο στη "sys.exit()"· συνήθως, έναν ακέραιο ή "None" (που
επιστρέφεται αυτοματοποιημένα εάν η συνάρτησή σας δεν έχει δήλωση
επιστροφής).

Ακολουθώντας προληπτικά αυτή τη σύμβαση, το module μας θα έχει την
ίδια συμπεριφορά όταν εκτελείται απευθείας (δηλαδή "python echo.py")
όπως θα έχει εάν αργότερα το συσκευάσουμε ως σημείο εισόδου σε σενάριο
κονσόλας σε ένα πακέτο που μπορεί να εγκατασταθεί με pip.

Ιδιαίτερα, να είστε προσεκτικοί/ές σχετικά με την επιστροφή
συμβολοσειρών από τη δική σας συνάρτηση "main". Το "sys.exit()" θα
ερμηνεύσει ένα όρισμα συμβολοσειράς ως μήνυμα αποτυχίας, οπότε το
πρόγραμμά σας θα έχει κωδικό εξόδου "1", που υποδεικνύει αποτυχία, και
η συμβολοσειρά θα γραφεί στο "sys.stderr".  Το παράδειγμα "echo.py"
από νωρίτερα αποτελεί παράδειγμα χρήσης της σύμβασης
"sys.exit(main())".

Δείτε επίσης:

  Το Python Packaging User Guide περιέχει μια συλλογή εγχειριδίων και
  αναφορών σχετικά με το πώς να διανείμετε και να εγκαθιστάτε πακέτα
  Python με σύγχρονα εργαλεία.


"__main__.py" στα πακέτα της Python
===================================

Εάν δεν είστε εξοικειωμένοι με τα πακέτα της Python, δείτε την ενότητα
Πακέτα του εγχειριδίου.  Συνήθως, το αρχείο "__main__.py"
χρησιμοποιείται για να παρέχει μια διεπαφή γραμμής εντολών για ένα
πακέτο. Σκεφτείτε το ακόλουθο υποθετικό πακέτο, "bandclass":

   bandclass
     ├── __init__.py
     ├── __main__.py
     └── student.py

Το "__main__.py" θα εκτελεστεί όταν το πακέτο αυτό εκκινηθεί απευθείας
από τη γραμμή εντολών χρησιμοποιώντας τη σημαία "-m". Για παράδειγμα:

   $ python -m bandclass

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

   # bandclass/__main__.py

   import sys
   from .student import search_students

   student_name = sys.argv[1] if len(sys.argv) >= 2 else ''
   print(f'Found student: {search_students(student_name)}')

Σημειώστε ότι το "from .student import search_students" είναι ένα
παράδειγμα σχετικής εισαγωγής. Αυτό το στυλ εισαγωγής μπορεί να
χρησιμοποιηθεί όταν αναφέρεστε σε modules εντός ενός πακέτου. Για
περισσότερες λεπτομέρειες, δείτε το Intra-package αναφορές στην
ενότητα Modules του εγχειριδίου.


Χρήσεις με ιδιωματισμό
----------------------

Το περιεχόμενο του "__main__.py" συνήθως δεν περικλείεται με ένα μπλοκ
"if __name__ == '__main__'".  Αντίθετα, αυτά τα αρχεία διατηρούνται
σύντομα και εισάγουν συναρτήσεις για εκτέλεση από άλλα modules.  Αυτά
τα άλλα αρχεία μονάδων μπορούν στη συνέχεια να ελεγχθούν εύκολα με
unit tests και είναι κατάλληλα επαναχρησιμοποιήσιμα.

Εάν χρησιμοποιηθεί, ένα μπλοκ "if __name__ == '__main__'" θα
λειτουργήσει όπως αναμένεται για ένα αρχείο "__main__.py" μέσα σε ένα
πακέτο, επειδή το χαρακτηριστικό του "__name__" θα περιλαμβάνει τη
διαδρομή του πακέτου εάν εισαχθεί:

   >>> import asyncio.__main__
   >>> asyncio.__main__.__name__
   'asyncio.__main__'

Αυτό δεν θα λειτουργήσει όμως για αρχεία "__main__.py" στον ριζικό
κατάλογο ενός αρχείου ".zip".  Επομένως, για συνέπεια, προτιμάται ένα
ελάχιστο "__main__.py" χωρίς έλεγχο "__name__".

Δείτε επίσης:

  Δείτε το "venv" για ένα παράδειγμα πακέτου με ένα ελάχιστο
  "__main__.py" στη βιβλιοθήκη προτύπων. Δεν περιέχει μπλοκ "if
  __name__ == '__main__'". Μπορείτε να το εκκινήσετε με "python -m
  venv [directory]".

  Δείτε το "runpy" για περισσότερες λεπτομέρειες σχετικά με τη σημαία
  "-m" για το εκτελέσιμο του διερμηνέα.

  Δείτε το "zipapp" για το πώς να εκτελέσετε εφαρμογές που
  συσκευάζονται ως αρχεία *.zip*. Σε αυτή την περίπτωση, η Python
  αναζητά ένα αρχείο "__main__.py" στον ριζικό κατάλογο του αρχείου.


"import __main__"
=================

Ανεξάρτητα από το ποιο module ξεκίνησε ένα πρόγραμμα Python, άλλα
modules που εκτελούνται μέσα σε αυτό το ίδιο πρόγραμμα μπορούν να
εισάγουν το πεδίο ονομάτων ανωτέρου επιπέδου (*namespace*) εισάγοντας
το module "__main__".  Αυτό δεν εισάγει ένα αρχείο "__main__.py" αλλά
μάλλον όποιο module έλαβε το ειδικό όνομα "'__main__'".

Εδώ είναι ένα παράδειγμα module που χρησιμοποιεί το πεδίο ονομάτων
"__main__":

   # namely.py

   import __main__

   def did_user_define_their_name():
       return 'my_name' in dir(__main__)

   def print_user_name():
       if not did_user_define_their_name():
           raise ValueError('Define the variable `my_name`!')

       print(__main__.my_name)

Ένα παράδειγμα χρήσης αυτού του module θα μπορούσε να είναι το εξής:

   # start.py

   import sys

   from namely import print_user_name

   # my_name = "Dinsdale"

   def main():
       try:
           print_user_name()
       except ValueError as ve:
           return str(ve)

   if __name__ == "__main__":
       sys.exit(main())

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

   $ python start.py
   Define the variable `my_name`!

Ο κωδικός εξόδου του προγράμματος θα ήταν 1, υποδεικνύοντας ένα
σφάλμα. Αποσχολιάζοντας τη γραμμή "my_name = "Dinsdale"" διορθώνεται
το πρόγραμμα και τώρα τερματίζει με κωδικό κατάστασης 0,
υποδεικνύοντας επιτυχία:

   $ python start.py
   Dinsdale

Σημειώστε ότι η εισαγωγή του "__main__" δεν προκαλεί κανένα πρόβλημα
με την ακούσια εκτέλεση κώδικα ανωτέρου επιπέδου που προορίζεται για
χρήση σε σενάριο και τοποθετείται στο μπλοκ "if __name__ ==
"__main__"" του module "start". Γιατί λειτουργεί αυτό;

Η Python εισάγει ένα κενό module "__main__" στο "sys.modules" κατά την
εκκίνηση του διερμηνέα και το γεμίζει εκτελώντας κώδικα ανωτέρου
επιπέδου. Στο παράδειγμά μας, αυτό είναι το module "start" που
εκτελείται γραμμή-γραμμή και εισάγει το "namely". Με τη σειρά του, το
"namely" εισάγει το "__main__" (που είναι πραγματικά το "start"). Αυτό
είναι ένας κύκλος εισαγωγής! Ευτυχώς, επειδή το μερικώς γεμάτο module
"__main__" είναι παρόν στο "sys.modules", η Python το περνάει στο
"namely". Δείτε Special considerations for __main__ στην αναφορά του
συστήματος εισαγωγής για λεπτομέρειες σχετικά με το πώς λειτουργεί.

Το REPL της Python είναι ένα ακόμη παράδειγμα "πεδίου ονομάτων
ανωτέρου επιπέδου", οπότε οτιδήποτε οριστεί στο REPL γίνεται μέρος του
πεδίου ονομάτων "__main__":

   >>> import namely
   >>> namely.did_user_define_their_name()
   False
   >>> namely.print_user_name()
   Traceback (most recent call last):
   ...
   ValueError: Define the variable `my_name`!
   >>> my_name = 'Jabberwocky'
   >>> namely.did_user_define_their_name()
   True
   >>> namely.print_user_name()
   Jabberwocky

Το πεδίο ονομάτων "__main__" χρησιμοποιείται στην υλοποίηση των "pdb"
και "rlcompleter".
