# 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_string("%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:/python314/lib/weakref.py", line 46, in __getitem__
        o = self.data[key]()
    KeyError: 'primary'

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

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

Το module `array` παρέχει ένα αντικείμενο `array` που μοιάζει με μια λίστα που αποθηκεύει μόνο ομοιογενή δεδομένα και τα αποθηκεύει με πιο συμπαγή τρόπο. Το ακόλουθο παράδειγμα δείχνει έναν πίνακα αριθμών που είναι αποθηκευμένοι ως δύο byte ανυπόγραφων δυαδικών (binary) αριθμών (κωδικός τύπου `"H"`) αντί για τα συνηθισμένα 16 byte ανά καταχώρηση για κανονικές λίστες αντικειμένων τύπου Python int:

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

Το module `collections` παρέχει ένα αντικείμενο `deque` που μοιάζει με λίστα με ταχύτερα προσαρτήματα και αναδύεται από την αριστερή πλευρά αλλά με πιο αργές αναζητήσεις στη μέση. Αυτά τα αντικείμενα είναι κατάλληλα για υλοποίηση ουρών και αναζητήσεων σε πρώτο δέντρο εύρους:

    >>> 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. Δεκαδική Αριθμητικής Κινητής Υποδιαστολής

Το module `decimal` προσφέρουν έναν τύπο δεδομένων `Decimal` για δεκαδική αριθμητική κινητής υποδιαστολής. Σε σύγκριση με την ενσωματωμένη (built-in) εφαρμογή `float` του δυαδικού κινητής υποδιαστολής, η κλάση είναι ιδιαίτερα χρήσιμη για

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

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

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

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

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

Για παράδειγμα, ο υπολογισμός ενός φόρου 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
    >>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 == 1.0
    False

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

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