11. Σύντομη περιήγηση στην Πρότυπη Βιβλιοθήκη --- Μέρος II
**********************************************************

Αυτή η δεύτερη περιήγηση καλύπτει τα πιο προηγμένα modules που
υποστηρίζουν επαγγελματικές ανάγκες προγραμματισμού.  Αυτά τα modules
σπάνια εμφανίζονται σε μικρά scripts.


11.1. Μορφοποίηση εξόδου
========================

Το module "reprlib" παρέχει μια έκδοση του "repr()" προσαρμοσμένη για
συντομευμένες εμφανίσεις μεγάλων ή βαθιά ένθετων containers:

   >>> import reprlib
   >>> reprlib.repr(set('supercalifragilisticexpialidocious'))
   "{'a', 'c', 'd', 'e', 'f', 'g', ...}"

Το module "pprint" προσφέρει πιο εξελιγμένο έλεγχο της εκτύπωσης τόσο
των ενσωματωμένων (built-in) και των καθορισμένων από τον χρήστη
αντικειμένων με τρόπο που είναι ευανάγνωστο από τον διερμηνέα. Όταν το
αποτέλεσμα είναι μεγαλύτερο από μία γραμμή, ο "pretty printer"
προσθέτει αλλαγές γραμμής και εσοχές για να εμφανιστεί πιο ξεκάθαρα η
δομή δεδομένων:

   >>> import pprint
   >>> t = [[[['black', 'cyan'], 'white', ['green', 'red']], [['magenta',
   ...     'yellow'], 'blue']]]
   ...
   >>> pprint.pprint(t, width=30)
   [[[['black', 'cyan'],
      'white',
      ['green', 'red']],
     [['magenta', 'yellow'],
      'blue']]]

Το module "textwrap" μορφοποιεί τις παραγράφους του κειμένου ώστε να
ταιριάζει σε ένα δεδομένο πλάτος οθόνης:

   >>> import textwrap
   >>> doc = """The wrap() method is just like fill() except that it returns
   ... a list of strings instead of one big string with newlines to separate
   ... the wrapped lines."""
   ...
   >>> print(textwrap.fill(doc, width=40))
   The wrap() method is just like fill()
   except that it returns a list of strings
   instead of one big string with newlines
   to separate the wrapped lines.

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

   >>> import locale
   >>> locale.setlocale(locale.LC_ALL, 'English_United States.1252')
   'English_United States.1252'
   >>> conv = locale.localeconv()          # get a mapping of conventions
   >>> x = 1234567.8
   >>> locale.format("%d", x, grouping=True)
   '1,234,567'
   >>> locale.format_string("%s%.*f", (conv['currency_symbol'],
   ...                      conv['frac_digits'], x), grouping=True)
   '$1,234,567.80'


11.2. Templating
================

Το module "string" περιλαμβάνει μια ευέλικτη κλάση "Template" με
απλοποιημένη σύνταξη κατάλληλη για επεξεργασία από τελικούς χρήστες.
Αυτό επιτρέπει στους χρήστες να προσαρμόζουν τις εφαρμογές τους χωρίς
να χρειάζεται να αλλάξουν την εφαρμογή.

Η μορφή χρησιμοποιεί ονόματα κράτησης θέσης που σχηματίζονται από "$"
με έγκυρα αναγνωριστικά Python (αλφαριθμητικούς χαρακτήρες και κάτω
παύλες).  Περιβάλλοντας το placeholder με αγκύλες επιτρέπει να
ακολουθείται από περισσότερα αλφαριθμητικά γράμματα χωρίς ενδιάμεσα
κενά.  Γράφοντας "$$" δημιουργεί ένα ενιαίο "$":

   >>> from string import Template
   >>> t = Template('${village}folk send $$10 to $cause.')
   >>> t.substitute(village='Nottingham', cause='the ditch fund')
   'Nottinghamfolk send $10 to the ditch fund.'

Η μέθοδος "substitute()" κάνει raise ένα "KeyError" όταν ένα
placeholder δεν παρέχεται σε ένα λεξικό ή ένα όρισμα λέξης-κλειδιού.
Για εφαρμογές στυλ συγχώνευσης mail, τα δεδομένα που παρέχονται από
τον χρήστη ενδέχεται να είναι ελλιπή και η μέθοδος "safe_substitute()"
μπορεί να είναι πιο κατάλληλη --- θα αφήσει αμετάβλητα τα placeholders
εάν λείπουν δεδομένα:

   >>> t = Template('Return the $item to $owner.')
   >>> d = dict(item='unladen swallow')
   >>> t.substitute(d)
   Traceback (most recent call last):
     ...
   KeyError: 'owner'
   >>> t.safe_substitute(d)
   'Return the unladen swallow to $owner.'

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

   >>> import time, os.path
   >>> photofiles = ['img_1074.jpg', 'img_1076.jpg', 'img_1077.jpg']
   >>> class BatchRename(Template):
   ...     delimiter = '%'
   ...
   >>> fmt = input('Enter rename style (%d-date %n-seqnum %f-format):  ')
   Enter rename style (%d-date %n-seqnum %f-format):  Ashley_%n%f

   >>> t = BatchRename(fmt)
   >>> date = time.strftime('%d%b%y')
   >>> for i, filename in enumerate(photofiles):
   ...     base, ext = os.path.splitext(filename)
   ...     newname = t.substitute(d=date, n=i, f=ext)
   ...     print('{0} --> {1}'.format(filename, newname))

   img_1074.jpg --> Ashley_0.jpg
   img_1076.jpg --> Ashley_1.jpg
   img_1077.jpg --> Ashley_2.jpg

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


11.3. Εργασία με δυαδικές διατάξεις εγγραφής δεδομένων
======================================================

Το module "struct" παρέχει τις συναρτήσεις "pack()" και "unpack()" για
εργασία με μορφές δυαδικών (binary) εγγραφών μεταβλητού μήκους.  Το
ακόλουθο παράδειγμα δείχνει πως να κάνετε μια λούπα μέσω των
πληροφοριών κεφαλίδας στο ένα αρχείο ZIP χωρίς τη χρήση του module
"zipfile".  Οι κωδικοί πακέτου ""H"" και ""I"" αντιπροσωπεύουν
αριθμούς χωρίς υπογραφή δύο και τεσσάρων byte αντίστοιχα.  Το ""<""
υποδηλώνει ότι είναι τυπικού μεγέθους και σε σειρά byte λίγο endian:

   import struct

   with open('myfile.zip', 'rb') as f:
       data = f.read()

   start = 0
   for i in range(3):                      # show the first 3 file headers
       start += 14
       fields = struct.unpack('<IIIHH', data[start:start+16])
       crc32, comp_size, uncomp_size, filenamesize, extra_size = fields

       start += 16
       filename = data[start:start+filenamesize]
       start += filenamesize
       extra = data[start:start+extra_size]
       print(filename, hex(crc32), comp_size, uncomp_size)

       start += extra_size + comp_size     # skip to the next header


11.4. Multi-threading
=====================

Το threading είναι μια τεχνική για την αποσύνδεση εργασιών που είναι
διαδοχικά εξαρτώμενες.  Τα νήματα μπορούν να χρησιμοποιηθούν για την
βελτίωση της ανταπόκρισης των εφαρμογών που δέχονται είσοδο από τον
χρήστη ενώ άλλες εργασίες εκτελούνται στο παρασκήνιο.  Μια σχετική
περίπτωση χρήσης εκτελεί I/O παράλληλα με υπολογισμούς στο άλλο νήμα.

Ο ακόλουθος κώδικας δείχνει πως το module υψηλού επιπέδου "threading"
μπορεί να εκτελεί εργασίες στο παρασκήνιο ενώ το κύριο πρόγραμμα
συνεχίζει να εκτελείται:

   import threading, zipfile

   class AsyncZip(threading.Thread):
       def __init__(self, infile, outfile):
           threading.Thread.__init__(self)
           self.infile = infile
           self.outfile = outfile

       def run(self):
           f = zipfile.ZipFile(self.outfile, 'w', zipfile.ZIP_DEFLATED)
           f.write(self.infile)
           f.close()
           print('Finished background zip of:', self.infile)

   background = AsyncZip('mydata.txt', 'myarchive.zip')
   background.start()
   print('The main program continues to run in foreground.')

   background.join()    # Wait for the background task to finish
   print('Main program waited until background was done.')

Η κύρια πρόκληση των εφαρμογών πολλαπλών νημάτων είναι ο συντονισμός
νημάτων που μοιράζονται δεδομένα ή άλλους πόρους.  Για το σκοπό αυτό,
το thread module παρέχει έναν αριθμό πρωτόγονων συγχρονισμού,
συμπεριλαμβανομένων locks, events, μεταβλητών συνθηκών, και
semaphores.

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


11.5. Logging
=============

Το module "logging" προσφέρει ένα πλήρως εξοπλισμένο και ευέλικτο
σύστημα καταγραφής. Στην απλούστερη μορφή του, τα μηνύματα καταγραφής
αποστέλλονται σε ένα αρχείο ή στο "sys.stderr":

   import logging
   logging.debug('Debugging information')
   logging.info('Informational message')
   logging.warning('Warning:config file %s not found', 'server.conf')
   logging.error('Error occurred')
   logging.critical('Critical error -- shutting down')

Αυτό παράγει την ακόλουθη έξοδο:

   WARNING:root:Warning:config file server.conf not found
   ERROR:root:Error occurred
   CRITICAL:root:Critical error -- shutting down

Από default (προεπιλογή), τα ενημερωτικά μηνύματα και τα μηνύματα
εντοπισμού σφαλμάτων αποκρύπτονται και η έξοδος αποστέλλεται σε τυπικό
σφάλμα.  Άλλες επιλογές εξόδου περιλαμβάνουν δρομολόγηση μηνυμάτων
μέσω email, datagrams, υποδοχών (sockets), ή σε HTTP διακομιστή
(server).  Τα νέα φίλτρα μπορούν να επιλέξουν διαφορετική δρομολόγηση
με βάση την προτεραιότητα του μηνύματος: "DEBUG", "INFO", "WARNING",
"ERROR", και "CRITICAL".

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


11.6. Αδύναμες αναφορές
=======================

Η Python κάνει αυτόματη διαχείριση μνήμης (καταμέτρηση αναφορών για τα
περισσότερα αντικείμενα και *garbage collection* για την εξάλειψη των
κύκλων).  Η μνήμη ελευθερώνεται λίγο μετά την κατάργηση της τελευταίας
αναφοράς σε αυτήν.

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

   >>> import weakref, gc
   >>> class A:
   ...     def __init__(self, value):
   ...         self.value = value
   ...     def __repr__(self):
   ...         return str(self.value)
   ...
   >>> a = A(10)                   # create a reference
   >>> d = weakref.WeakValueDictionary()
   >>> d['primary'] = a            # does not create a reference
   >>> d['primary']                # fetch the object if it is still alive
   10
   >>> del a                       # remove the one reference
   >>> gc.collect()                # run garbage collection right away
   0
   >>> d['primary']                # entry was automatically removed
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
       d['primary']                # entry was automatically removed
     File "C:/python311/lib/weakref.py", line 46, in __getitem__
       o = self.data[key]()
   KeyError: 'primary'


11.7. Εργαλεία για εργασία με λίστες
====================================

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

The "array" module provides an "array()" object that is like a list
that stores only homogeneous data and stores it more compactly.  The
following example shows an array of numbers stored as two byte
unsigned binary numbers (typecode ""H"") rather than the usual 16
bytes per entry for regular lists of Python int objects:

   >>> from array import array
   >>> a = array('H', [4000, 10, 700, 22222])
   >>> sum(a)
   26932
   >>> a[1:3]
   array('H', [10, 700])

The "collections" module provides a "deque()" object that is like a
list with faster appends and pops from the left side but slower
lookups in the middle. These objects are well suited for implementing
queues and breadth first tree searches:

   >>> from collections import deque
   >>> d = deque(["task1", "task2", "task3"])
   >>> d.append("task4")
   >>> print("Handling", d.popleft())
   Handling task1

   unsearched = deque([starting_node])
   def breadth_first_search(unsearched):
       node = unsearched.popleft()
       for m in gen_moves(node):
           if is_goal(m):
               return m
           unsearched.append(m)

Εκτός από τις εναλλακτικές υλοποιήσεις λιστών, η βιβλιοθήκη προσφέρει
επίσης και άλλα εργαλεία όπως το module "bisect" με συναρτήσεις για
τον χειρισμό ταξινομημένων λιστών:

   >>> import bisect
   >>> scores = [(100, 'perl'), (200, 'tcl'), (400, 'lua'), (500, 'python')]
   >>> bisect.insort(scores, (300, 'ruby'))
   >>> scores
   [(100, 'perl'), (200, 'tcl'), (300, 'ruby'), (400, 'lua'), (500, 'python')]

Το module "heapq" παρέχει λειτουργίες για την υλοποίηση σωρών (heaps)
που βασίζονται σε κανονικές λίστες.  Η καταχώριση με την χαμηλότερη
τιμή διατηρείται πάντα στη θέση μηδέν.  Αυτό είναι χρήσιμο για
εφαρμογές που έχουν επανειλημμένα πρόσβαση στο μικρότερο στοιχείο αλλά
δεν θέλουν να εκτελέσουν μια πλήρη ταξινόμηση λίστας:

   >>> from heapq import heapify, heappop, heappush
   >>> data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
   >>> heapify(data)                      # rearrange the list into heap order
   >>> heappush(data, -5)                 # add a new entry
   >>> [heappop(data) for i in range(3)]  # fetch the three smallest entries
   [-5, 0, 1]


11.8. Decimal Floating Point Arithmetic
=======================================

The "decimal" module offers a "Decimal" datatype for decimal floating
point arithmetic.  Compared to the built-in "float" implementation of
binary floating point, the class is especially helpful for

* οικονομικές εφαρμογές και άλλες χρήσεις που απαιτούν ακριβή δεκαδική
  αναπαράσταση,

* έλεγχος για την ακρίβεια,

* έλεγχος της στρογγυλοποίησης για την εκπλήρωση νομικών ή
  κανονιστικών απαιτήσεων,

* παρακολούθηση σημαντικών δεκαδικών ψηφίων, ή

* εφαρμογές όπου ο χρήστης αναμένει ότι τα αποτελέσματα ταιριάζουν με
  υπολογισμούς που έγιναν με το χέρι.

Για παράδειγμα, ο υπολογισμός ενός φόρου 5% σε χρέωση τηλεφώνου 70
λεπτών δίνει διαφορετικά αποτελέσματα σε δεκαδική κινητή υποδιαστολή
και σε δυαδική (binary) κινητή υποδιαστολή. Η διαφορά γίνεται
σημαντική εάν τα αποτελέσματα στρογγυλοποιηθούν στο πλησιέστερο λεπτό:

   >>> from decimal import *
   >>> round(Decimal('0.70') * Decimal('1.05'), 2)
   Decimal('0.74')
   >>> round(.70 * 1.05, 2)
   0.73

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

Η ακριβής αναπαράσταση επιτρέπει στην κλάση "Decimal" να εκτελεί
υπολογισμούς modulo και δοκιμές ισότητας που είναι ακατάλληλες για
δυαδική κινητή υποδιαστολή:

   >>> Decimal('1.00') % Decimal('.10')
   Decimal('0.00')
   >>> 1.00 % 0.10
   0.09999999999999995

   >>> sum([Decimal('0.1')]*10) == Decimal('1.0')
   True
   >>> sum([0.1]*10) == 1.0
   False

Το module "decimal" παρέχει αριθμητική με όση ακρίβεια χρειάζεται:

   >>> getcontext().prec = 36
   >>> Decimal(1) / Decimal(7)
   Decimal('0.142857142857142857142857142857142857')
