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: eks_1_lib.py). Sånne filer kalles modul/bibliotek:

# Vi kan definere funksjoner...

def fib(n):
    """Return the fibonacci sequence up to n as a list"""
    a, b = 0, 1
    seq = []
    while a < n:
        seq.append(a)
        a, b = b, a+b
    return seq

        
def collatz(n):
    """Return the collatz sequence from n as a list"""
    def collatz_step(n):
        if n % 2 == 0:
            return n//2
        else:
            return 3*n + 1
    seq = [n]
    while n > 1:
        n = collatz_step(n)
        seq.append(n)
    return seq

# ...eller verdier i en bibliotek

# speed of light [m/s]
C_LIGHT = 299792458

# Planck constant [Js]
H_PLANCK = 6.62607015e-34

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: eksempel_1.py)

print('Option 1')
import eks_1_lib #import module

fib_1 = eks_1_lib.fib(100)
col_1 = eks_1_lib.collatz(17)
print(fib_1)
print(col_1)

#################################

print('Option 2')
import eks_1_lib as el

print(el.collatz(5))
c = el.C_LIGHT
print(c,'m/s')

#############################

print('Option 3')
from eks_1_lib import fib

fib_2 = fib(75)
print(fib_2)

##############################

print('Option 4')
from eks_1_lib import H_PLANCK as h, C_LIGHT as c, fib
print(c)
print(h)
print(fib(30))

Advarsel

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

Obs

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.

Oppgave 1

Lag et bibliotek uke_11_oppg_1.py i den samme mappen sånn at det følgende programmet fungerer:

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 \(x_1, x_2, ..., x_n\) beregnes med formelen:

    \[\frac{x_1 + x_2 + ... + x_n}{n}.\]

    For eksempel:

    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:

    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:

    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__?

# if __name__ == "__main__" example

print('This is the top level, or 0 indentation level, of eksempel_2.py')

def hello():
    print('hey there')


if __name__ == "__main__":

    print('eksempel_2.py is being run directly')
    print(f'{__name__} is the value of __name__') #what prints here? 

else:
    print('eksempel_2.py is being imported')
    print(f'{__name__} is the value of __name__')  # what prints here?
    hello() # we only call function if file is imported 

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.

Obs

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: eksempel_3.py):

import math

# The floor and ceiling functions
print(f"{4 / 3}")
print(f"{math.floor(4 / 3)}")
print(f"{math.ceil(4 / 3)}")

print()

# The constant e and powers of it
print(f"{math.e}")
print(f"{math.exp(1)}")
print(f"{math.exp(0)}")
print(f"{math.exp(2)}")

print()

# Logarithms
print(f"{math.log(math.e)}")
print(f"{math.log(1)}")
print(f"{math.log(5)}")

print()

print(f"{math.log2(2)}")
print(f"{math.log2(1)}")
print(f"{math.log2(5)}")
print(f"{math.log2(8)}")

print()

print(f"{math.log10(10)}")
print(f"{math.log10(1)}")
print(f"{math.log10(5)}")
print(f"{math.log10(100)}")

print()

# Exponentials
print(f"{math.pow(2, 3)}")
print(f"{math.pow(2, math.log2(3.3))}")
print(f"{math.pow(4, 1 / 2)}")
print(f"{math.sqrt(4)}")
print(f"{math.sqrt(math.pow(3, 4))}")
# print(f"{math.sqrt(-1)}")  # This raises an error
# Use the library 'cmath' for complex numbers

print()

from math import pi

print(f"{pi}")
# The trigonometric functions take input in radians, not degrees
# What should all these values be?
print(f"{math.sin(pi / 2)}")
print(f"{math.sin(pi)}")
print(f"{math.cos(pi / 2)}")
print(f"{math.cos(pi)}")
print(f"{math.tan(pi)}")
print(f"{math.tan(pi / 2)}")

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.

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 eksempel_4.py viser noen enkle bruk av biblioteken:

from datetime import date

today = date.today()
my_birthday = date(today.year, 6, 24)

if my_birthday < today:
    my_birthday = my_birthday.replace(year=today.year + 1)

time_to_birthday = abs(my_birthday - today)
print(time_to_birthday)


from datetime import datetime

now = datetime.now()
exam = datetime(2022, 5, 27, 9, 0, 0)
print(exam - now)

Prøv å legge inn noe du fant i dokumentasjonen.

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.

Obs

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 å telle antall ord i en tekst, men denne gangen bruker vi Counter. Du kan laste ned koden her: eksempel_5.py.

from collections import Counter

text = """Alice was beginning to get very tired of sitting by her sister
            on the bank, and of having nothing to do: once or twice she had peeped
            into the book her sister was reading, but it had no pictures
            or conversations in it, 'and what is the use
            of a book,' thought Alice 'without pictures or conversation?'"""

letter_count = Counter() # instead of dict() or {}

# Count letters
for let in text:
    let = let.lower()
    if let in "abcdefghijklmnopqrstuvwxyz":
        letter_count[let] += 1
# With Counter we don't have to first create a key for every letter

for let, count in letter_count.items():
    print(f"{let} is used {count:3d} times")

print("\n\n\n")


word_count = Counter()

for word in text.split():
    word = word.lower()
    word_count[word] += 1
# With Counter we don't have to first create a key for every word

# The 5 most used words, Counter has a useful helper function most_common()
# we don't need to sort ourselves
print("The 5 most common words")
print(word_count.most_common(10))
for w, c in word_count.most_common(5):
    print(f"{w:14} is used {c:3d} times")

Eksempel 6

itertools-biblioteken gjør det enklere å skrive ulike typer løkke. Filen eksempel_6.py viser et tilfelle, men det finnes mange eksempler i dokumentasjonen. Gjerne prøv noen av dem.

import itertools

print("All DNA triples")

bases = "ATGC"
# for i in bases:
#     for j in bases:
#         for k in bases:
#             print(i+j+k)

# the below two lines do the same as the above commented lines
for i, j, k in itertools.product(bases, repeat=3):
    print(i + j + k)

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:

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:

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:

>>> 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: eksempel_7a.py. Prøv å kjør koden noen ganger uten random.seed(123457) og noen ganger med random.seed(123457). Hva er forskjellen?

import random

# random.seed(123456) # reproduserbare resultater, ulik for hver int

# randint() simulates a random integer
print(f"A random dice throw: {random.randint(1,6)}")  #  1 <= N <= 6
print()

foods = ["pancakes", "soup", "stir fry", "lasagne"]
# choice() simulates a random choice among a collection of things
print(f"Today's lunch is: {random.choice(foods)}")
# choices() simulates a list of choices
print(f"This week's lunches are: {random.choices(foods, k=7)}")
print()

people = ["Person A", "Person B", "Person C", "Person D", "Person E"]
# sample() simulates a random sample among a collection of things
print(f"The people selected for the trial are: {random.sample(people, k=3)}")
print()


cards = []
for i in range(1, 14):
    cards += [("hearts", i), ("clubs", i), ("spades", i), ("diamonds", i)]

print(f"Unshuffled card deck: {cards[:6]}...")
print()
# shuffle() simulates a random permutation
random.shuffle(cards)
print(f"Shuffled card deck: {cards[:6]}...")
print()
print(f"Player 1 takes the first 3 cards: {cards[:3]}")
print()

# random() without arguments simulates a uniform distribution
# over the interval [0.0, 1.0)
print(f"A random number in the interval [0.0, 1.0): {random.random()}")
print()

# uniform() simulates a uniform distribution over the given interval
print(
    f"A random number in the interval [12, 20]: {random.uniform(12.0, 20.0)}"
)  #  12.0 <= N <= 20.0

# gauss() simulates a normal distribution with the given
# the mean and standard deviation
mu = 40
sigma = 12
print(
    f"A random number from a normal distribution with mean={mu}"
    + f" and standard deviation={sigma}: {random.gauss(mu, sigma)}"
)

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: eksempel_7b.py. Prøv å kjør koden noen ganger med ulike verdier. Ser standard_deviation() ut til å fungere som den skal?

from random import gauss
from math import sqrt
from statistics import mean


# we define our own standard deviation function
def standard_deviation(data):
    data_mean = mean(data)
    s = sum([(x - data_mean) ** 2 for x in data])
    return sqrt(s / (len(data) - 1))


if __name__ == "__main__":
    # we check that it behaves as it should
    sigma = 12  # try with different values
    data = [gauss(40, sigma) for _ in range(20000)]
    print(f"{standard_deviation(data) = }")
    print(f"Expected value: {sigma}")

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.

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?

Du kan laste ned koden her: eksempel_7c.py.

import turtle as t
import random
import math
from statistics import mean

t.setup(1000, 1000)  # set canvas size
t.hideturtle()

steps = 100
step_size = 10
distances = []

# create 5 turtles
a = t.Turtle()
b = t.Turtle()
c = t.Turtle()
d = t.Turtle()
e = t.Turtle()

# assign different color to each turtle
a.color("red")
b.color("green")
c.color("blue")
d.color("orange")
e.color("black")

turtles = [a, b, c, d, e]

for tu in turtles:
    # instructions for walk path
    tu.goto(0, 0)
    tu.speed(0)
    tu.down()
    for _ in range(steps):
        direction = random.choice([0, 90, 180, 270]) # grid walk
        # direction = random.uniform(0,360) # any direction allowed
        tu.setheading(direction) 
        tu.forward(step_size)

    tu.up()

    # distance traveled is the square root of the sum of squared change in x and squared change in y
    xdist = tu.xcor()
    ydist = tu.ycor()
    d = math.sqrt(xdist**2 + ydist**2)
    distances.append(d)

print(distances)
print(f"Direct way to calculate average distance yields {mean(distances):.2f}")

# check against theory (holds true for many repetitions of random walk, fewer repetitions is less accurate)
avg_distance = step_size * math.sqrt(steps)
print(f"Theoretical calc for avg distance is {avg_distance:.2f}")

t.done()

Oppgave 4

Vi skal regne ut verdien til \(\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:

100 Random points plotted with a quarter area of a circle plotted

Denne andelen tilsvarer forholdet mellom arealene til kvartsirkelen og firkantet:

\[\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 \(\frac{1}{4}\pi\) og arealen til kvadratet er 1.

\[\frac{N_{\mathrm{inside}}}{N_{\mathrm{all}}} \approx \frac{1}{4}\pi\]

eller, med \(\pi\) på venstre siden,

\[\pi \approx 4\frac{N_{\mathrm{inside}}}{N_{\mathrm{all}}}\]

Jo flere punkter vi simulerer, jo presisere blir verdien til \(\pi\):

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 \(\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.

from decimal import Decimal
from fractions import Fraction
from math import pi

# using Decimal avoids rounding errors
print(f"Using floats: {0.1 + 0.2 = }")
print(f"Using Decimal: 0.1 + 0.2 = {Decimal('0.1') + Decimal('0.2')}")
print()

print(f"Using floats: {(1/14) * (7/3) = }")
print(f"Using Fraction: (1/14) * (7/3) = {Fraction(1, 14) * Fraction(7, 3)}")
print()

# we can use the fractions library to find the closest fraction to a given number
print(
    "The closest fraction to π with denominator at most 1000 is:",
    str(Fraction(pi).limit_denominator(1000)),
)

Oppgave 5

I denne oppgaven skal du bruke biblioteket decimals til å skrive et program som produserer en kvittering fra en butikk.

Last ned uke_11_oppg_5.py og spar den på datamaskinen din (med samme navn). Filen ser slik ut:

def receipt_content(prices_filename, cash_register_filename):
    """Construct contents of a receipt of the cash register events,
    given the store prices."""

    # din kode her


def receipt(prices_filename, cash_register_filename):
    """Construct a receipt of the cash register events,
    given the store prices."""

    # get receipt content from receipt_content()
    purcases_returns, total, vat, payment, change = receipt_content(
        prices_filename, cash_register_filename
    )

    # the formatted lines of the receipt
    receipt_lines = [f"{'Nr.':>4}  {'Item':18}  {'Price':>10}"]

    for nr, item, price in purcases_returns:
        receipt_lines.append(f"{nr:4d}  {item:18}  {price:10.2f}")

    receipt_lines.append(f"Total: {total}")
    receipt_lines.append(f"Of which VAT: {vat:.2f}")
    receipt_lines.append(f"Payment: {payment}")
    receipt_lines.append(f"Change {change}")

    # add some dividers
    max_len = max(len(line) for line in receipt_lines)
    divider = "-" * max_len
    receipt_lines.insert(1, divider)
    receipt_lines.insert(-4, divider)
    receipt_lines.insert(-2, divider)

    return "\n".join(receipt_lines)

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 prices.txt (som du kan bruke til å teste programmet ditt):

banana;7.40
dish soap;26.20
toilet paper;34.00
apple;5.00
frozen pizza;54.40
stamp;3.00
bread;22.20
ice cream;35.10
toothpaste;13.70
pocket book;149.00
chips;24.30
tomato;10.00
peanuts;18.50

Først står produkt, så «;», og så priset.

Filen med hendelsene ved kassen er formattert slik som eksempelfilen cash_register.txt (som du kan bruke til å teste programmet ditt):

buy;apple
buy;dish soap
buy;apple
buy;toilet paper
buy;frozen pizza
buy;peanuts
return;toothpaste
buy;tomato
buy;chips
buy;tomato
return;pocket book
buy;tomato
pay;100.00

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"):

 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 \(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

Obs

Testene tilgjengelig her: uke_11_tests.zip for oppgavene 1, 3 og 4.

Oppgave 1

Du finner Oppgave 1 nedenfor Eksempel 1.

Oppgave 2

Du finner Oppgave 2 nedenfor Eksempel 4.

Oppgave 3

Du finner Oppgave 3 nedenfor Eksempel 6.

Oppgave 4

Du finner Oppgave 4 nedenfor Eksempel 7.

Oppgave 5

Du finner Oppgave 5 nedenfor Eksempel 8.