Den klassiska programmodellen Introduktion till programmering D0009E indata Hur kan data överleva en programkörning? utdata indata utdata Föreläsning 11: Filer och undantag Program A Program B Filsystem tid 1 2 Interaktiva program Filsystem 3 Hur kan data överleva en programkörning? indata utdata indata utdata Filsystem tid 4 Består av en mängd filer organiserade i en ofta hierarkisk struktur Varje fil har ett namn och ett innehåll (samt eventuella övriga detaljer såsom läs- och skrivrättigheter) Interna noder i filstrukturen kallas kataloger (directories) eller mappar (folders) ej att förväxla med Pythons inbyggda datatyp kataloger (dictionaries) En fils plats i filstrukturen bestäms av namnet. : /usr/share/dict/words betyder filen words i katalogen dict som ligger i katalogen share i katalogen usr på systemets toppnivå Filer Filer kan betraktas som böcker: De måste öppnas före de kan läsas eller skrivas De läses (eller skrivs) oftast från pärm till pärm, men det är också möjligt att hoppa omkring på andra vis Att blanda läsning med skrivning är mer komplicerat än att bara läsa (eller bara skriva) Allra sist ska de stängas Att öppna filen test.dat för skrivning: f = open("test.dat", "w") Denna operation skapar filen test.dat om den inte redan finns, i annat fall krymps den till storlek 0 Filobjekt Resultatet av en lyckad öppning är ett filobjekt en intern datastruktur som fungerar som "handtag" till den egentliga filen För att skriva data används filobjektets write-metod: f.write("now is the time") f.write("to close the file") Efter stängning blir filen tillgänglig för andra att läsa Att öppna filen test.dat för läsning: f = open("test.dat", "r") Denna fil måste finnas, annars sker ett runtime-fel 5 6 1
Filläsning Filkopiering Metoden read ger filens hela innehåll som en sträng: >>> text = f.read() >>> print text Now is the timeto close the file >>> Alternativ read där antalet önskade tecken också anges: >>> f = open("test.dat", "r") >>> print f.read(5) Now i >>> print f.read(1000006) s the timeto close the file >>> print f.read(), >>> Kopierar filen oldfile till filen newfile def copyfile(oldfile, newfile): f1 = open(oldfile, "r") f2 = open(newfile, "w") while True: text = f1.read(50) if text == "": break f2.write(text) f1.close() f2.close() OBS: ny sats break bryter närmast yttre snurra 7 8 Filtyper De flesta filer i Unix-baserade filsystem är textfiler, dvs innehållet är bitmönster som representerar text enligt samma kodning som Python använder för strängar Textfiler kan i sin tur ha olika struktur och syntax en fil kan vara Python programtext, en annan ett epostmeddelande, en tredje en webbsida i html, etc Filer som inte är textfiler är antingen rena maskinkodfiler (körbara program) eller s k binära filer enligt något annat specialiserat dataformat (exempelvis bilder i jpeg-format, filmer i mpeg-format). Binära filer kommer inte att behandlas ytterligare i denna kurs. Ofta (men inte alltid) avspeglas filtypen i filnamnet: Python-filer (.py), mpeg-filer (.mpeg), etc 9 10 Textfiler Underliggande struktur som går igen i alla textfiler (oberoende av format): en textfil är organiserad som en följd av rader En ny rad markeras av att ASCII-tecknet nyrad ('\n') ligger insprängt i texten Låt oss skapa en textfil med tre rader: f = open("test.dat", "w") f.write("line one\nline two\nline three\n") Vi förstår det som att varje rad slutar med tecknet '\n', utom möjligtvis filens sista rad Radläsning Radläsning 11 Filmetoden read() ger som sagt hela innehållet som en sträng. Ofta kan det vara bättre att läsa en rad i taget: >>> f = open("test.dat", "r") >>> print f.readline() line one >>> print f.readline() + f.readline() line two line three >>> print f.readline()=="" True 12 Metoden readlines() ger filens (kvarvarande) innehåll som en lista av rader: >>> f = open("test.dat", "r") >>> print f.readlines() ['line one\012', 'line two\012', 'line three\012'] >>> print f.readlines() [] Observera: print snyggar bara till sådana strängar som anges direkt som argument (tar bort ", skriver riktiga nyrad, etc). Strängar som ingår i sammansatta data (som listor) skrivs ut på samma sätt som de läses in i Python (Av historiska skäl skrivs \-koderna här med basen 8) 2
Filkopiering med filtrering Kopiera oldfile, men skippa rader som inleds med # def filterfile(oldfile, newfile): f1 = open(oldfile, "r") f2 = open(newfile, "w") while True: text = f1.readline() if text=="": break if text[0]=='#': continue f2.write(text) f1.close() f2.close() Ny sats: continue hoppar till nästa varv i en snurra Filkopiering med filtrering Alternativ formulering, nu med for-snurra: def filterfile(oldfile, newfile): f1 = open(oldfile, "r") f2 = open(newfile, "w") for text in f1: if text[0]!= '#': f2.write(text) f1.close() f2.close() Filer kan alltså också betraktas som sekvenser på samma sätt som listor, strängar och tupler. Elementen i en filsekvens är filens rader 13 14 15 Textformatering Att ha möjligheten att skriva text är gott och väl, men ska andra typer av värden sparas på textfiler måste de först konverteras till strängar Detta kan göras med den inbyggda konverteringsfunktionen str() kombinerat med konkatenering: num = 73 value = 3.14 f.write( "Row " + str(num) + ": = " + str(value) ) Python erbjuder dock ett mycket behändigt alternativ: operatorn % i en ny betydelse som formateringsoperation! (% betyder som bekant modulus när den appliceras på heltal) 16 Formateringsoperatorn % Operatorn % applicerad på en strängmall och en tupel med värden producerar en kopia av mallen där markerade "hål" har ersatts med värden från tupeln: 'abchål1defhål2gh...ijhålnklm' 'abcv1defv2gh...ijvnklm' Underförstått: v1, v2,..., vn konverteras till strängformat innan de sätts in på motsvarande plats i mallen % ( v1, v2,..., vn ) Hål Mer om formateringsoperatorn 17 En ny form av escape-sekvens (hanteras dock i run-time) %d heltalsvärde %f flyttalsvärde %s strängvärde (notera tecknet som möjliggör udda radbrytning): >>> 'In %d days we made %f million %s' \ % \ (34, 6.1, 'dollars') 'In 34 days we made 6.100000 million dollars' Som vi ser skrivs flyttal ut med 6 decimaler (default) 18 Felaktig användning: >>> '%d %d %d' % (1, 2) TypeError: not enough arguments for format string >>> '%d' % 'dollars' TypeError: int argument required Ok, om än lite förvirrande: >>> '%s' % 37 '37' Underförstådd betydelse av föregående exempel: >>> '%s' % str(37) '37' 3
Ytterligare kontroll av formatet Önskat minsta antal tecken (fyll ut med mellanslag): >>> '%6d' % 62 ' 62' >>> '%-6d' % 62 '62 ' >>> '%12f' % 6.1 ' 6.100000' Önskat antal decimaler för flyttal: >>> '%12.2f' % 6.1 ' 6.10' Funktion som skriver ut en lönerapport (given som ett dictionary) i en tabell med två kolumner: def report(wages, file): f = open(file, "w") people = wages.keys() people.sort() for x in people: f.write( '%-20s %12.2f' % (x, wages[x]) ) Notera: koden är egentligen inte beroende av att det är just namn och löner som skrivs ut alla dictionaries som mappar strängar till flyttal är ok! 19 20 Testkörning: wages = {'mary' : 10.23, 'joe' : 5.45, 'josh' : 4.25} report(wages, "test.dat") Genererat innehåll i filen test.dat: joe 5.45 josh 4.25 mary 10.23 Rapporten ser ok ut så länge namnen är kortare än 20 tecken och lönerna har färre än 9 siffror + 2 decimaler Alltså, formatsträngen '%-20s %12.2f' Sammansatta data Tyvärr fungerar formateringsoperatorn bara för de primitiva typerna, ej för sammansatta data som listor Här får i stället funktionen str() användas: f.write( str([1,2,3]) ) f.write( str({'x' : 2, 'y' : 3}) ) Ett intressant problem uppstår dock om vi vill kunna läsa in filen f vid ett senare tillfälle, och återskapa de sammansatta värdena. Betrakta den genererade texten: '[1,2,3]{'x': 2, 'y': 3}' Hur ska vi angripa en sådan sträng? Tecken för tecken? Hur vet vi var ett värde slutar och ett annat börjar? 21 22 23 Modulen pickle Lösning: om vi mest är intresserade av att kunna spara undan data på fil för att sedan kunna läsa in dem, och inte har några åsikter om hur den genererade texten egentligen ser ut, så erbjuder Python modulen pickle Pickle betyder (salt-)inläggning, och det är så vi ska se den sparade texten: som en inläggning av godtyckliga data för att "öka hållbarheten" mellan programkörningar Skriver en textkodning av värdet x på filen f: pickle.dump( x, f ) Avkodar nästa värde i filen f och returnerar det: x = pickle.load( f ) 24 Kod som lägger in data i filen test.dat: import pickle f = open("test.dat", "w") pickle.dump( [1,2,3], f ) pickle.dump( {'x': 2, 'y': 3}, f ) Kod (kanske i ett annat program) som läser in dessa data: import pickle f = open("test.dat", "r") list = pickle.load( f ) dict = pickle.load( f ) 4
25 Runtime-fel vi stött på Division med 0 >>> print 55/0 ZeroDivisionError: integer division or modulo Indexering utanför en lista: >>> a = [] >>> print a[5] IndexError: list index out of range Uppslagning av obefintlig nyckel i ett dictionary: >>> b = {} >>> print b['what'] KeyError: what 26 Runtime-fel vi stött på Försök att öppna en icke existerande fil: >>> f = open("i.dont.exist", "r") IOError: [Errno 2] No such file: 'I.dont.exist' Referens av odefinierad variabel: >>> print kalle NameError: name 'kalle' is not defined Anrop av funktion med fel antal argument: >>> def f(x,y): return x+y >>> print f(3) TypeError: f() takes exactly 2 arguments (1 given) 27 Undantag Alla dessa runtime-fel är exempel på s k undantag (exceptions), och de karaktäriseras av ett namn (TypeError, IndexError, etc) och en beskrivande text Normalt beteende då ett undantag uppstår: avsluta programkörningen skriv ut information om undantaget Det är dock möjligt att hantera undantag själv också, med hjälp av try-satsen: except namn: Kan upprepas för olika namn 28 Om hanterar undantaget själv programmet avslutas inte inget skrivs ut på skärmen Istället kör koden efter except namn är tänkt att ta hand om felet Hantera felet upplysa användaren göra nåt annat lämpligt Undantag except namn1: 1 except namn2: 2... except namn: 29 Hantering av det fall att en namngiven fil inte finns: filename = raw_input('enter a file name: ') f = open(filename, "r") except IOError: print "There is no file named", filename Variant på idén, nu som funktion: def exists(filename): f = open(filename) return True except IOError: return False 30 Att generera undantag Använd satsen raise namn, beskrivning: def inputnumber(): x = input('pick a number: ') if x == 17: raise ValueError, '17 is a bad number' return x Vid körning: >>> inputnumber() Pick a number: 17 ValueError: 17 is a bad number >>> 5
31 Att generera undantag En funktion som ger undantag returnerar inget värde! ger undantaget istället för värdet Undantag kan betraktas som ett sidospår i exekveringen när vi inte tycker att det går att returnera nåt vi slipper hitta på ett värde att returnera (t.ex. -1) Bra system för att hantera fel i program kasta undantag i funktion som gått fel fånga i funktionen som anropade inget returvärde 32 Att generera undantag Undantaget NotImplementedError är lämpligt att använda om man vill definiera halvfärdiga funktioner: def myfun(x, y): if x > y: return x-y else: raise NotImplementedError, 'myfun' Övriga fördefinierade undantag kan också användas i raise, och det är t o m möjligt att skapa egna namn Undantag genererade med raise kan naturligtvis också fångas med try (vanligt i större program) 6