Λίστες

Μια λίστα είναι μια ακολουθία

Όπως και μια συμβολοσειρά, μια list είναι μια ακολουθία τιμών. Σε μια συμβολοσειρά, οι τιμές είναι χαρακτήρες ενώ σε μια λίστα, μπορούν να είναι οποιουδήποτε τύπου. Οι τιμές στη λίστα ονομάζονται στοιχεία (elements ή μερικές φορές items).

Υπάρχουν διάφοροι τρόποι για να δημιουργήσετε μια νέα λίστα. Ο πιο απλός είναι να περικλείσετε τα στοιχεία σε αγκύλες (“[” και ”]”):

[10, 20, 30, 40]
['crunchy frog', 'ram bladder', 'lark vomit']

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

['spam', 2.0, 5, [10, 20]]

Μια λίστα σε μια άλλη λίστα είναι εμφωλευμένη.

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

Όπως θα περίμενε κανείς, μπορείτε να εκχωρήσετε τιμές λίστας σε μεταβλητές:

>>> τυριά = ['Cheddar', 'Edam', 'Gouda']
>>> αριθμοί = [17, 123]
>>> κενή = []
>>> print(τυριά, αριθμοί, κενή)
['Cheddar', 'Edam', 'Gouda'] [17, 123] []

Οι λίστες είναι μεταβαλόμενες

Η σύνταξη, για την πρόσβαση στα στοιχεία μιας λίστας, είναι η ίδια με αυτήν της πρόσβασης στους χαρακτήρες μιας συμβολοσειράς: ο τελεστής αγκύλης. Η έκφραση μέσα στις αγκύλες καθορίζει το δείκτη. Θυμηθείτε ότι οι δείκτες ξεκινούν από το 0:

>>> print(τυριά[0])
Cheddar

Σε αντίθεση με τις συμβολοσειρές, οι λίστες είναι μεταβλητές, μπορείτε δηλαδή να αλλάξετε τη σειρά των στοιχείων μιας λίστας ή να εκχωρήσετε εκ νέου ένα στοιχείο σε μια λίστα. Όταν ο τελεστής αγκύλης εμφανίζεται στην αριστερή πλευρά μιας ανάθεσης, προσδιορίζει το στοιχείο της λίστας που θα τροποποιηθεί.

>>> numbers = [17, 123]
>>> numbers[1] = 5
>>> print(numbers)
[17, 5]

Το στοιχείο στη θέση ένα της numbers, που ήταν 123, είναι τώρα 5.

Μπορείτε να σκεφτείτε μια λίστα ως μια σχέση μεταξύ δεικτών και στοιχείων. Αυτή η σχέση ονομάζεται χαρτογράφηση (mapping). Κάθε δείκτης “αντιστοιχίζεται” σε ένα από τα στοιχεία.

Οι δείκτες λιστών λειτουργούν με τον ίδιο τρόπο όπως οι δείκτες συμβολοσειρών:

Ο τελεστής in λειτουργεί και σε λίστες.

>>> τυριά = ['Cheddar', 'Edam', 'Gouda']
>>> 'Edam' in τυριά
True
>>> 'Brie' in τυριά
False

Διάσχιση λίστα

Ο πιο συνηθισμένος τρόπος για να διασχίσετε τα στοιχεία μιας λίστας είναι με έναν βρόχο for. Η σύνταξη είναι η ίδια με τις συμβολοσειρές:

for τυρί in τυριά:
    print(τυρί)

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

for i in range(len(αριθμοί)):
    αριθμοί[i] = αριθμοί[i] * 2

Αυτός ο βρόχος διασχίζει τη λίστα και ενημερώνει κάθε στοιχείο. Το len επιστρέφει το πλήθος των στοιχείων της λίστας. Το range επιστρέφει μια λίστα δεικτών από 0 έως n − 1, όπου το n είναι το μήκος της λίστας. Κάθε φορά μέσω του βρόχου, το i λαμβάνει τον δείκτη του επόμενου στοιχείου. Η δήλωση ανάθεσης στο σώμα χρησιμοποιεί το i για να διαβάσει την παλιά τιμή του στοιχείου και να εκχωρήσει τη νέα τιμή.

Ένας βρόχος for σε μια κενή λίστα δεν εκτελεί ποτέ το σώμα:

for x in κενή:
    print('Αυτό δεν εκτελείται ποτέ.')

Αν και μια λίστα μπορεί να περιέχει μια άλλη λίστα, η ένθετη λίστα εξακολουθεί να υπολογίζεται ως ένα μεμονωμένο στοιχείο. Το μήκος της λίστας, στο παρακάτω παράδειγμα, είναι τέσσερα:

['spam', 1, ['Brie', 'Roquefort', 'Pol le Veq'], [1, 2, 3]]

Λειτουργίες λίστας

Ο τελεστής + συνενώνει λίστες:

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> c = a + b
>>> print(c)
[1, 2, 3, 4, 5, 6]

Ομοίως, ο τελεστής * επαναλαμβάνει μια λίστα πολλές φορές:

>>> [0] * 4
[0, 0, 0, 0]
>>> [1, 2, 3] * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]

Το πρώτο παράδειγμα επαναλαμβάνεται την πρώτη λίστα τέσσερις φορές και το δεύτερο τρεις φορές.

Διαμέριση λίστας

Ο τελεστής διαμέρισης λειτουργεί και σε λίστες:

>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> t[1:3]
['b', 'c']
>>> t[:4]
['a', 'b', 'c', 'd']
>>> t[3:]
['d', 'e', 'f']

Εάν παραλείψετε τον πρώτο δείκτη, το τμήμα ξεκινά από την αρχή της λίστας. Αν παραλείψετε τον δεύτερο, το τμήμα φτάνει μέχρι το τέλος της λίστας. Έτσι, εαν παραλείψετε και τους δύο, το τμήμα είναι αντίγραφο ολόκληρης της λίστας.

>>> t[:]
['a', 'b', 'c', 'd', 'e', 'f']

Δεδομένου ότι οι λίστες είναι μεταβαλλόμενες, είναι συχνά χρήσιμο να δημιουργείτε ένα αντίγραφο πριν εκτελέσετε λειτουργίες που “ανακατεύουν”, “σπάνε” ή τροποποιούν τις λίστες.

Ένας τελεστής διαμέρισης στο αριστερό μέλος μιας ανάθεσης μπορεί να ενημερώσει πολλά στοιχεία ταυτόχρονα:

>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> t[1:3] = ['x', 'y']
>>> print(t)
['a', 'x', 'y', 'd', 'e', 'f']

Μέθοδοι λίστας

Η Python παρέχει μεθόδους που λειτουργούν σε λίστες. Για παράδειγμα, η append προσθέτει ένα νέο στοιχείο στο τέλος μιας λίστας:

>>> t = ['a', 'b', 'c']
>>> t.append('d')
>>> print(t)
['a', 'b', 'c', 'd']

Η extend παίρνει μια λίστα ως όρισμα και προσθέτει όλα τα στοιχεία της στην λίστα στην οποία εφαρμόστηκε:

>>> t1 = ['a', 'b', 'c']
>>> t2 = ['d', 'e']
>>> t1.extend(t2)
>>> print(t1)
['a', 'b', 'c', 'd', 'e']

Αυτό το παράδειγμα δεν τροποποιεί το t2.

Η sort ταξινομεί τα στοιχεία της λίστας από το μικρότερο προς το μεγαλύτερο:

>>> t = ['d', 'c', 'e', 'b', 'a']
>>> t.sort()
>>> print(t)
['a', 'b', 'c', 'd', 'e']

Οι περισσότερες μέθοδοι λίστας είναι κενές. Τροποποιούν τη λίστα και επιστρέφουν None. Αν κατά λάθος γράψετε t = t.sort(), θα απογοητευτείτε με το αποτέλεσμα.

Διαγραφή στοιχείων

Υπάρχουν διάφοροι τρόποι για να διαγράψετε στοιχεία από μια λίστα. Εάν γνωρίζετε το ευρετήριο του στοιχείου που θέλετε ν διαγράψετε, μπορείτε να χρησιμοποιήσετε την pop:

>>> t = ['a', 'b', 'c']
>>> x = t.pop(1)
>>> print(t)
['a', 'c']
>>> print(x)
b

Η pop τροποποιεί τη λίστα και επιστρέφει το στοιχείο που αφαιρέθηκε. Εάν δεν δώσετε κάποιον δείκτη, διαγράφει και επιστρέφει το τελευταίο στοιχείο.

Εάν δεν χρειάζεστε την καταργημένη τιμή, μπορείτε να χρησιμοποιήσετε την εντολή del:

>>> t = ['a', 'b', 'c']
>>> del t[1]
>>> print(t)
['a', 'c']

Εάν γνωρίζετε το στοιχείο που θέλετε να αφαιρέσετε (αλλά όχι το δείκτη του), μπορείτε να χρησιμοποιήσετε το remove:

>>> t = ['a', 'b', 'c']
>>> t.remove('b')
>>> print(t)
['a', 'c']

Η επιστρεφόμενη τιμή του remove είναι None.

Για να αφαιρέσετε περισσότερα από ένα στοιχεία, μπορείτε να χρησιμοποιήσετε το del με ένα δείκτη διαμέρισης:

>>> t = ['a', 'b', 'c', 'd', 'e', 'f']
>>> del t[1:5]
>>> print(t)
['a', 'f']

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

Λίστες και συναρτήσεις

Υπάρχει ένα πλήθος ενσωματωμένων συναρτήσεων που μπορούν να χρησιμοποιηθούν σε λίστες και που σας επιτρέπουν να εξετάζεται γρήγορα μια λίστα, χωρίς να γράφετε τους δικούς σας βρόχους:

>>> nums = [3, 41, 12, 9, 74, 15]
>>> print(len(nums))
6
>>> print(max(nums))
74
>>> print(min(nums))
3
>>> print(sum(nums))
154
>>> print(sum(nums)/len(nums))
25

Η συνάρτηση sum() λειτουργεί μόνο όταν τα στοιχεία της λίστας είναι αριθμοί. Οι άλλες δύο συναρτήσεις (max(), len(), etc.) λειτουργούν και με λίστες συμβολοσειρών και άλλους τύπους, που μπορούν να είναι συγκρίσιμοι.

Θα μπορούσαμε να ξαναγράψουμε ένα προηγούμενο πρόγραμμα, που υπολόγιζε τον μέσο όρο μιας λίστας αριθμών που εισήγαγε ο χρήστης, χρησιμοποιώντας μια λίστα.

Πρώτα, το πρόγραμμα για τον υπολογισμό ενός μέσου όρου χωρίς λίστα:

σύνολο = 0
πλήθος = 0
while (True):
    είσοδος = input('Εισαγάγετε έναν αριθμό: ')
    if είσοδος == 'τέλος': break
    τιμή = float(είσοδος)
    σύνολο = σύνολο + τιμή
    πλήθος = πλήθος + 1

μέσοςΌρος = σύνολο / πλήθος
print('Μέσος Όρος:', μέσοςΌρος)

# Code: http://www.gr.py4e.com/code3/avenum.py

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

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

numlist = list()
while (True):
    είσοδος = input('Εισαγάγετε έναν αριθμό: ')
    if είσοδος == 'τέλος': break
    τιμή = float(είσοδος)
    numlist.append(τιμή)

μέσοςΌρος = sum(numlist) / len(numlist)
print('Μέσος Όρος:', μέσοςΌρος)

# Code: http://www.gr.py4e.com/code3/avelist.py

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

Λίστες και συμβολοσειρές

Μια συμβολοσειρά είναι μια ακολουθία χαρακτήρων και μια λίστα είναι μια ακολουθία τιμών, αλλά μια λίστα χαρακτήρων δεν είναι ίδια με μια συμβολοσειρά. Για να μετατρέψετε μια συμβολοσειρά σε μια λίστα χαρακτήρων, μπορείτε να χρησιμοποιήσετε τη list:

>>> s = 'spam'
>>> t = list(s)
>>> print(t)
['s', 'p', 'a', 'm']

Επειδή το list είναι το όνομα μιας ενσωματωμένης συνάρτησης, θα πρέπει να αποφύγετε τη χρήση της ως όνομα μεταβλητής. Επίσης αποφεύγω το γράμμα “l’ γιατί μοιάζει πάρα πολύ με τον αριθμό”1”. Γι’ αυτό λοιπόν χρησιμοποιώ το “t”.

Η συνάρτηση list σπάει μια συμβολοσειρά σε μεμονωμένα γράμματα. Εάν θέλετε να χωρίσετε μια συμβολοσειρά σε λέξεις, μπορείτε να χρησιμοποιήσετε τη μέθοδο split:

>>> s = 'pining for the fjords'
>>> t = s.split()
>>> print(t)
['pining', 'for', 'the', 'fjords']
>>> print(t[2])
the

Αφού χρησιμοποιήσετε το split για να σπάσετε τη συμβολοσειρά σε μια λίστα λέξεων, μπορείτε να χρησιμοποιήσετε τον τελεστή ευρετηρίου (τετράγωνη αγκύλη) για να δείτε μια συγκεκριμένη λέξη στη λίστα.

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

>>> s = 'spam-spam-spam'
>>> οριοθέτης = '-'
>>> s.split(οριοθέτης)
['spam', 'spam', 'spam']

Η join είναι το αντίστροφο του split. Παίρνει μια λίστα με συμβολοσειρές και συνενώνει τα στοιχεία της. Το join είναι μια μέθοδος συμβολοσειράς, επομένως πρέπει να την καλέσετε στον οριοθέτη και να μεταβιβάσετε τη λίστα ως παράμετρο:

>>> t = ['pining', 'for', 'the', 'fjords']
>>> οριοθέτης = ' '
>>> οριοθέτης.join(t)
'pining for the fjords'

Σε αυτήν την περίπτωση, ο οριοθέτης είναι ένας χαρακτήρας διαστήματος, επομένως η join βάζει ένα διάστημα μεταξύ των λέξεων. Για να συνδέσετε συμβολοσειρές χωρίς κενά, μπορείτε να χρησιμοποιήσετε την κενή συμβολοσειρά ““, ως οριοθέτη.

Ανάλυση γραμμών

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

From [email protected] Sat Jan  5 09:14:16 2008

Η μέθοδος split είναι πολύ αποτελεσματική, όταν αντιμετωπίζετε τέτοιου είδους προβλήματα. Μπορούμε να γράψουμε ένα μικρό πρόγραμμα που αναζητά γραμμές, που ξεκινούν με “From”, να διαχωρίσουμε (split) αυτές τις γραμμές και, στη συνέχεια, να εκτυπώσουμε την τρίτη λέξη στη γραμμή:

fhand = open('mbox-short.txt')
for γραμμή in fhand:
    γραμμή = γραμμή.rstrip()
    if not γραμμή.startswith('From '): continue
    λέξεις = γραμμή.split()
    print(λέξεις[2])

# Code: http://www.gr.py4e.com/code3/search5.py

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

    Sat
    Fri
    Fri
    Fri
    ...

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

Αντικείμενα και τιμές

Εάν εκτελέσουμε αυτές τις εντολές ανάθεσης:

a = 'banana'
b = 'banana'

Γνωρίζουμε ότι το a και το b αναφέρονται και τα δύο σε μια συμβολοσειρά, αλλά δεν ξέρουμε αν αναφέρονται στην ίδια συμβολοσειρά. Υπάρχουν δύο πιθανές καταστάσεις:

Μεταβλητές και Αντικείμενα
Μεταβλητές και Αντικείμενα

Στο πρώτο σχήμα, τα a και b αναφέρονται σε δύο διαφορετικά αντικείμενα, που έχουν την ίδια τιμή. Στο δεύτερο σχήμα αναφέρονται στο ίδιο αντικείμενο.

Για να ελέγξετε αν δύο μεταβλητές αναφέρονται στο ίδιο αντικείμενο, μπορείτε να χρησιμοποιήσετε τον τελεστή is.

>>> a = 'banana'
>>> b = 'banana'
>>> a is b
True

Σε αυτό το παράδειγμα, η Python δημιούργησε μόνο ένα αντικείμενο συμβολοσειράς και το a και το b αναφέρονται σε αυτό.

Αλλά όταν δημιουργείτε δύο λίστες, δημιουργούνται δύο αντικείμενα:

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a is b
False

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

Μέχρι τώρα, χρησιμοποιούσαμε το “αντικείμενο” και την “τιμή” εναλλακτικά, αλλά είναι πιο ακριβές να πούμε ότι ένα αντικείμενο έχει μια τιμή. Εάν a = [1,2,3], το a αναφέρεται σε ένα αντικείμενο λίστας του οποίου η τιμή είναι μια συγκεκριμένη ακολουθία στοιχείων. Εάν μια άλλη λίστα έχει τα ίδια στοιχεία, θα λέγαμε ότι έχει την ίδια τιμή.

Ψευδωνυμία

Εάν το a αναφέρεται σε ένα αντικείμενο και εκτελέσετε b = a, τότε και οι δύο μεταβλητές αναφέρονται στο ίδιο αντικείμενο:

>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True

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

Ένα αντικείμενο με περισσότερες από μία αναφορές, έχει περισσότερα από ένα ονόματα, οπότε λέμε ότι το αντικείμενο έχει ψευδώνυμα.

Εάν το αντικείμενο με ψευδώνυμα είναι μεταβαλλόμενο, οι αλλαγές που γίνονται με το ένα ψευδώνυμο επηρεάζουν το άλλο:

>>> b[0] = 17
>>> print(a)
[17, 2, 3]

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

Για αμετάβλητα αντικείμενα όπως οι συμβολοσειρές, η ψευδωνυμία δεν είναι τόσο σοβαρό πρόβλημα. Σε αυτό το παράδειγμα:

a = 'banana'
b = 'banana'

Σχεδόν ποτέ δεν έχει διαφορά εάν το a και το b αναφέρονται στην ίδια συμβολοσειρά ή όχι.

Ορίσματα λίστας

Όταν μεταβιβάζετε μια λίστα σε μια συνάρτηση, η συνάρτηση λαμβάνει μια αναφορά στη λίστα. Εάν η συνάρτηση τροποποιήσει μια παράμετρο λίστας, η αλλαγή πραγματοποιείται και στην λίστα που μεταβιβάστηκε ως όρισμα. Για παράδειγμα, το delete_head αφαιρεί το πρώτο στοιχείο από μια λίστα:

def delete_head(t):
    del t[0]

Δείτε πώς χρησιμοποιείται:

>>> γράμματα = ['a', 'b', 'c']
>>> delete_head(γράμματα)
>>> print(γράμματα)
['b', 'c']

Η παράμετρος t και η μεταβλητή γράμματα είναι ψευδώνυμα για το ίδιο αντικείμενο.

Είναι σημαντικό να γίνεται διάκριση μεταξύ λειτουργιών που τροποποιούν λίστες και λειτουργιών που δημιουργούν νέες λίστες. Για παράδειγμα, η μέθοδος append τροποποιεί μια λίστα, αλλά ο τελεστής + δημιουργεί μια νέα λίστα:

>>> t1 = [1, 2]
>>> t2 = t1.append(3)
>>> print(t1)
[1, 2, 3]
>>> print(t2)
None

>>> t3 = t1 + [3]
>>> print(t3)
[1, 2, 3]
>>> t2 is t3
False

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

def bad_delete_head(t):
    t = t[1:]              # WRONG!

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

Μια εναλλακτική είναι να γράψετε μια συνάρτηση που δημιουργεί και επιστρέφει μια νέα λίστα. Για παράδειγμα, η tail επιστρέφει όλα εκτός από το πρώτο στοιχείο μιας λίστας:

def tail(t):
    return t[1:]

Αυτή η συνάρτηση αφήνει την αρχική λίστα χωρίς αμετάβλητη. Δείτε πώς χρησιμοποιείται:

>>> γράμματα = ['a', 'b', 'c']
>>> υπόλοιπο = tail(γράμματα)
>>> print(υπόλοιπο)
['b', 'c']

Άσκηση 1: Γράψτε μια συνάρτηση με όνομα chop, που δέχεται μια λίστα και την τροποποιεί, αφαιρώντας το πρώτο και το τελευταίο στοιχείο και επιστρέφει None. Στη συνέχεια, γράψτε μια συνάρτηση που ονομάζεται middle, που δέχεται μια λίστα και επιστρέφει μια νέα λίστα που περιέχει όλα τα στοιχεία της αρχική, εκτός από το πρώτο και το τελευταίο.

Εκσφαλμάτωση

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

  1. Μην ξεχνάτε ότι οι περισσότερες μέθοδοι λίστας τροποποιούν το όρισμα και επιστρέφουν None, αντίθετα από τις μεθόδους συμβολοσειράς, οι οποίες επιστρέφουν μια νέα συμβολοσειρά και αφήνουν το πρωτότυπο αναλλοίωτο.

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

    word = word.strip()

    Είναι, συχνά, δελεαστικό να γράψετε αντίστοιχο κώδικα και για λίστες:

    t = t.sort()           # ΛΑΘΟΣ!

    Επειδή η sort επιστρέφει None, η επόμενη λειτουργία που θα εκτελέσετε με το t είναι πιθανό να αποτύχει. Πριν χρησιμοποιήσετε μεθόδους λίστας και τελεστές, θα πρέπει να διαβάσετε προσεκτικά την τεκμηρίωση και στη συνέχεια να κάνετε δοκιμές σε διαδραστική λειτουργία. Οι μέθοδοι και οι τελεστές που είναι κοινές σε λίστες με άλλες ακολουθίες (όπως συμβολοσειρές) τεκμηριώνονται στη διεύθυνση:

    docs.python.org/library/stdtypes.html#common-sequence-operations

    Οι μέθοδοι και οι τελεστές που ισχύουν μόνο για μεταβαλλόμενες ακολουθίες τεκμηριώνονται στη διεύθυνση:

    docs.python.org/library/stdtypes.html#mutable-sequence-types

  2. Διάλεξε ένα ιδίωμα και μείνε με αυτό.

    Μέρος του προβλήματος με τις λίστες είναι ότι υπάρχουν πάρα πολλοί τρόποι για να κάνετε πράγματα. Για παράδειγμα, για να αφαιρέσετε ένα στοιχείο από μια λίστα, μπορείτε να χρησιμοποιήσετε τις pop, remove, del ή ακόμα και μια εκχώρηση με τον τελεστή διαμέρισης.

    Για να προσθέσετε ένα στοιχείο, μπορείτε να χρησιμοποιήσετε τη μέθοδο append ή τον τελεστή +. Αλλά μην ξεχνάτε ότι αυτό είναι το σωστά:

    t.append(x)
    t = t + [x]

    Και αυτό είναι λάθος:

    t.append([x])          # ΛΑΘΟΣ!
    t = t.append(x)        # ΛΑΘΟΣ!
    t + [x]                # ΛΑΘΟΣ!
    t = t + x              # ΛΑΘΟΣ!

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

  3. Δημιουργήστε αντίγραφα για να αποφύγετε τη ψευδωνυμία

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

    orig = t[:]
    t.sort()

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

  4. Λίστες, split και αρχεία

    Όταν διαβάζουμε και αναλύουμε αρχεία, υπάρχουν πολλές πιθανότητες να συναντήσουμε είσοδο που ενδέχεται να διακόψει το πρόγραμμά μας, επομένως είναι καλή ιδέα να επανεξετάσουμε το μοτίβο guardian (κηδεμονίας) όταν πρόκειται να γράψουμε προγράμματα που διαβάζουν από ένα αρχείο και να αναζητήσουμε μια “βελόνα στο άχυρα”.

    Ας δούμε ξανά το πρόγραμμά μας, που αναζητά την ημέρα της εβδομάδας στις γραμμές του αρχείου μας:

    From [email protected] Sat Jan  5 09:14:16 2008

    Εφόσον χωρίζουμε αυτή τη γραμμή σε λέξεις, θα μπορούσαμε να παραιτηθούμε από τη χρήση του startswith και απλώς να δούμε την πρώτη λέξη της γραμμής για να προσδιορίσουμε αν μας ενδιαφέρει αυτή η γραμμή ή όχι. Μπορούμε να χρησιμοποιήσουμε το continue για να παραλείψουμε τις γραμμές που δεν έχουν το “From” ως πρώτη λέξη ως εξής:

    fhand = open('mbox-short.txt')
    for γραμμή in fhand:
        λέξεις = γραμμή.split()
        if λέξεις[0] != 'From' : continue
        print(λέξεις[2])

    Αυτό φαίνεται πολύ πιο απλό και δεν χρειάζεται καν να χρησιμοποιήσουμε το rstrip για να αφαιρέσουμε το χαρακτήρα νέας γραμμής από το τέλος του αρχείου. Είναι όμως καλύτερο;

    python search8.py
    Sat
    Traceback (most recent call last):
      File "search8.py", line 5, in <module>
        if λέξεις[0] != 'From' : continue
    IndexError: list index out of range

    Λειτουργεί αρχικά και βλέπουμε την ημέρα από την πρώτη γραμμή (Sat), αλλά μετά το πρόγραμμα αποτυγχάνει με ένα σφάλμα traceback. Τι πήγε στραβά; Ποια μπερδεμένα δεδομένα προκάλεσαν την αποτυχία του κομψό, έξυπνου και πολύ Pythonic προγράμματός μας;

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

    Τώρα αυτή η προσέγγιση μπορεί να παράξει πολλές γραμμές εξόδου, αλλά τουλάχιστον θα έχετε αμέσως κάποιες ενδείξεις για το πρόβλημα που αντιμετωπίζετε. Έτσι, προσθέτουμε μια εκτύπωση της μεταβλητής words, ακριβώς πριν από τη γραμμή πέντε. Προσθέτουμε ακόμη και ένα πρόθεμα “Εντοπισμός σφαλμάτων:” στη γραμμή, ώστε να μπορούμε να διατηρήσουμε την κανονική μας έξοδο ξεχωριστά από την έξοδο εντοπισμού σφαλμάτων.

    for γραμμή in fhand:
        λέξεις = γραμμή.split()
        print('Εντοπισμός σφαλμάτων:', λέξεις)
        if λέξεις[0] != 'From' : continue
        print(λέξεις[2])

    Όταν εκτελούμε το πρόγραμμα, πολλές εξόδοι κυλούν στο πάνω μέρος της οθόνης, αλλά στο τέλος, βλέπουμε την έξοδο εντοπισμού σφαλμάτων και την σφάλμα traceback, ώστε να γνωρίζουμε τι συνέβη λίγο πριν από το σφάλμα.

    Debug: ['X-DSPAM-Confidence:', '0.8475']
    Debug: ['X-DSPAM-Probability:', '0.0000']
    Debug: []
    Traceback (most recent call last):
      File "search9.py", line 6, in <module>
        if λέξεις[0] != 'From' : continue
    IndexError: list index out of range

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

    X-DSPAM-Result: Innocent
    X-DSPAM-Processed: Sat Jan  5 09:14:16 2008
    X-DSPAM-Confidence: 0.8475
    X-DSPAM-Probability: 0.0000
    
    Details: http://source.sakaiproject.org/viewsvn/?view=rev&rev=39772

    Το σφάλμα παρουσιάζεται όταν το πρόγραμμά μας συναντήσει μια κενή γραμμή! Φυσικά, υπάρχουν μηδέν λέξεις σε μια κενή γραμμή. Γιατί δεν το σκεφτήκαμε όταν γράφαμε τον κώδικα; Όταν ο κώδικας αναζητά την πρώτη λέξη (λέξη[0]) για να ελέγξει αν ταιριάζει με το “From”, λαμβάνουμε το σφάλμα “index out of range”.

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

    fhand = open('mbox-short.txt')
    for γραμμή in fhand:
        λέξεις = γραμμή.split()
        # print('Εντοπισμός σφαλμάτων:', λέξεις)
        if len(λέξεις) == 0 : continue
        if λέξεις[0] != 'From' : continue
        print(λέξεις[2])

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

    Οι δύο δηλώσεις continue μας βοηθούν να φιλτράρουμε το σύνολο των γραμμών που μας “ενδιαφέρουν” και τις οποίες θέλουμε να επεξεργαστούμε λίγο περισσότερο. Μια γραμμή που δεν έχει λέξεις μας είναι “αδιάφορη”, οπότε την αγνοούμε και μεταβαίνουμε στην επόμενη γραμμή. Μια γραμμή που δεν έχει ως πρώτη λέξη το “From” δεν μας ενδιαφέρει, οπότε την παρακάμπτουμε κι αυτήν.

    Το πρόγραμμα όπως τροποποιήθηκε εκτελείται με επιτυχία, οπότε ίσως είναι σωστό. Η δήλωση του κηδεμόνα μας διασφαλίζει ότι το λέξεις[0] δεν θα αποτύχει ποτέ, αλλά ίσως δεν είναι αρκετό. Όταν προγραμματίζουμε, πρέπει πάντα να σκεφτόμαστε, “Τι μπορεί να πάει στραβά;”

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

Άσκηση 3: Ξαναγράψτε τον κώδικα κηδεμόνα του παραπάνω παραδείγματος, χωρίς τις δύο εντολές if. Αντ’ αυτών, χρησιμοποιήστε μια σύνθετη λογική έκφραση, χρησιμοποιώντας τον λογικό τελεστή or με μία μόνο εντολή if.

Γλωσσάριο

αναφορά
Η συσχέτιση μεταξύ μιας μεταβλητής και της τιμής της.
αντικείμενο
Κάτι στο οποίο μπορεί να αναφέρεται μια μεταβλητή. Ένα αντικείμενο έχει έναν τύπο και μια τιμή.
δείκτης
Μια ακέραια τιμή που υποδεικνύει ένα στοιχείο σε μια λίστα.
διάσχιση λίστας (list traversal)
Η διαδοχική πρόσβαση σε κάθε στοιχείο μιας λίστας.
εμφωλευμένη λίστα
Μια λίστα που είναι στοιχείο μιας άλλης λίστας.
ισοδύναμο
Έχει την ίδια τιμή.
λίστα
Μία ακολουθία τιμών.
οριοθέτης
Ένας χαρακτήρας ή συμβολοσειρά που χρησιμοποιείται για να υποδείξει πού πρέπει να χωριστεί μια συμβολοσειρά.
στοιχείο
Μία από τις τιμές σε μια λίστα (ή άλλη ακολουθία).
ταυτόσημο
Είναι το ίδιο αντικείμενο (που συνεπάγεται ισοδύναμο).
ψευδωνυμία
Μια περίσταση όπου δύο ή περισσότερες μεταβλητές αναφέρονται στο ίδιο αντικείμενο.

Ασκήσεις

Άσκηση 4: Βρείτε όλες τις μοναδικές λέξεις σε ένα αρχείο

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

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

Για να ξεκινήσετε, κατεβάστε ένα αντίγραφο του αρχείου www.gr.py4e.com/code3/romeo.txt. Δημιουργήστε μια λίστα,στην οποία να εμφανίζεται κάθε λέξη μία μόνο φορά, η οποία θα περιέχει το τελικό αποτέλεσμα. Γράψτε ένα πρόγραμμα για να ανοίξετε το αρχείο romeo.txt και να το διαβάσετε γραμμή προς γραμμή. Για κάθε γραμμή, χωρίστε την σε μια λίστα λέξεων χρησιμοποιώντας τη συνάρτηση split. Για κάθε λέξη, ελέγξτε αν η λέξη περιέχεται ήδη στη λίστα με τις μοναδικές λέξεις. Εάν η λέξη δεν περιέχεται στη λίστα με τις μοναδικές λέξεις, προσθέστε τη στη λίστα. Όταν ολοκληρωθεί το πρόγραμμα, ταξινομήστε και εκτυπώστε τη λίστα με τις μοναδικές λέξεις, σε αλφαβητική σειρά.

Εισαγάγετε το αρχείο: romeo.txt
['Arise', 'But', 'It', 'Juliet', 'Who', 'already',
'and', 'breaks', 'east', 'envious', 'fair', 'grief',
'is', 'kill', 'light', 'moon', 'pale', 'sick', 'soft',
'sun', 'the', 'through', 'what', 'window',
'with', 'yonder']

Άσκηση 5: Μινιμαλιστικός Εξυπηρετητής Email (Email Client).

Το MBOX (mail box) είναι μια δημοφιλής μορφή αρχείου για αποθήκευση και κοινή χρήση μιας συλλογής email. Αυτό χρησιμοποιήθηκε από πρώιμους διακομιστές email και εφαρμογές επιτραπέζιου υπολογιστή. Χωρίς να μπαίνουμε σε πάρα πολλές λεπτομέρειες, το MBOX είναι ένα αρχείο κειμένου, στο οποίο αποθηκεύονται διαδοχικά email. Τα email διαχωρίζονται από μια ειδική γραμμή που ξεκινά με From (προσέξτε το διάστημα). Είναι σημαντικό ότι οι γραμμές που ξεκινούν με From: (προσέξτε την άνω και κάτω τελεία) περιγράφουν το ίδιο το email και δεν λειτουργούν ως διαχωριστικά. Φανταστείτε ότι έχετε γράψει μια μινιμαλιστική εφαρμογή email, η οποία αναφέρει τα email των αποστολέων στα Εισερχόμενα του χρήστη και μετράει τον αριθμό των email.

Γράψτε ένα πρόγραμμα που διαβάζει τα δεδομένα του γραμματοκιβωτίου και όταν εντοπίσει γραμμή που ξεκινά με “From”, χωρίζει τη γραμμή σε λέξεις, χρησιμοποιώντας τη συνάρτηση split. Μας ενδιαφέρει ποιος έστειλε το μήνυμα, που είναι η δεύτερη λέξη στη γραμμή From”.

From [email protected] Sat Jan 5 09:14:16 2008

Θα αναλύει τη γραμμή From και θα εκτυπώνει τη δεύτερη λέξη κάθε γραμμής From, στη συνέχεια θα μετράει τον αριθμό των γραμμών From (όχι From:) και θα εκτυπώνει, στο τέλος, το πλήθος τους. Αυτό είναι ένα καλό δείγμα εξόδου με μερικές γραμμές που έχουν αφαιρεθεί:

python fromcount.py
Εισαγάγετε ένα όνομα αρχείου: mbox-short.txt
[email protected]
[email protected]
[email protected]

[...κάποια έξοδος αφαιρέθηκε...]

[email protected]
[email protected]
[email protected]
[email protected]
Βρέθηκαν 27 γραμμές στο αρχείο με πρώτη λέξη το From

Άσκηση 6: Ξαναγράψτε το πρόγραμμα που ζητά από τον χρήστη μια λίστα με αριθμούς και εκτυπώνει το μέγιστο και το ελάχιστο των αριθμών, στο τέλος, όταν ο χρήστης εισάγει “τέλος”. Τροποποιήστε το πρόγραμμα ώστε να αποθηκεύει τους αριθμούς, που εισάγει ο χρήστης, σε μια λίστα και χρησιμοποιήστε τις συναρτήσεις max() και min() για να υπολογίσετε τον μέγιστο και τον ελάχιστο αριθμή μετά την ολοκλήρωση του βρόχου.

Εισαγάγετε έναν αριθμό: 6
Εισαγάγετε έναν αριθμό: 2
Εισαγάγετε έναν αριθμό: 9
Εισαγάγετε έναν αριθμό: 3
Εισαγάγετε έναν αριθμό: 5
Εισαγάγετε έναν αριθμό: τέλος
Μέγιστο: 9.0
Ελάχιστο: 2.0

Αν εντοπίσετε κάποιο λάθος σε αυτό το βιβλίο μην διστάσετε να μου στείλετε τη διόρθωση στο Github.