L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E Den klassiska programmodellen Introduktion till programmering D0009E Hur kan data överleva en programkörning? indata Föreläsning 11: “Filer och undantag” utdata indata Program A utdata Program B Filsystem tid 1 2 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K Interaktiva program Filsystem Består av en mängd filer organiserade i en ofta hierarkisk struktur Hur kan data överleva en programkörning? indata utdata indata INTRODUKTION TILL PROGRAMMERING D0009E utdata 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) Filsystem En fils plats i filstrukturen bestäms av namnet. Exempel: /usr/share/dict/words betyder filen words i katalogen dict som ligger i katalogen share i katalogen usr på systemets toppnivå tid 3 4 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K Filer Filobjekt 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 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 f.close() Att öppna filen test.dat för skrivning: f = open("test.dat", "w") Att öppna filen test.dat för läsning: f = open("test.dat", "r") Denna operation skapar filen test.dat om den inte redan finns, i annat fall krymps den till storlek 0 5 INTRODUKTION TILL PROGRAMMERING D0009E Denna fil måste finnas, annars sker ett runtime-fel 6 1 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K Filläsning INTRODUKTION TILL PROGRAMMERING D0009E Filkopiering Metoden read ger filens hela innehåll som en sträng: >>> text = f.read() >>> print text Now is the timeto close the file >>> f.close() 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(), >>> 7 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 8 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K Filtyper INTRODUKTION TILL PROGRAMMERING D0009E Textfiler 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 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") f.close() Vi förstår det som att varje rad slutar med tecknet '\n', utom möjligtvis filens sista rad 10 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E Radläsning Radläsning 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 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() [] >>> print f.readline() + f.readline() line two line three >>> print f.readline()=="" True 11 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) 12 2 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K 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 13 INTRODUKTION TILL PROGRAMMERING D0009E 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 14 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E Textformatering Formateringsoperatorn % 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) 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: 15 '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 16 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K Hål En ny form av escape-sekvens (hanteras dock i run-time) • %d heltalsvärde • %f flyttalsvärde • %s strängvärde Exempel (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) 17 ( v1, v2, ..., vN ) INTRODUKTION TILL PROGRAMMERING D0009E Mer om formateringsoperatorn 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' 18 3 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E Ytterligare kontroll av formatet Exempel Önskat minsta antal tecken (fyll ut med mellanslag): >>> '%6d' % 62 ' 62' >>> '%-6d' % 62 '62 ' >>> '%12f' % 6.1 ' 6.100000' 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]) ) f.close() Önskat antal decimaler för flyttal: >>> '%12.2f' % 6.1 ' 6.10' 19 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! 20 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K Exempel Sammansatta data 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' 21 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? 22 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K 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 ) 23 INTRODUKTION TILL PROGRAMMERING D0009E INTRODUKTION TILL PROGRAMMERING D0009E Exempel 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 ) f.close() 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 ) f.close() 24 4 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K Runtime-fel vi stött på INTRODUKTION TILL PROGRAMMERING D0009E Runtime-fel vi stött på Division med 0 >>> print 55/0 ZeroDivisionError: integer division or modulo Försök att öppna en icke existerande fil: >>> f = open("I.dont.exist", "r") IOError: [Errno 2] No such file: 'I.dont.exist' Indexering utanför en lista: >>> a = [] >>> print a[5] IndexError: list index out of range Referens av odefinierad variabel: >>> print kalle NameError: name 'kalle' is not defined Uppslagning av obefintlig nyckel i ett dictionary: >>> b = {} >>> print b['what'] KeyError: what 25 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) 26 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K Undantag 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 Om hanterar undantaget själv Normalt beteende då ett undantag uppstår: •avsluta programkörningen •skriv ut information om undantaget • Istället Det är dock möjligt att hantera undantag själv också, med hjälp av try-satsen: try: satslista except namn: Kan upprepas för olika namn satslista 27 • programmet avslutas inte • inget skrivs ut på skärmen • kör koden efter except namn • är tänkt att ”ta hand” om felet try: satslista except namn1: satslista1 except namn2: satslista2 . . . except namn: satslista • Hantera felet • upplysa användaren • göra nåt annat lämpligt 28 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K Exempel Hantering av det fall att en namngiven fil inte finns: filename = raw_input('Enter a file name: ') try: f = open(filename, "r") except IOError: print "There is no file named", filename Variant på idén, nu som funktion: def exists(filename): try: f = open(filename) f.close() return True except IOError: return False 29 INTRODUKTION TILL PROGRAMMERING D0009E INTRODUKTION TILL PROGRAMMERING D0009E 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 >>> 30 5 L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K INTRODUKTION TILL PROGRAMMERING D0009E L U L E Å T E K N I S K A U N I V E R S I T ET SY S T E M T E K N I K 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 31 INTRODUKTION TILL PROGRAMMERING D0009E 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) 32 6