Uke 11 - modules / import / standardbibliotek ============================================= Eksempler --------- I Python kan vi dele opp kode i flere ulike filer. Dette blir raskt nødvendig når man gjør større prosjekt. Filene kalles moduler og man kan importere kode i en modul fra en annen modul. Dette skal vi se på denne uken. Eksempel 1 .......... Vi kan gjenbruke funksjoner og variabler fra en annen fil med ``import``. Her er en fil med noe nyttig innhold (du kan laste ned filen her: :download:`eks_1_lib.py`). Sånne filer kalles modul/bibliotek: .. literalinclude:: eks_1_lib.py I hovedprogrammet kan vi gjenbruke filen på ulike måter: Vi kan ``import``-e hele filen med sitt navn, eller vi kan gi et nytt navn med ``import .. as``. Se hvordan vi bruker funksjoner fra filen. En annen mulighet er å plukke ut spesifikke funksjoner med ``from .. import ...``. Vi kan også bruke ``as`` her. (du kan laste ned hovedprogrammet her: :download:`eksempel_1.py`) .. literalinclude:: eksempel_1.py .. warning:: Pass på at begge filene ligger i samme mappen, og at terminalen kjører derfra. Ved bruk av import kan man strukturere store programmer på en oversiktlig måte, gjerne også i flere mapper. Det går utover pensum her, men mer informasjon finnes på https://docs.python.org/3/tutorial/modules.html .. note:: Noen ganger kan du se noe slikt som ``from module import *`` i Python kode. Dette importerer alle tingene fra modulen ``module`` med sine egne navn. Dette er ikke en rekommendert måte å importere. Om man importerer fra flere ulike moduler/bibliotek er det vanskelig å si hvor det man bruker i koden er definert om man vil se på den koden. Bruker man istedet ``from module1 import function1`` og ``from module2 import function2`` så ser man direkte at ``function1`` er definert i ``module1`` og ``function2`` er definert i ``module2``. Dessuten kan det hende at et navn på en funksjon i modulen du importerer (som du ikke ens bruker) klasjer med et navn i din kode. .. _uke_11_oppgave_1: Oppgave 1 ......... Lag et bibliotek ``uke_11_oppg_1.py`` i den samme mappen sånn at det følgende programmet fungerer: .. code-block:: python import uke_11_oppg_1 data = [3, 1, 7, -3, 5, 9, 1, 5, 9, 7, -3, 7] a = uke_11_oppg_1.mean(data) b = uke_11_oppg_1.median(data) c = uke_11_oppg_1.mode(data) print(a, b, c) Mer spesifikasjon: * Du får ikke bruke de eksisterende funksjonene ``mean``, ``median`` og ``mode`` fra Python. * Alle funksjonene skal ta én liste som argument. * Funksjonen ``mean(xs)`` skal returnere gjennomsnittet av tallene i listen. Gjennomsnittet av tallene :math:`x_1, x_2, ..., x_n` beregnes med formelen: .. math:: \frac{x_1 + x_2 + ... + x_n}{n}. For eksempel: .. code-block:: python uke_11_oppg_1.mean([2, 5, 3, 1]) # skal returnere 2.75 * Funksjonen ``median(xs)`` skal returnere medianen av tallene i listen. Dette er tallet som er i mitten etter att listen er blitt sortert. Om lengden av listen er jevn er medianen gjennomsnittet av de to tallene i mitten. For eksempel: .. code-block:: python uke_11_oppg_1.median([4, 12, 3, 9, 5]) # skal returnere 5 uke_11_oppg_1.median([3, 6, 93, 45, 14, 22]) # skal returnere 18 * Funksjonen ``mode(xs)`` skal returnere typetallet: det vanligste tallet i listen. Om flere tall er like vanlige skal funksjonen returnere det tallet som står først i listen. For eksempel: .. code-block:: python uke_11_oppg_1.mode([3, 4, 22, 7, 4, 15, 4, 7, 1]) # skal returnere 4 uke_11_oppg_1.mode([3, 22, 7, 4, 15, 4, 7, 1, 4, 7, 22, 15, 22]) # skal returnere 22 Eksempel 2 .......... Når du skriver Pythonkode i en fil, finnes det alltid en (skjult) variabel som heter ``__name__`` (her er det **to** understrekk "_" på hver side). Verdien til ``__name__`` er ``"__main__"`` hvis filen kjøres direkte fra terminalen. F.eks hvis man kjører ``eksempel_2.py`` fra terminalen slik: ``python eksempel_2.py``, så vil variabelen ``__name__`` ha verdien ``"__main__"``. Hvis ``eksempel_2.py`` importeres fra en annen Python fil, det vil si den blir ikke direkte kjørt fra terminalen, så er variabelen ``__name__`` satt til ``"eksempel_2"``, altså navnet på modulen. Noen ganger vil vi kjøre noe kode i en fil KUN hvis filen blir kjørt direkte, men ikke hvis den blir importert av en annen modul / Python fil. For å gjøre dette skriver vi ``if name == "__main__":``, og putter koden som skal kjøre hvis vi kjører filen direkte fra terminalen innenfor denne if-clause. Last ned eksempel_2.py og kjør filen fra terminalen. Skriver funksjonen ``hello`` ut? Hva er verdien på ``__name__``? .. literalinclude:: eksempel_2.py Nå skal vi se hva ``__name__`` er hvis vi kjører ``python`` rett fra terminalen. Åpne en terminal i samme mappe som filen ``eksempel_2.py``, og skriv ``python3``. - Skriv så ``import eksempel_2``. Hva skjer nå? - Hva er verdien på ``__name__``? - Skriver funksjonen ``hello`` ut denne gangen? Standardbiblioteker ................... Mange nyttige biblioteker er allerede med i hver Python-installasjon, det er "Python Standard Library". Vi skal vise noen eksempler av nyttige moduler her. En full oversikt finnes på Pythons hovedsiden: https://docs.python.org/3/library/ Når man kommer til å jobbe mer med Python er det lurt å bli kjent med oversikten her. Man trenger ikke å være ekspert i alle modulene, men man må ha en idé hva slags moduler det finnes. Moduler som ``sys`` eller ``os`` er brukt i nesten alle prosjektene, mens biblioteker som f.eks ``sunau`` (Read and write Sun AU audio files) har en veldig spesialisert målgruppe. Dokumentasjonen for hvert bibliotek er veldig bra, med mange eksempler. Det finnes alltid noe nyttig når man leser gjennom. Du trenger ikke pugge modulene og bibliotekene vi skal diskutere denne uken. Bruk Python dokumentasjonen som en referanse. .. note:: Det kan ta litt tid til å bli vant med å lese dokumentasjon, men det er veldig bra å kunne. Et tips når du vil lese dokumentasjon er å først se på eksemplene og prøve å finne et eksempel som tilsvarer det du vil gjøre. Etter det leser du dokumentasjonen for de tingene som var med i eksemplet/eksemplene. Eksempel 3 .......... Pythons bibliotek ``math`` inneholder mange vanlige matematiske funksjoner og konstanter. Du finner dokumentasjonen for biblioteket på https://docs.python.org/3/library/math.html. Her er noen eksempler på bruk av ``math`` (du kan laste ned koden her: :download:`eksempel_3.py`): .. literalinclude:: eksempel_3.py Det finnes også andre bibliotek for å gjøre beregninger. `Her er en liste på numeriske biblioteker `_. Se på noen av dem og lag dine egne eksempler. .. .. fractions?? .. https://docs.python.org/3/library/fractions.html Eksempel 4 .......... Om man trenger en kalender eller må beregne tidspunkt, er det best å bruke en ferdig bibliotek. I Python er det `datetime `_. Les gjennom siden og se på eksemplene der. Filen :download:`eksempel_4.py` viser noen enkle bruk av biblioteken: .. literalinclude:: eksempel_4.py Prøv å legge inn noe du fant i dokumentasjonen. .. _uke_11_oppgave_2: Oppgave 2 ......... Følgende tekst er fra `Project Euler nr.19 `_: "How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)?" Vi skal svare på dette spørsmålet for **fredager** som føll på den **2.** i måneden. I filen ``uke_11_oppg_2.py`` skriv kode som bruker ``datetime``-biblioteket til å beregne og så skrive ut på skjermen hvor mange **fredager** som føll på den **2.** i maneden fra 1. januar 1901 til 31 desember 2000. .. note:: Her igjen finner du dokumentasjonen for `datetime.date `_ Eksempel 5 .......... Når vi vil telle antall kan vi bruke klassen ``Counter`` i biblioteket ``collections``, som er en spesiell versjon av dict(), hvor hvert element begynner med 0 allerede. Dokumentasjonen finner du her: `collections `_. Vi prøver `igjen <../uke_09/#eksempel-4>`_ å telle antall ord i en tekst, men denne gangen bruker vi ``Counter``. Du kan laste ned koden her: :download:`eksempel_5.py`. .. literalinclude:: eksempel_5.py Eksempel 6 .......... `itertools `_-biblioteken gjør det enklere å skrive ulike typer løkke. Filen :download:`eksempel_6.py` viser et tilfelle, men det finnes mange eksempler i dokumentasjonen. Gjerne prøv noen av dem. .. literalinclude:: eksempel_6.py .. _uke_11_oppgave_3: Oppgave 3 ......... Anta at du underviser i et kurs der hver av studentene dine får tildelt en anonym ID som starter på 1 og går opp til antall studenter som er påmeldt kurset. For eksempel vil ID-ene til et kurs med 20 studenter være: .. code-block:: python id_col = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] Du angir om en student har bestått eller ikke bestått emnet med ``0`` for ikke bestått og ``1`` for bestått. For et emne med 20 studenter vil denne datakolonnen se slik ut: .. code-block:: python pass_fail_col = [0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0] Du avslutter semesteret og må levere en liste til administrasjonen over hvem som kvalifiserer til å ta eksamen. I filen ``uke_11_oppg_3.py``, skriv en funksjon som heter ``select_pass(id_col, pass_fail_col)`` som ta som input to lister, en av student-IDer og en av studenters bestått/ikke bestått resultater. Funksjonen skal returneres IDer i en liste av bestått studentene. Funksjonen skal fungere med ulike kursstørrelser. Denne oppgaven er lett, men ideen er å søk i dokumentasjonen og bruke en funksjon fra `itertools `__ Eksempelkjøring: .. code-block:: python >>> select_pass(id_col,pass_fail_col) [5, 6, 8, 12, 15] Eksempel 7a ........... Du kan bruke biblioteket ``random`` til å generere tilfeldige tall. Dokumentasjonen for dette biblioteket finner du `her `_. Du kan laste ned koden her: :download:`eksempel_7a.py`. Prøv å kjør koden noen ganger **uten** ``random.seed(123457)`` og noen ganger med ``random.seed(123457)``. Hva er forskjellen? .. literalinclude:: eksempel_7a.py Eksempel 7b ........... Vi kan bruke ``random`` til å se om vår egen implementasjon av en funksjon som beregner standardavvikelse fungerer som den skall. Du kan laste ned koden her: :download:`eksempel_7b.py`. Prøv å kjør koden noen ganger med ulike verdier. Ser ``standard_deviation()`` ut til å fungere som den skal? .. literalinclude:: eksempel_7b.py Eksempel 7c ........... Vi kan også bruke ``random`` for simulering og analyse. La oss demonstrere dette med ``Python-turtle`` og tilfeldige turer Anta for eksempel at skilpadden tar 100 skritt på en tilfeldig bane fra startposisjonen i midten av lerretet. Skilpadden utfører totalt fem av disse tilfeldige turene. .. image:: ./turtle-paths.png :width: 500 :alt: Random walk paths of 5 different turtles, with a random change of direction every 10 steps. Du kan deretter analysere resultatene av denne serien med tilfeldige turer, for eksempel beregne i gjennomsnitt hvor langt skilpadden har reist fra startposisjonen. Dette resultatet samsvarer med et velkjent matematisk-fenomen - vet du hva det er? .. .. (distance covered is sq root of number of steps done multiplied by the step size) Du kan laste ned koden her: :download:`eksempel_7c.py`. .. literalinclude:: eksempel_7c.py .. _uke_11_oppgave_4: Oppgave 4 ......... Vi skal regne ut verdien til :math:`\pi` ved å simulere mange tilfeldige (x, y) punkter mellom 0 og 1. Så teller vi opp hvor mange av punktene ligger inni kvartsirkelen med radius 1 sammenlignet med antallet punkter i alt: .. image:: ./circle_quarter_100.png :width: 400 :alt: 100 Random points plotted with a quarter area of a circle plotted Denne andelen tilsvarer forholdet mellom arealene til kvartsirkelen og firkantet: .. math:: \frac{N_{\mathrm{inside}}}{N_{\mathrm{all}}} \approx \frac{N_{\bigcirc}}{N_{\square}} I vårt eksempel fra 0 til 1 er areal til kvartsirkelen :math:`\frac{1}{4}\pi` og arealen til kvadratet er 1. .. math:: \frac{N_{\mathrm{inside}}}{N_{\mathrm{all}}} \approx \frac{1}{4}\pi eller, med :math:`\pi` på venstre siden, .. math:: \pi \approx 4\frac{N_{\mathrm{inside}}}{N_{\mathrm{all}}} Jo flere punkter vi simulerer, jo presisere blir verdien til :math:`\pi`: .. image:: ./circle_quarter_1000.png :width: 400 :alt: 1000 Random points plotted with a quarter area of a circle plotted I filen ``uke_11_oppg_4.py``, skriv en funksjon med navn ``find_pi(N)`` som tar som argument antallet punkter ``N`` som skal simuleres. Bruk så ``random``-biblioteket for å lage ``N`` x- og y-verdier. En punkt ligger inni sirkelen når ``x**2 + y**2 <= 1``. Funksjonen skal returnere antallet punkter inni sirkelen dividert med antallet i alt, dvs. ``N``. Prøv funksjonen din med ``N = 1000`` og ``N = 10000`` punkter også. Når ``N`` blir større, kommer da verdien som du beregner nærmere til :math:`\pi` ? Eksempel 8 .......... Biblioteken `fractions `_ og `decimal `_ kan brukes til beregninger om man vil undvike avrundingsfeil som man får om man bruker float. For å lage en desimal skriver du for eksempel ``Decimal("0.2")``. For å lage en brøkdel skriver du for eksempel ``Fraction(10, 5)``. Det første argumentet er teller og det andre argumentet er nevner. ``Fraction`` kan gjøre forenklinger akkurat som vi gjør på papir når vi regner med brøkdeler. Vi kan også bruke ``Fraction`` til å finne den nermeste ratsjonelle approsimasjonen av et irratsjonellt tall med en øvre grens på nevneren. .. literalinclude:: eksempel_8.py .. _uke_11_oppgave_5: Oppgave 5 ......... I denne oppgaven skal du bruke biblioteket `decimals `_ til å skrive et program som produserer en kvittering fra en butikk. Last ned :download:`uke_11_oppg_5.py` og spar den på datamaskinen din (med samme navn). Filen ser slik ut: .. literalinclude:: uke_11_oppg_5.py Du skal skrive koden til funksjonen ``receipt_content()`` som beregner inneholdet i kvitteringen utifra en fil med butikkens priser og en fil med hendelsene ved kassen. Argumentet ``prices_filename`` er en streng med navnet til filen som inneholder prisene. Argumentet ``cash_register_filename`` er en streng med navnet til filen som inneholder hendelsene ved kassen. Funksjonen ``receipt_content()`` skal returnere en tupel som inneholder følgende (i denne orden): 1. En liste med tupler som inneholder (i følgende orden) antall, produkt og totalt pris, først for alle ting som har blitt kjøpt, i alfabetisk orden, og så for alle ting som har blitt returnert, i alfabetisk orden (se eksempelkvittering nedenfor). For de returnerte produktene blir det negative tall. 2. Det totale priset. 3. Hvor mye av det totale priset som er mva. 4. Hvor mye som har blitt betalt. 5. Hvor mye som blir betalt tilbake (her blir det ikke-positive tall). Filen med butikkens priser er formattert slik som eksempelfilen :download:`prices.txt` (som du kan bruke til å teste programmet ditt): .. literalinclude:: prices.txt :language: text Først står produkt, så ";", og så priset. Filen med hendelsene ved kassen er formattert slik som eksempelfilen :download:`cash_register.txt` (som du kan bruke til å teste programmet ditt): .. literalinclude:: cash_register.txt :language: text Først står hva som skjer (kjøp, retur eller betaling), så ";", og så produkten/verdien. Funksjonen ``receipt()`` skal du ikke endre. Den bruker ``receipt_content()`` til å beregne inneholdet i kvitteringen og så produserer ``receipt()`` en pen kvittering utifra det innholdet. Du skal bare skrive kode til ``receipt_content()``. Om din kode til ``receipt_content()`` er riktig så skal du for eksempel få følgende streng når du bruker ``receipt("prices.txt", "cash_register.txt")``: .. code-block:: text Nr. Item Price ------------------------------------ 2 apple 10.00 1 chips 24.30 1 dish soap 26.20 1 frozen pizza 54.40 1 peanuts 18.50 1 toilet paper 34.00 3 tomato 30.00 -1 pocket book -149.00 -1 toothpaste -13.70 ------------------------------------ Total: 34.70 Of which VAT: 6.94 ------------------------------------ Payment: 100.00 Change -65.30 **Din kode skal inneholde to egendefinerte exceptions:** 1. Om priset til en produkt som kjøpes/returneres ikke finnes i prislisten skal koden produsere (``raise``) en egendefinert Exception som heter ``NoPrice`` (uten melding). (Denne skal ikke fanges av programmet uten programmet skal krasje med denne feiltypen.) 2. Om kunden har ikke betalt nok skal koden produsere (``raise``) en egendefinert Exception som heter ``NotEnoughPaid`` (uten melding). (Denne skal ikke fanges av programmet uten programmet skal krasje med denne feiltypen.) Mer spesifikasjon og tips: * Du må bruke biblioteket `decimals `_ for at beregningene skal bli riktige (prøv hvordan kvitteringen blir om du bruker ``float``). * Det er mulig at betaling skjer flere ganger underveis ved kassen. * Du kan anta at alle betalinger ved kassen har positiv verdi. * Du kan anta at det ikke er noen feil i filene som inneholder prisene og hendelsene ved kassen. * Det er mulig at det totale priset blir negativt (om kunden for eksempel bare returnerer ting). * Gitt et pris inklusive mva så multipliserer du priset med :math:`0.2` for å finne ut hvor mye av priset som er mva. * Det kan være lurt å bruke dictionaries til prislisten og det som blitt kjøpt og det som blitt returnert. * Du må ikke skrive all kode innen funksjonen ``receipt_content()``. Du kan dele opp koden i flere funksjoner på en måte du synes føles naturlig. Oppgaver -------- .. note:: Testene tilgjengelig her: :download:`uke_11_tests.zip` for oppgavene 1, 3 og 4. Oppgave 1 ......... Du finner :ref:`uke_11_oppgave_1` nedenfor Eksempel 1. Oppgave 2 ......... Du finner :ref:`uke_11_oppgave_2` nedenfor Eksempel 4. Oppgave 3 ......... Du finner :ref:`uke_11_oppgave_3` nedenfor Eksempel 6. Oppgave 4 ......... Du finner :ref:`uke_11_oppgave_4` nedenfor Eksempel 7. Oppgave 5 ......... Du finner :ref:`uke_11_oppgave_5` nedenfor Eksempel 8.