Σημειωματάριο Τρίτης 24 Νοεμβρίου 2015

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

Πώς διαβάζουμε από αρχεία

Για να διαβάσουμε από ένα αρχείο, π.χ. το αρχείο a.txt που είναι αποθηκευμένο στο δίσκο μας και του οποίου τα περιεχόμενα είναι οι παρακάτω γραμμές

arnaki aspro
kai paxy

ths manas toy kamari.

χρησιμοποιούμε τη συνάρτηση open που φαίνεται παρακάτω για να "ανοίξουμε" το αρχείο. Η συνάρτηση open παίρνει ως ορίσματα το όνομα του αρχείου και το string "r" ή το string "w" αν πρόκειται να διαβάσουμε ή να γράψουμε το αρχείο αντίστοιχα (υπάρχουν κι άλλες επιλογές για το δεύτερο όρισμα αλλά δε θα τις χρειαστούμε προς το παρόν).

Η συνάρτηση open, αν τρέξει επιτυχώς (μπορεί να αποτύχει αν π.χ. το αρχείο δε βρεθεί ή αν βρεθεί και δεν έχουμε το δικαίωμα να το διαβάσουμε) επιστρέφει μια τιμή της οποίας ο τύπος είναι "αρχείο" (δεν έχουμε ξαναδεί τέτοιο τύπο δεδομένων) αλλά δε χρειάζεται να ασχοληθούμε καθόλου με το τι είδους είναι αυτός ο τύπος δεδομένων. Το σημαντικό για μας είναι ότι μετά την κλήση στη συνάρτηση open, π.χ. με τη γραμμή f = open("a.txt", "r") που φαίνεται παρακάτω, στη μεταβλητή f είναι πλέον αποθηκευμένη όλη η πληροφορία που χρειαζόμαστε για να έχουμε πρόσβαση (ανάγνωση ή εγγραφή) στο αρχείο μας. Οποιαδήποτε πράξη κάνουμε στο αρχείο μας από δω και πέρα θα γίνεται μέσω της μεταβλητής αυτής.

Αφού ανοίξουμε το αρχείο μας με την open έπειτα διαβάζουμε με τη μέθοδο .readlines() όλες τις γραμμές του αρχείου. Η κλήση f.readlines() λοιπόν μας επιστρέφει σε μια λίστα, με τη σειρά, τόσα strings όσες και οι γραμμές του αρχείου που έχουμε ανοίξει στη μεταβλητή f. Κάθε string της λίστας αυτής είναι μια ολόκληρη γραμμή του αρχείου.

Τυπώνουμε στο παράδειγμα αυτό τη λίστα αυτή. Παρατηρούμε ότι ο χαρακτήρας αλλαγής γραμμής '\n' είναι τμήμα καθενός από αυτά τα strings.

Τέλος κλείνουμε το αρχείο (πάντα πρέπει να το κάνουμε αυτό αφού τελειώσουμε την επεξεργασία του αρχείου) με την εντολή f.close().

In [4]:
f = open("a.txt", "r")
print f.readlines()
f.close()
['arnaki aspro\n', 'kai paxy\n', '\n', 'ths manas toy kamari.\n']

Πώς γράφουμε σε αρχεία

Στο επόμενο κελλί ανοίγουμε το αρχείο b.txt για γράψιμο. Είναι σημαντικό να τονίσουμε εδώ ότι με αυτή την εντολή (α) αν το αρχείο b.txt δεν υπάρχει τότε αυτό δημισουργείται ως ένα κενό αρχείο και (β) αν το αρχείο b.txt υπάρχει τότε τα περιεχόμενα που είχε πριν εξαφανίζονται και το αρχείο είναι πλέον κενό, έτοιμο να δεχτεί νέο κείμενο για εγγραφή.

Στο επόμενο πρόγραμμα γράφουμε 100 γραμμές στο αρχείο, όλες ίσες με abc. Η εγγραφή ενός string γίνεται στο αρχείο f με τη μέθοδο .write() η οποία παίρνει ως όρισμα το string που θέλουμε να εγγραφεί. Σε αντίθεση με τη συνάρτηση print, της οποίας η προκαθορισμένη συμπεριφορά είναι να τυπώσει ό,τι τυπώσει και μετά να αλλάξει γραμμή, η μέθοδος .write() δεν αλλάζει γραμμή. Αν λοιπόν εμείς θέλουμε να γράψουμε μια ολόκληρη γραμμή πρέπει ρητά να ζητήσουμε να τυπωθεί ο χαρακτήρας αλλαγής γραμμής '\n', και αυτό κάνουμε παρακάτω.

Αφού γράψουμε όλες τις γραμμές που θέλουμε να γράψουμε κλείνουμε το αρχείο με τη μέθοδο .close() και πάλι. Πλέον το αρχείο μας είναι γραμμένο στο δίσκο.

In [8]:
f = open("b.txt", "w")
for i in range(100):
    f.write("abc\n")
f.close()

Οι μέθοδοι rstrip και split

Για να απαλλαγούμε από τους χαρακτήρες '\n' που έχουν τα strings μέσα στη λίστα που μας επιστρέφει η .readlines() τη μέθοδο rstrip. Στην απλή της μορφή, αν s είναι ένα string, το string s.rstrip() είναι το ίδιο το s μόνο που έχουμε στο τέλος του διαγράψει όλους τους "λευκούς" χαρακτήρες (κενό, tab ή newline). Έτσι π.χ. το string "abc efg \n".rstrip() είναι το `"abc efg".

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

In [2]:
print "abc efg \n".rstrip()

print "abc,def,ghijk".split(",")
abc efg
['abc', 'def', 'ghijk']

Συνέχεια του προγράμματος τηλεφωνικού καταλόγου. Διάβασμα και γράψιμο του καταλόγου σε αρχείο.

Ως είχε μέχρι τώρα το πρόγραμμα του τηλεφωικού καταλόγου ήταν μάλλον άχρηστο γιατί όλη η πληροφορία που υπήρχε στη μνήμη μας (μεταβλητή mem) χανόταν μετά το τέλος του προγράμματος. Για να μη συμβαίνει αυτό θα πρέπει η πληροφορία αυτή να γράφεται σε ένα αρχείο (εδώ έχουμε επιλέξει το όνομα phonebook.txt για αυτό το αρχείο, το οποίο και αποθηκεύσαμε στη μεταβλητή filename) με τρόπο ώστε να μπορούμε, διαβάζοντας αυτό το αρχείο να την ανακτήσουμε σε μελλοντικό τρέξιμο του προγράμματος.

Για αυτό το λόγο και προσθέσαμε τις δύο επιλογές

    if ans=="r":
        readfromfile()
        return 0
    if ans=="w":
        writetofile()
        return 0

στη συνάρτηση menu(). Επιλέγοντας την πρώτη ο χρήστης διαβάζει τα περιεχόμενα του αρχείου και τα αποθηκεύει στη μεταβλητή mem και επιλέγοντας τη δεύτερη ο χρήστης αποθηκεύει τα περιεχόμενα της μεταβλητής mem στο αρχείο αυτό (ώστε να τα ξαναβρεί αργότερα). Δείτε τα σχόλια μέσα σε αυτές τις δύο συναρτήσεις για να δείτε πώς λειτουργούν.

Προσθέσαμε επίσης τη συνάρτηση showperson(d), όπου d είναι το λεξικό κάποιου ατόμου, η οποία με ενιαίο τρόπο τυπώνει όλη την πληροφορία που έχουμε για το άτομο. Είναι καλύτερο να έχουμε μια τέτοια συνάρτηση και να την καλούμε όποτε τη χρειαζόμαστε παρά να γράφουμε κάθε φορά την εντολή print για να έχουμε το ίδιο αποτέλεσμα.

Τέλος προσθέσαμε τη δυνατότητα αποθήκευσης και email σε κάθε άτομο (προαιρετικά). Π.χ. η συνάρτηση showperson ελέγχει αν το λεξικό έχει και κλειδί "email" και αν έχει το τυπώνει και αυτό στις πληροφορίες για το άτομο. Σε κάποια σημεία του προγράμματος όμως δεν έχει τελειώσει η προσαρμογή του ώστε να χειρίζεται το προαιρετικό κλειδί email. Π.χ. δεν υπάρχει ακόμη η δυνατότητα στο χρήστη να εισάγει email ενός ατόμου.

In [*]:
from IPython.display import clear_output

filename = "phonebook.txt"

mem = [] # Η μεταβλητή αυτή είναι αρχικά κενή.

def exists(fn, ln):
    """
    Ψάχνει τη λίστα mem για να δει αν υπάρχει άτομο με firstname ίσο με fn και lastname ίσο με ln.
    Αν υπάρχει μας επιστρέφει τη θέση του στη λίστα (>=0) αλλιώς επιστρέφει -1.
    Το αν ένα γράμμα είναι κεφαλαίο ή μικρό δεν επηρεάζει το αποτέλεσμα.
    """
    count = 0
    for person in mem:
        if ( fn.lower()==person["firstname"].lower() and 
             ln.lower() == person["lastname"].lower() ):
            return count # Βρέθηκε εγγραφή στο mem με αυτά τα στοιχεία
        else:
            count += 1
    return -1 # Δε βρέθηκε τέτοια εγγραφή στο mem


def readstring(old):
    """
    Διαβάζει ένα string από το χρήστη. Αν ο χρήστης δώσει enter επιστρέφει το string old,
    αλλιώς ό,τι έδωσε ο χρήστης. Έτσι είναι εύκολο για το χρήστη να αποδεχτεί την προτεινόμενη
    από το πρόγραμμα τιμή ή να επιλέξει να δώσει μια νέα τιμή.
    """
    s = raw_input("Give new value or enter for [%s]: " % old)
    if len(s) == 0:
        return old
    else:
        return s

def showperson(d):
    """
    Τυπώνει τις πληροφορίες για το άτομο με λεξικό d
    """
    # Κατ' αρχήν τυπώνει μικρό όνομα, επώνυμο και τηλέφωνο.
    print "%-20s %-30s %-15s" % (d["firstname"], d["lastname"], d["phone"]),
    if "email" in d: # Αν υπάρχει και το κλειδί email στο λεξικό τότε τυπώνει και το email, αλλιώς όχι.
        print " %-25s" % d["email"]
    else:
        print 
    
# Η παρακάτω συνάρτηση είναι η συνάρτηση που λειτουργεί στο ανώτατο επίπεδο και καλεί όλες τις άλλες συναρτήσεις
def menu():
    """
    Ο χρήστης επιλέγει τι θα κάνει. Αν επιλέξει q επιστρέφει η συνάρτηση 1 αλλιώς
    επιστρέφει 0
    """
    clear_output() # Καθαρίζει την οθόνη (μόνο για το σύστημα ipython)
    print """
    s. Look up a person
    i. Insert a person
    e. Edit a person
    d. Delete a person
    l. Show all
    r: Read from file
    w: Write to file
    q: Quit
    """
    ans = raw_input("Please choose: ") # Ο χρήστης επιλέγει τι θέλει να κάνει δίνοντας (συνήθως) ένα γράμμα
    
    if len(ans) == 0: # Πάτησε απλά enter. Επιστρέφουμε χωρίς να κάνουμε τίποτε.
        return 0

    if ans in "": # Οι επιλογές αυτές δεν έχουν υλοποιηθεί ακόμη
        print "Not implemented yet"
        return 0
    if ans=="s": # Αναζήτηση ατόμου
        lookup_and_print()
        return 0
    if ans=="i": # Εισαγωγή νέου ατόμου
        insert()
        return 0
    if ans=="e": # Επεξεργασία υπάρχοντος ατόμου
        edit()
        return 0
    if ans=="d": # Διαγραφή ατόμου
        delete()
        return 0
    if ans=="l": # Λίστα όλων των ατόμων του καταλόγου
        showall()
        return 0
    if ans=="r": # Διάβασμα του καταλόγου από το αρχείο
        readfromfile()
        return 0
    if ans=="w": # Γράψιμο του καταλόγου στο αρχείο
        writetofile()
        return 0
    if ans=="q":# Έξοδος από το πρόγραμμα. Επιστρέφουμε 1 ώστε όποιος κάλεσε τη menu() να καταλάβει
                # ότι δεν πρέπει να την ξανακαλέσει. Σε όλες τις άλλες περιπτώσεις επιστρέφουμε 0.
        print "Bye"
        return 1
        
    return 0


def showall():
    """
    Τυπώνει όλες τις εγγραφές του καταλόγου
    """
    count = 0
    print "------------------------------------"
    for person in mem:
        showperson(person)
        #print "%2d: %s %s --> %s" % (count, person["firstname"],person["lastname"], person["phone"])
        count += 1
    print "------------------------------------"

    
def lookup_and_print():
    """
    Ρωτάει το χρήστη για ένα string. Διανύει το mem και τυπώνει όσα λεξικά
    έχουν το string ως κομμάτι του firstname  του lastname.
    """
    text = raw_input("What are you looking for: ") # Το κείμενο που αναζητάμε
    count = 0
    for person in mem:
        if text.lower() in person["firstname"].lower() or text.lower() in person["lastname"].lower():
            showperson(person)
            count += 1
            
def insert():
    """
    Εισάγει ένα νέο άτομο.
    """
    print "Insert new person"
    fn = raw_input("Give first name: ") # Διαβάζει από το χρήστη να στοιχεία του νέου ατόμου
    ln = raw_input("Give last name: ") 
    pn = raw_input("Give phone number: ")
    if exists(fn, ln) >= 0: # Αν τέτοιο άτομο υπάρχει ήδη δεν το κρατάμε
        print "Person already in. Not added."
    else:
        mem.append( {"firstname": fn, "lastname": ln, "phone": pn} ) # Αλλιώς το προσθέτουμε στη μνήμη (λίστα mem)
    

def edit():
    """
    Αλλάζει τις τιμές σε ένα άτομο.
    """
    showall() # Πρώτα δείχνουμε όλα τα άτομα ώστε ο χρήστης να επιλέξει ποιο θέλει
              # να τροποποιήσει δίνοντας τον αριθμό του
    n = int(raw_input("Give the number of the person to edit: "))
    if n<0 or n>=len(mem):
        print "Bad number"
        return    
    person = mem[n]
    newfn = readstring(person["firstname"]) # Διαβάζουμε τις νέες τιμές του ατόμου
    newln = readstring(person["lastname"])  # δίνοντας ως προεπιλεγμένες τιμές τις ήδη υπάρχουσες
    newpn = readstring(person["phone"])
    if exists(newfn, newln) in [n, -1]: # Αν δεν υπάρχει αλλού το άτομο αυτό τότε κάνουμε την τροποποίηση
        person["firstname"] = newfn
        person["lastname"] = newln
        person["phone"] = newpn
    else:
        print "Person already in. Doing nothing."  # Το νέο όνομα που έδωσε ο χρήστης υπάρχει ήδη αλλού στο mem.
                                                   # Δεν καταχωρείται η τροποποίηση.

def readfromfile():
    """
    Διαβάζει στη μεταβλητή mem τα περιεχόμενα του αρχείου filename. Για τη μορφή που έχουν οι γραμμές του
    αρχείου αυτού δείτε την περιγραφή στη συνάρτηση writetofile().
    """
    global mem # Δηλώνουμε τη μεταβλητή mem ως καθολική μεταβλητή. Αν δεν το κάνουμε τότε επειδή η πρώτη φορά που
               # η μεταβλητή mem χρησιμοποιείται σε αυτή τη συνάρτηση είναι σε εκχώρηση τιμής σε αυτή (mem = []) η
               # python θα θεωρήσει ότι πρόκειται για τοπική μεταβλητή της συνάρτησης και ό,τι τιμή και αν πάρει αυτή μέσα
               # στη συνάρτηση δε θα επηρεάσει τη μεταβλητή mem του προγράμματος.
               # Σε άλλες συναρτήσεις δε χρειάζεται αυτή η δήλωση (αλλά δε θα έβλαπτε κιόλας) γιατί η πρώτη φορά που
               # αναφερόμαστε στη μεταβλητή mem σε αυτές είναι για να διαβάσουμε από αυτήν. Σε αυτή την περίπτωση η
               # python, αφού δεν τη βρίσκει ως τοπική μεταβλητή καταλαβαίνει (αφού το πρόγραμμα πάει να τη διαβάσει) ότι
               # πρόκειται για καθολική μεταβλητή.
    f = open(filename, "r") # Άνοιγμα του αρχείου για διάβασμα.
    mem = [] # Μηδενισμός της μνήμης
    for line in f.readlines(): # Για κάθε γραμμή του αρχείου
        ll = line.rstrip().split(',') # ll είναι μια λίστα με τις λέξεις που περιέχονται στη γραμμή διαχωρισμένες με κόμμα.
        fn = ll[0] # Μικρό όνομα.
        ln = ll[1] # Επώνυμο
        pn = ll[2] # Αριθμός τηλεφώνου
        d = {"firstname": fn, "lastname": ln, "phone": pn} # Φτιάχνουμε το λεξικό (άτομο) που αντιστοιχεί σε αυτή τη γραμμή
        if len(ll) == 4: # Αν υπάρχει και 4ο πεδίο στη γραμμή αυτό είναι το email του ατόμου
            email = ll[3]
            d["email"] = email # και το προσθέτουμε στο λεξικό
        mem.append(d) # Προσθέτουμε το λεξικό που φτιάξαμε στη λίστα mem.
    f.close() # Κλείνουμε το αρχείο.

def writetofile():
    """
    Γράφει τα περιεχόμενα της μνήμης (μεταβλητή mem) στο αρχείο filename.
    Χρησιμοποιούμε μια γραμμή κειμένου για κάθε άτομο, με τρία ή τέσσερα (αν έχει και email)
    πεδία διαχωρισμένα με κόμμα.
    """
    f = open(filename, "w") # Ανοίγουμε το αρχείο για γράψιμο
    for person in mem: # Για κάθε άτομο στη mem
        # Στη μεταβλητή s παρακάτω φτιάχνουμε το string που θα τυπώσουμε (με την f.write()) στο αρχείο.
        # Κατ'αρχήν γράφουμε την πληροφορία που σίγουρα υπάρχει και αν υπάρχει το email το προσθέτουμε κι αυτό.
        s = "%s,%s,%s" % (person["firstname"], person["lastname"], person["phone"])
        if "email" in person:
            s = s + "," + person["email"]
        f.write(s+"\n") # Γράφουμε το string s στο αρχείο. Προσθέτουμε και το '\n' για να αλλάξουμε γραμμή.
    f.close() # Κλείνουμε το αρχείο.
    
    
def delete():
    """
    Ρωτάει το χρήστη για το ποιο άτομο να σβήσει (αριθμό).
    """
    showall() # Δίχνουμε όλα τα άτομα ώστε να επιλέξει ο χρήστης ποιο θα σβήσει με τον αριθμό του
    n = int(raw_input("Give the number of the person to delete: "))
    if n<0 or n>=len(mem): # Αριθμός εκτός ορίων
        print "Bad number"
        return
    del mem[n] # Το σβήνουμε από το mem
    
# Κυρίως πρόγραμμα. Καλούμε συνεχώς τη συνάρτηση menu() μέχρι αυτή να επιστρέψει 1, το οποίο συμβαίνει
# μόνο όταν ο χρήστης επέλεξε q (Quit).
while True:
    ret = menu()
    if ret == 1:
        break
    raw_input("Hit enter to continue: ") # Αν λείψει αυτή η εντολή από δω τότε η menu() ξανακαλείται άμεσα
                                         # που έχει ως συνέπεια ότι καθαρίζεται η οθόνη και δε βλέπει ο χρήστης
                                         # τα αποτελέσματα από την προηγούμενη λειτουργία.

    s. Look up a person
    i. Insert a person
    e. Edit a person
    d. Delete a person
    l. Show all
    r: Read from file
    w: Write to file
    q: Quit
    

In []: