TDDE44 Programmering, grundkurs¶

Föreläsning 5.1-5.2¶

Johan Falkenjack, johan.falkenjack@liu.se¶

Föreläsningsöversikt, FÖ 5.1-5.2¶

  • Period 1 & Period 2
  • Objektorienterad programmering (OOP)
    • Begrepp: klass, instans (objekt), instansvariabler, metoder
    • Syntax: skapa klasser, använda instanser
    • Funktioner som objekt
    • Exempel på OOP: grafiska gränssnitt
    • Exempel på OOP: undantagshantering
    • UML-diagram
  • Kodstandarder för Python: PEP8 & PEP257
  • Programmeringsmetod
    • Implementation, testning, felsökning

Sammanfattning av period 1¶

  • Grundläggande datatyper/datastrukturer
  • Syntax och användning av funktioner
  • Syntax och användning av kontrollstrukturer (villkor, loopar)
  • Repetition på två sätt: iteration och rekursion
  • Dela upp problem, bryta ut funktioner, introduktion till abstraktion

Halvtidsutvärdering

qrcode_for_tdde44_halvtidsutvardering_vt25.png
QR-kod som leder till utvärderingen
  • Stänger fredag 4e april.
  • Dina svar är anonyma, men du får gärna ange vilken klass du går i.
    • (Inloggning med liuid krävs för att undvika spam, men vi kan inte koppla ihop dig med dina svar.)
  • Länk till utvärderingen: https://forms.office.com/e/0urYBCaG36

Inför period 2¶

  • Anmäl er till nya grupper
  • Stark rekommendation: byt programmeringspartner

Att göra period 2¶

  • 3 laborationer i par (något mindre än tidigare år)
  • 12 små och webbaserade tutorials som genomförs individuellt (nya för i år)
  • Prova-på-tenta, 21 maj kl 10.15-11.00 och 11.15-12.00
    • (bekanta dig med tentasystemet i förväg, slipp stress och strul på riktiga tentan)
  • Tenta, 3e juni, 8.00-12.00

Laboration 5-7¶

  • Kodstandard, PEP8 och PEP257, är ett krav.
  • Större uppgifter.
  • Mer problemlösning.
  • Nya koncept och nya konstruktioner.
  • Laboration 5 är publicerad på kurshemsidan, laboration 6 och 7 kommer snart.

Vad är objektorienterad programmering?¶

Objektorientering¶

  • Ett paradigm (sätt att organisera kod) som sätter objekt i fokus med flera syften:
    1. Abstraktion genom inkapsling (eng. encapsulation)
    2. Modularitet och återanvändning
    3. Polymorfism
    4. Generalisering, t.ex. genom arv, sammansättning, eller kontrakt
  • Vi kommer titta närmare på vad dessa koncept är och innebär i Python.

Python är objektbaserat

values_are_objects.svg
  • Alla värden i Python är objekt.
  • Vi kan tänka på ett objekt som något som omger ett faktiskt värde, representerat som 0:or och 1:or i minnet.
  • Objektet anger värdets typ och vilka attribut värdet har.
  • answer = 42
    
  • Många andra programmeringsspråk har vissa så kallade primitiva datatyper (ofta heltal, flyttal, tecken, array, etc.) och värden av dessa typer är inte objekt.
  • Skapa objekt¶

    • Vi har främst skapat objekt mha literaler, t.ex. heltalet 42, strängen 'hej' eller listan [1, 2, 3].
    • Detta är syntaktiskt socker för ett mer generellt sätt att skapa objekt.
    • Vi har sett det generella sättet att skapa objekt, men främst använt det för typkonvertering:
    In [12]:
    print(int())
    print(list())
    print(str())
    print(float())
    
    0
    []
    
    0.0
    

    Hur är en lista egentligen implementerad?¶

    • Antag att listan [1, 'två', 75, 6.0, 'sju'] ligger på minnesaddressen 0x7a3f5.
    • Är listan uppbyggd av cons-celler? Varje element representeras som ett par av referenser, en referens till det faktiska värdet och en referens till nästa element, som kan ligga på en helt annan plats i minnet?

    (Nej, ni förväntas inte veta vad cons-celler är sedan tidgare, om ni inte kodat i Lisp.)

    Hur är en lista egentligen implementerad?¶

    • Antag att listan [1, 'två', 75, 6.0, 'sju'] ligger på minnesaddressen 0x7a3f5.
    • Ligger alla element kant i kant med varandra i minnet, separerade med något specialtecken som visar när ett element slutar och ett annat börjar?

    Hur är en lista egentligen implementerad?¶

    • Antag att listan [1, 'två', 75, 6.0, 'sju'] ligger på minnesaddressen 0x7a3f5.
    • Ligger det referenser till alla element kant i kant med varandra i minnet?

    Behöver vi veta hur listan egentligen är implementerad?¶

    • Hittills har det räckt att vi vet hur den används.
    • Den underliggande komplexiteten har varit dold från oss men det har gått bra ändå.
    • Detta är ett exempel på den abstraktion som objekt erbjuder.
      • (Det finns situationer när det spelar roll hur en viss datatyp är implementerad, men lite mer om det i Labb 7 och mycket mer i TDDE54.)

      Meddelanden¶

      • Genom att skicka meddelanden (eng. message passing) till ett objekt kan vi indirekt påverka objektet eller komma åt den data som finns där i.
      • Det är objektet självt som avgör vad som ska hända när ett meddelande inkommer.
        • Detta kallas för dynamic dispatch och är ett sätt att uppnå polymorfi, mer om det snart.
      • Ett meddelande skickas till ett objekt genom att använda punktnotation.
      In [13]:
      names = list()
      names.append
      
      Out[13]:
      <function list.append(object, /)>
      • / i parameterlistan betyder att append inte kan ta nyckelordsargument

      Metodanrop¶

      • När vi skickade meddelandet append till listobjektet names fick vi tillbaka en funktion.
      • En sådan funktion, som existerar inne i ett objekt, kallas för en metod.
      • Vi kallar ett uttryck som names.append("Johan") för metodanrop.
      In [14]:
      names = list()
      names.append("Johan")
      print(names)
      
      ['Johan']
      

      Kan vi skicka vilka meddelanden som helst?¶

      • Om vi skickar ett meddelande som inte matchar något av objektets attribut så kommer vi få ett AttributeError:
      In [15]:
      names = list()
      names.tjotahejti
      
      ---------------------------------------------------------------------------
      AttributeError                            Traceback (most recent call last)
      Cell In[15], line 2
            1 names = list()
      ----> 2 names.tjotahejti
      
      AttributeError: 'list' object has no attribute 'tjotahejti'

      Funktionen dir¶

      • Vi kan använda dir för att ta reda på vilka meddelanden ett objekt kan ta emot.
      In [16]:
      names = list()
      dir(names)
      
      Out[16]:
      ['__add__',
       '__class__',
       '__class_getitem__',
       '__contains__',
       '__delattr__',
       '__delitem__',
       '__dir__',
       '__doc__',
       '__eq__',
       '__format__',
       '__ge__',
       '__getattribute__',
       '__getitem__',
       '__gt__',
       '__hash__',
       '__iadd__',
       '__imul__',
       '__init__',
       '__init_subclass__',
       '__iter__',
       '__le__',
       '__len__',
       '__lt__',
       '__mul__',
       '__ne__',
       '__new__',
       '__reduce__',
       '__reduce_ex__',
       '__repr__',
       '__reversed__',
       '__rmul__',
       '__setattr__',
       '__setitem__',
       '__sizeof__',
       '__str__',
       '__subclasshook__',
       'append',
       'clear',
       'copy',
       'count',
       'extend',
       'index',
       'insert',
       'pop',
       'remove',
       'reverse',
       'sort']
      • Mer om vad vissa av dessa är snart.

      Vad avgör vilka attribut ett visst objekt har?¶

      • Kan vi skicka meddelandet append till ett heltal?

      Objektets klass¶

      Klasser¶

      • Alla objekt är instanser av en klass.
      • I Python är alla datatyper, även de inbyggda, representerade som klasser.
      • En klass är en mall, en beskrivning av något som vi vill representera i ett program.
      • Klassen beskriver vilka egenskaper och beteenden ett objekt har.
        • Egenskaper representeras som instansvariabler, speciella variabler som existerar inne i ett objekt.
        • Beteenden realiseras genom metoder.
      • Ofta används klasser för att modellera verkligheten.

      Klasser i föreläsningssalen¶

      • Y1.a? MED1? TMA1?
      • Proletariat? Bourgeoisie? Aristokrati?
      • Kanske, men snarare:
        • Bänk
        • Stol
        • Eluttag
        • Whiteboard
        • Projektor
        • Student
        • Lärare
        • Person

      Klasser med olika syften¶

      • Klasser som modellerar verkligheten, t.ex. studenter.
      • Klasser som implementerar generella datastrukturer, t.ex. listor eller dictionaries.
      • Klasser som representerar input-/output-enheter, t.ex. filer eller nätverksresurser.
      • Klasser som utgör komponenter i större program.
        • Ska vi vara riktigt strikta så gäller detta alla klasser.

      Vår första egna klass¶

      • Scenario: Vi vill skriva ett program som kan testa huruvida två personer har gemensamma intessen.
      • Vilka klasser kan vi tänka oss här?
      • Person
      • Interest

      Klassen Person: Nyckelordet class¶

      • Nyckelordet class används för att inleda en klassdefinition, på samma sätt som def används för att inleda en funktionsdefinition.
      • class följs av namnet på den klass man skapar och kolon, ungefär som en funktionsdefinition.
      • I resten av klassdefinitionen, dvs i klassens kropp, defineras klassens egenskaper och beteenden.
      In [ ]:
      class Person:
          
      
      In [18]:
      class Person:
          
          pass
      
      p = Person()
      print(type(p))
      
      <class '__main__.Person'>
      
      • Notera att vi döpte klassen till Person, inte person. Bara Pythons inbyggda klasser inleds med liten bokstav.

      Klassen Person: Konstruktor, self och instansvariabler¶

      • När en ny instans av en klass skapas så anropas klassens konstruktor.
      • En konstruktor förbereder objektet som skapas, t.ex. genom att skapa eventuella instansvariabler som representerar det skapade objektets egenskaper.
      • I Python representeras konstruktorn alltid som en metod med namnet __init__ som tar minst ett argument, self, som representerar det aktuella objektet.
      • Vilka egenskaper behöver Person ha?
      In [19]:
      class Person:
          
          def __init__(self):
              self._interest = None
      
      In [ ]:
      class Person:
          
          def __init__(self):
              self._interest = None
      

      Klassen Person: Varför self._interest och inte self.interest?¶

      In [20]:
      class Person:
          
          def __init__(self):
              self._interest = None
      
      • Vi använder _ i början av ett attribut för att signalera till andra programmerare att detta attribut bara ska användas inne i klassen.
      • Om man har ett Person-objekt i en variabel p så ska man alltså aldrig skriva p._interest.
      • Det är vanligt att låta alla instansvariabler inledas med _ och tillhandahålla metoder för att komma åt dem.
      • Obs! Detta är en konvention, något vi programmerare kommit överens om att följa, inte en syntaktisk regel i Python.

      Klassen Person: Beteenden och metoder¶

      • Vilka beteenden behöver Person ha?
      In [21]:
      class Person:
          
          def __init__(self):
              self._interest = None
              
          def set_interest(self, interest):
              self._interest = interest
          
          def get_interest(self):
              return self._interest
          
          def share_interest(self, other):
              return self._interest == other.get_interest()
              
      ada = Person()
      ada.set_interest('programming')
      print(ada.get_interest())
      
      programming
      
      • Lägg till intresse till person ada
        • ada.set_interest('Bernoulli numbers')
      • Ta reda på om en person ada och en person charles matchar
        • ada.share_interest(charles)
      def set_interest(self, interest):
              self._interest = interest
      
          def get_interest(self):
              return self._interest
      
          def share_interest(self, other):
              return self._interest == other.get_interest()
      

      Klassen Person: Testkörning¶

      In [22]:
      class Person:
          
          def __init__(self):
              self._interest = None
              
          def set_interest(self, interest):
              self._interest = interest
              
          def get_interest(self):
              return self._interest
              
          def share_interest(self, other):
              return self._interest == other.get_interest()
      
      annie = Person()
      annie.set_interest('rocketry')
      grace = Person()
      grace.set_interest('compilers')
      margaret = Person()
      margaret.set_interest('rocketry')
      
      print(annie.share_interest(grace))
      print(annie.share_interest(margaret))
      
      False
      True
      
      annie = Person()
      annie.set_interest('rocketry')
      grace = Person()
      grace.set_interest('compilers')
      margaret = Person()
      margaret.set_interest('rocketry')
      margaret.share_interest(annie)
      
      • Annie Easley, en av NASAs första svarta anställda, utbildade sig till matematiker och programmerare innan "programmerare" ens var ett etablerat begrepp. Genomförde många av de grundläggande beräkningarna för NASAs raketer.

      • Grace Hopper, vice-amiral och pådrivande för att program skulle skrivas i något som liknade mänskligt språk, tidig pionjär inom kompilatorteknik.

      • Margaret Hamilton, programmeringschef för Apollo-landaren på NASA.

      • ~Adele Goldberg, objektorienteringspionjär och var en av drivkrafterna bakom Smalltalk-80, ett av de första objektorienterade programmeringsspråken och hennes arbete ligger till grund för de fönstersystem som används i de flesta moderna operativsystemen.~

      Varför alla dessa metoder i Person?¶

      • Det verkar onödigt krångligt, varför inte bara skriva som nedan?
        • (Obs! För den som läser detta i efterhand, nedanstående är alltså inte lämpligt.)
      In [24]:
      class Person:
          
          def __init__(self):
              self.interest = None
      
      annie = Person()
      annie.interest = 'rocketry'
      grace = Person()
      grace.interest = 'compilers'
      margaret = Person()
      margaret.interest = 'rocketry'
      
      def matches(person_a, person_b):
          return person_a.interest == person_b.interest
      
      print(matches(annie, grace))
      print(matches(annie, margaret))
      
      False
      True
      

      Inkapsling (Encapsulation)¶

      • Objekt är själva ansvariga för den data de innehåller och ingen kod utanför objektet får (givet ren objektorientering) direkt påverka det som finns inne i objektet.
        • Obs! Python är inte ett rent objektorienterat språk utan tillåter att man hur som helst bryter inkapslingen, som på föregående slide.
        • Att använda _ i början av instansvariabler indikerar att en instansvariabel inte får användas utanför klassdefinitionen.
      • Jag och labassistenterna tillåter det däremot inte.
      • Varför inte?

      Vår andra egna klass: Datatypen kö (eng. queue)

      No description has been provided for this image
      • Sekvens med objekt som ska hanteras i den ordning de kommer in, enligt principen first in, first out (FIFO).
        • Jmf. med datatypen stack som är last in, first out (LIFO)
      • Operationen enqueue lägger till ett värde till slutet på kön.
      • Operationen dequeue returnerar värdet som är först i kön och plockar även bort det från kön.
      • Vi kan implementera detta som en klass.

      Klassen Queue¶

      • Vilka egenskaper behöver Queue ha?
      In [25]:
      class Queue:
          
          def __init__(self):
              self._storage = []
      
      In [ ]:
      class Queue:
          
          def __init__(self):
              self._storage = []
      

      Klassen Queue¶

      • Vilka beteenden behöver Queue ha?
      • Det bestämde vi ju i förväg: enqueue och dequeue
      In [26]:
      class Queue:
          
          def __init__(self):
              self._storage = []
          
          def enqueue(self, value):
              self._storage.append(value)
          
          def dequeue(self):
              if self._storage:
                  return self._storage.pop(0)
              else:
                  return None
          
      
      In [ ]:
      class Queue:
          
          def __init__(self):
              self._storage = []
              
          def enqueue(self, value):
              self._storage.append(value)
              
          def dequeue(self):
              if self._storage:
                  return self._storage.pop(0)
              else:
                  return None
      

      Klassen Queue¶

      In [27]:
      class Queue:
          
          def __init__(self):
              self._storage = []
              
          def enqueue(self, value):
              self._storage.append(value)
              
          def dequeue(self):
              if self._storage:
                  return self._storage.pop(0)
              else:
                  return None
      
      q = Queue()
      q.enqueue('a')
      q.enqueue('b')
      print(q.dequeue())
      q.enqueue('c')
      print(q.dequeue())
      print(q.dequeue())
      print(q.dequeue())
      
      a
      b
      c
      None
      
      In [ ]:
      q = Queue()
      q.enqueue('a')
      q.enqueue('b')
      print(1, f"{q.dequeue()=}")
      q.enqueue('c')
      print(2, f"{q.dequeue()=}")
      print(3, f"{q.dequeue()=}")
      print(4, f"{q.dequeue()=}")
      

      Antag att vi bryter inkapslingen¶

      In [28]:
      q = Queue()
      q._storage.append('a')
      q._storage.append('b')
      print(1, f"{q._storage.pop(0)=}")
      q._storage.append('c')
      print(2, f"{q._storage.pop(0)=}")
      print(3, f"{q._storage.pop(0)=}")
      print(4, f"{q._storage.pop(0)=}")
      
      1 q._storage.pop(0)='a'
      2 q._storage.pop(0)='b'
      3 q._storage.pop(0)='c'
      
      ---------------------------------------------------------------------------
      IndexError                                Traceback (most recent call last)
      Cell In[28], line 8
            6 print(2, f"{q._storage.pop(0)=}")
            7 print(3, f"{q._storage.pop(0)=}")
      ----> 8 print(4, f"{q._storage.pop(0)=}")
      
      IndexError: pop from empty list

      Inkapsling minskar risken för liknande fel¶

      • Metoden dequeue ser till att även fall då kön är tom hanteras utan att fel uppstår.

      Antag att vi ändrar implementationen av Queue

      No description has been provided for this image
      • En lista är inte optimal för att representera en kö eftersom det är beräkningsmässigt tungt att ta bort första elementet, särskilt om listan är lång.
      • Klurar man lite så inser man dock kanske att om vi låter varje element i kön hålla koll på vilket element som kommer efter det så behöver själva kön bara hålla koll på två element, det första (front) och det sista (back).
        • Instansvariabeln _storage ersätts av instansvariablerna _front och _back.
      • Metoderna enqueue och dequeue kommer att kunna användas på precis samma sätt som tidigare.
      • Försöker vi dock göra enqueue med q._storage.append(value) och dequeue med g._storage.pop(0) kommer det bli fel.

      För den vetgirige¶

      • Här är en implementation av Queue som bara håller koll på första och sista elementet, där varje element i kön representeras som en cons-cell realiserad som en lista av två element (hastigt ihopslängd, säg till om ni hittar buggar):
      In [29]:
      class Queue:
          
          def __init__(self):
              self._front = None
              self._back = None
          
          def enqueue(self, value):
              elem = [value, None]
              if self._front is None and self._back is None:
                  self._front = elem
                  self._back = elem
              else:
                  self._back[1] = elem
                  self._back = elem
          
          def dequeue(self):
              if self._front is None:
                  return None
              else:
                  value = self._front[0]
                  if self._front[1] is None:
                      self._front = None
                      self._back = None
                  else:
                      self._front = self._front[1]
                  return value
              
          def __str__(self):
              res = []
              cur = self._front
              while cur:
                  res.append(cur[0])
                  cur = cur[1]
              return f"(Queue: {', '.join(res)})"
      
      q = Queue()
      print(1, q)
      q.enqueue('a')
      print(2, q)
      q.enqueue('b')
      print(3, q)
      print(4, f"{q.dequeue()=}")
      print(5, q)
      q.enqueue('c')
      print(6, q)
      print(7, f"{q.dequeue()=}")
      print(8, f"{q.dequeue()=}")
      print(9, f"{q.dequeue()=}")
      
      1 (Queue: )
      2 (Queue: a)
      3 (Queue: a, b)
      4 q.dequeue()='a'
      5 (Queue: b)
      6 (Queue: b, c)
      7 q.dequeue()='b'
      8 q.dequeue()='c'
      9 q.dequeue()=None
      

      För den ännu mer vetgirige¶

      • Här är en implementation av Queue som bara håller koll på första och sista elementet och som dessutom har en nästlad klass QueueElement för elementen istället för att använda en lista (ja, Python tillåter att vi definierar klasser inne i andra klasser, men notera att vi måste skriva Queue.QueueElement eller self.QueueElement för att komma åt den):
      In [36]:
      class Queue:
          
          class QueueElement:
      
              def __init__(self, value):
                  self._value = value
                  self._next = None
      
              def set_next(self, element):
                  self._next = element
      
              def get_next(self):
                  return self._next
      
              def get_value(self):
                  return self._value
      
          def __init__(self):
              self._front = None
              self._back = None
          
          def enqueue(self, value):
              elem = Queue.QueueElement(value)
              # elem = self.QueueElement(value)
              if self._front is None and self._back is None:
                  self._front = elem
                  self._back = elem
              else:
                  self._back.set_next(elem)
                  self._back = elem
          
          def dequeue(self):
              if self._front is None:
                  return None
              else:
                  value = self._front.get_value()
                  if self._front == self._back:
                      self._front = None
                      self._back = None
                  else:
                      self._front = self._front.get_next()
                  return value
              
          def __str__(self):
              res = []
              cur = self._front
              while cur:
                  res.append(cur.get_value())
                  cur = cur.get_next()
              return f"(Queue: {', '.join(res)})"
      
      q = Queue()
      print(1, q)
      q.enqueue('a')
      print(2, q)
      q.enqueue('b')
      print(3, q)
      print(4, f"{q.dequeue()=}")
      print(5, q)
      q.enqueue('c')
      print(6, q)
      print(7, f"{q.dequeue()=}")
      print(8, f"{q.dequeue()=}")
      print(9, f"{q.dequeue()=}")
      
      1 (Queue: )
      2 (Queue: a)
      3 (Queue: a, b)
      4 q.dequeue()='a'
      5 (Queue: b)
      6 (Queue: b, c)
      7 q.dequeue()='b'
      8 q.dequeue()='c'
      9 q.dequeue()=None
      

      Repetition: Klassdefinition¶

      • Nyckelordet class används för att definiera en ny klass, på ungefär sätt som def används för att definiera en funktion.
      • class följs av namnet på den klass man skapar och kolon, ungefär som en funktionsdefinition.
      class Queue:
      
      • Inne i kodblocket för klassen, dvs i klassens kropp, defineras klassens metoder och attribut.

      Repetition: Metoddefinition¶

      • Inne i en klasskropp defineras metoder med def på samma sätt som vilken funktion som helst, med ett undantag.
      • Första argumentet till en metod är alltid den aktuella instansen, dvs. objektet självt, och därför använder vi namnet self för den första parametern.
        • Detta är inte i strikt mening ett krav i Python, men det är en konvention man alltid ska följa.
      • Inne i metoden används sedan self för att komma åt instansvariabler eller andra metoder.
      def enqueue(self, value):
              self._storage.append(value)
      

      Magic methods i Python¶

      No description has been provided for this image
      • Python-klasser kan implementera vissa specialmetoder som inte anropas direkt utan med hjälp av andra språkliga konstruktioner, dessa kallas för magic methods.
      • Eftersom Python använder double underscore (t.ex. __init__) för att indikera dessa kallas de ibland också för dundermetoder.

      __init__¶

      • Metoden __init__ är den vanligast förekommande magiska metoden och beskriver hur ett objekt ska skapas eller initieras.
      • Ofta kan __init__ ta många olika argument men likt alla metoder är den första parameterna alltid self.
      • Mer generellt kallas __init__ för en konstruktor och motsvarande konstruktioner finns i alla språk med klassbaserad OOP.
      • Konstruktorn anropas inte manuellt utan genom att "anropa" själva klassen
      class Queue:
      
          def __init__(self):
              self._storage = []
          ...
      
      q = Queue()
      

      __str__¶

      • Efter __init__ är __str__ antagligen den vanligast förekommande dundermetoden.
      • Metoden __str__ beskriver strängrepresentationen av ett objekt.
      • __str__ anropas automatiskt av funktionen print eller "halvmanuellt" genom att skapa en ny sträng-instans med str och skicka med objektet som argument.
      class Queue:
          ...
      
          def __str__(self):
              return f"(Queue: {str(self._storage)})"
          ...
      
      q = Queue()
      print(q)
      str(q)
      

      Andra dundermetoder¶

      • För att definiera beteendet hos operatorer, t.ex.
        • obj / 2 → obj.__div__(2)
        • obj == 2 → obj.__eq__(42)
        • obj += 1 → obj.__iadd__(1)
      • För att definiera hur objekt översätts till sanningsvärden:
        • obj and True → obj.__nonzero__() and True
      • För att göra dict-liknande uppslagning möjlig:
        • obj['name'] → obj.__getitem__('name')
      • För att göra det möjligt att iterera över innehållet i ett objekt:
        • for element in obj: → for element in obj.__iter__():

      Men vänta lite...¶

      • Vår implementation av Queue:
      In [37]:
      class Queue:
          
          def __init__(self):
              self._storage = []
              
          def enqueue(self, value):
              self._storage.append(value)
              
          def dequeue(self):
              if self._storage:
                  return self._storage.pop(0)
              else:
                  return None
      
      q = Queue()
      print(q)
      
      <__main__.Queue object at 0x7f2f6c228070>
      

      Varför får vi inte AttributeError?¶

      • Vi sa att print automatiskt anropar __str__, men Queue har ju ingen __str__-metod.
      In [38]:
      q = Queue()
      q.tjohej
      
      ---------------------------------------------------------------------------
      AttributeError                            Traceback (most recent call last)
      Cell In[38], line 2
            1 q = Queue()
      ----> 2 q.tjohej
      
      AttributeError: 'Queue' object has no attribute 'tjohej'
      In [39]:
      q = Queue()
      q.__str__
      q.__tjohej__
      
      ---------------------------------------------------------------------------
      AttributeError                            Traceback (most recent call last)
      Cell In[39], line 3
            1 q = Queue()
            2 q.__str__
      ----> 3 q.__tjohej__
      
      AttributeError: 'Queue' object has no attribute '__tjohej__'

      Varifrån kommer __str__-metoden Queue?¶

      Klassen Queue ärver metoden __str__ från sin basklass, object¶

      Arv - ett sätt att återanvända kod¶

      Översikt (vi kommer inte arbeta mycket med arv i denna kurs, men det är viktigt att känna till)

      Arv¶

      • Arv är inte en absolut nödvändig del av objektorientering* men är fundamentalt för hur objektorientering fungerar i många språk med klassbaserad objektorientering, t.ex. Python, C++, Java, C#, Swift, JavaScript och Objective-C
        • * moderna uppstickare som Rust och Go har t.ex. valt bort arv
      • Arv låter oss skapa härledda klasser (även kallade för subklasser).
      • En härledd klass ärver alla attribut från en eller flera basklasser (även kallad superklasser), men kan utöka eller ersätta dessa.

      Definiera en subklass¶

      • Python för bok över vilka klasser som finns. Varje klass lagras i en hierarki av klasser.
      • Klasshierarkin finns för att vi ska kunna återanvända kod från "klassföräldrar", basklasser (kallas ibland även för superklasser).
      • För att definiera en klass använder vi nyckelordet class följt av ett namn, samt ett parentespar med den nya klassens basklass.
      • Anges ingen basklass kommer klassen object användas som basklass.
      class Queue:
          pass
      
      • Samma som:
      class Queue(object):
          pass
      

      Klassen object¶

      • Pythons mest fundamentala basklass. Alla andra klasser ärver, i något ändligt antal steg, från object.
      • Vi har sett kod ärvd från object köras.
        • Innan vi skapade Queue.__str__ användes object.__str__ när vi skrev ut Queue-objekt.
        • Det var object.__str__ som returnerade '<__main__.Queue object at 0x0000021A9813B250>'
      • När vi skapade Queue.__str__ så överskuggade vi den ärvda __str__-metoden från object

      Exempel: Queue med list som basklass¶

      In [40]:
      class QueueList(list):
          
          def enqueue(self, value):
              self.append(value)
              
          def dequeue(self):
              if self:
                  return self.pop(0)
              else:
                  return None
      
      • Bara för att man kan göra något så betyder det inte att man ska.

      Exempel: Queue med list som basklass¶

      In [41]:
      q = QueueList()
      print(1, q)
      q.enqueue('a')
      print(2, q)
      q.enqueue('b')
      print(3, q)
      print(4, f"{q.dequeue()=}")
      print(5, q)
      q.enqueue('c')
      print(6, q)
      
      1 []
      2 ['a']
      3 ['a', 'b']
      4 q.dequeue()='a'
      5 ['b']
      6 ['b', 'c']
      
      In [42]:
      print(7, f"{q.dequeue()=}")
      print(8, f"{q.dequeue()=}")
      print(9, f"{q.dequeue()=}")
      
      7 q.dequeue()='b'
      8 q.dequeue()='c'
      9 q.dequeue()=None
      

      Mellanspel: Funktionsobjekt och högre ordningens funktioner¶

      Funktionsobjekt¶

      • Alla värden (data) är objekt i Python.
      • Definierade funktioner är också en typ av värden, alltså också en typ av objekt.
      • Objekt av klassen function.
      In [43]:
      def hejsan():
          print("Hejsan")
      
      print(type(hejsan))
      
      <class 'function'>
      

      Funktionsobjekt¶

      • Klasser kan instansieras och producera objekt
      • I Python är även funktioner objekt.
      • En funktion:
      def hejsan():
          print("Hejsan")
      
      • Ett funktionanrop: hejsan()
      • Funktionsobjektet: det som namnet hejsan refererar till

      Exempel på funktionsobjekt¶

      In [44]:
      def print_hello():
          print("Hello World!")
          
      bacon = print_hello
      bacon()
      
      Hello World!
      
      • I koden ovan refererar både print_hello och bacon till samma funktionsobjekt.

      Högre ordningens funktioner¶

      • Funktioner som antingen tar emot en funktion som argument eller returnerar en funktion kallas högre ordningens funktioner.
        • Mer generellt, högre ordningens funktioner är funktioner som opererar på funktioner.
      • Exempel på användning
        • Pythons inbyggda funktioner sum(), max(), min() m.fl. kan ta emot ett nyckelordsargument med en funktion som plockar fram det värde som ska användas.

      Exempel¶

      In [45]:
      def second(values):
          return values[1]
      
      value_pairs = [ ["Ada", 3], ["Bertil", 2], ["Cissi", 7] ]
      
      print("Högst poäng:", max(value_pairs, key=second))
      print("Lägst poäng:", min(value_pairs, key=second))
      
      Högst poäng: ['Cissi', 7]
      Lägst poäng: ['Bertil', 2]
      

      Grafiska gränssnitt med tkinter¶

      Programflöde: GUI¶

      • GUI = Graphical User Interface
      • Icke-linjär interaktion, händelsestyrt
      • Reaktivt gränssnitt - tät återkoppling
      • Interaktion t.ex. via mus och tangentbord
      • Interaktion med en begränsad och standardiserad uppsättning widgets (oftast)

      Om modulen tkinter¶

      • Lastgammal, men...
      • Det enda biblioteket för att bygga GUI-applikationer i Pythons standardbibliotek (dvs. det är inbyggt).
        • Långt ifrån det enda biblioteket/ramverket för att bygga GUI-applikationer i Python.
      • Python-wrapper för Tcl/Tk, ett GUI-bibliotek som sträcker sig tillbaka till början av 90-talet och som kan användas från massor olika programmeringsspråk.
        • Tcl, uttalat "tickle", är ett programmeringsspråk som främst lever kvar genom Tk.
      • Widgets är definierade som klasser: faktiska gränssnittskomponenter är instanser av klasserna.
      • Olika inställningar kan ges när man skapar en widget.
      • Widgets kan placeras i ett GUI på tre sätt - det finns tre "geometry managers" (i andra ramverk också kallade för "layout managers").

      Widgets¶

      Litet exempel¶

      In [46]:
      import tkinter as tk
      import random
      
      def clicked():
          colors = [("red", "white"), ("green", "black"), ("blue", "white")]
          bg_color, fg_color = random.choice(colors)
          label.config(bg=bg_color, fg=fg_color, text="button clicked")
      
      def mouse_enter(event):
          label.config(text="mouse over")
      
      def mouse_leave(event):
          label.config(text="mouse exit")
      
      # skapa ett Tk-fönster
      root = tk.Tk()
      
      # skapa label-widget
      label = tk.Label(root, text="Hello!")
      label.pack(fill=tk.X)
      
      # koppla ihop händelser med funktioner. OBS! Funktionsobjekt som argument.
      label.bind("<Enter>", mouse_enter)
      label.bind("<Leave>", mouse_leave)
      
      # skapa knappen. OBS! Funktionsobjekt som argument
      button = tk.Button(root, text="Press Me!", command=clicked)
      button.pack(fill=tk.X)
      
      # starta GUI-loopen
      root.mainloop()
      

      Laboration 5¶

      • Del 1: syntax för att skapa och använda klasser
      • Del 2: använda objekt + öva på att skriva kommentarer, följa kodstandarder

      Del 2¶

      No description has been provided for this image
      • Kodskelett: random-layout.py
      • Gränssnitt: lab5.py

      • Syftet är inte att ge en introduktion till hur moderna GUI-ramverk fungerar.

      random-layout.py¶

      In [47]:
      %%python
      #!/usr/bin/env python3
      """Laboration 5 -- TDDE44
      
      Exempel på slumpmässig layout-funktion. Layout-funktionen skickas som
      argument när en instans av lab5.LayoutTester skapas.
      """
      
      # Läs denna fil för att se hur gränssnittet skapats.
      import lab5
      import random
      
      def random_layout(squares, frame_height, frame_width):
          """Placera ut fyrkanterna i listan squares slumpmässigt.
          Argument:
          squares -- Lista som innehåller tkinter.Label-objekt
          frame_height -- Höjden (int) på den Fram som fyrkanterna ligger i
          frame_width -- Bredden (int) på den Frame som fyrkanterna ligger i
          """
          # Slumpa ut positioner för alla fyrkanter utan att de hamnar utanför framen
          for square in squares:
              square_size = square.winfo_width()
              xpos = random.randint(0, frame_width - square_size)
              ypos = random.randint(0, frame_height - square_size)
              square.place(x=xpos, y=ypos)
              
      if __name__ == "__main__":
          layout_tester = lab5.LayoutTester(random_layout)
          
      
      Running 'random_layout(<squares>, 171, 829)'...
      Running 'random_layout(<squares>, 171, 829)'...
      Running 'random_layout(<squares>, 171, 829)'...
      Running 'random_layout(<squares>, 171, 829)'...
      Running 'random_layout(<squares>, 171, 829)'...
      Running 'random_layout(<squares>, 171, 829)'...
      Running 'random_layout(<squares>, 171, 829)'...
      

      Introduktion till klassdiagram i UML¶

      UML - Unified Modelling Language¶

      • Ett sätt att grafiskt representera system, t.ex. datorprogram, hur de är uppbyggda och fungerar
      • UML definierar en uppsjö av olika diagram i tre grova kategorier för att beskriva olika aspekter
        • Strukturdiagram, beskriver hur ett system är uppbyggt.
        • Beteendediagram, beskriver hur olika processer sker i systemet.
        • Interaktionsdiagram, beskriver hur en användare interagerar med systemet.
      • I sin striktaste form, nästintill ett eget programmeringsspråk.
        • Används dock ofta mer informellt för att illustrera specifika saker, och då utelämnas detaljer som inte behövs i det fallet

      Klassdiagram¶

      • Klassdiagram beskriver klasser och deras relationer till varandra.
      • En typ av strukturdiagram på relativt låg abstraktionsnivå.
      • En av de vanligaste typerna av UML-diagram att stöta på.
      • En enskild klass i ett klassdiagram ritas som till höger.

      Klassdiagram¶

      • Ofta anger vi datatyper där det är praktiskt.

      Book-klassen, definition¶

      In [48]:
      class Book:
          
          def __init__(self, title):
              self.title = title
              self.author = "Unknown"
              self.year = None
      
          def print_citation(self):
              if not self.year:
                  year = "N/A"
              else:
                  year = self.year
              print(f"{self.author}. ({year}). {self.title}")
              
      

      Book-klassen, UML-diagram¶

      • Här anger vi datatyp även i konstruktorns parameterlista

      Klassdiagram, Laboration 5, Del 1


      No description has been provided for this image
      No description has been provided for this image
      • Ofta utelämnar vi konstruktorn helt men här har vi tagit med den.

      Relationer i klassdiagram¶

      • Relationer informerar oss om
        • Vilka klasser känner till vilka andra klasser?
        • Hur många instanser deltar i relationen? (Kardinalitet)
        • Vilken roll har en klass i relation till en annan?
      • Innebär att en instans av en klass har en referens till en instans av en annan klass (eller ytterligare en instans av den egna klassen)
      • Exempel på referenser
        • en instansvariabel låter oss referera till en enskild instans
        • en lista/ett dictionary låter oss referera till flera instanser

      UML: Relationen association¶

      UML_association_arrow.drawio.png

      • association: allmän relation
      • Klass A → Klass B
      • roll (i praktiken variabelnamn), t.ex. "lästa_böcker" och
        • Instansen/-erna av klassen B har rollen <roll>
      • multiplicitet (<lägst antal>..<högst antal>, * betyder godtyckligt många), exempel: 0..1, 1..1, 0..*
        • A känner till <multiplicitet> st instanser av klassen B
      • En dubbelriktad association kan ritas utan pilar.

      Exempel¶

      UML_LayoutTester_tkinter.drawio.png

      Exempel¶

      • Ett TodoApp-objekt känner till exakt ett TaskList-objekt
      • Ett TaskList-objekt känner till 0 eller fler Task-objekt
      • "känner till" i kod = har en referens till

      UML, klassdiagram: Association¶

      • Exempel på roller:
        • lästa_böcker
        • squares
        • contacts
      • Exempel på multiplicitet
        • 0..1: noll eller en
        • 0..*: noll eller flera
        • 1..*: en eller fler
        • 3: exakt tre
        • 0..3: noll till tre
        • 5..9: fem till nio

      UML: Andra relationer¶

      UML_relationship_arrows_Yanpas.png

      (Credit: Yanpas. Wikimedia Commons)

      Namngivning¶

      • Namn ska hjälpa läsaren förstå koden. Undvik intetsägande namn.

      • Namngivning av klasser: substantiv i singular, använd CamelCase
      • Namngivning av metoder: verb, använd snake_case
      • Namngivning av instansvariabler: substantiv, använd snake_case

      • OBS! Om klassen representerar en samling objekt, dvs fungerar som en behållare använd fortfarande singular, men lägg till ett suffix, t.ex. Collection, Set eller List

      Exempel på klassnamn¶

      • DataPoint
      • DataPointCollection
      • Word
      • Sentence
      • Paragraph
      • Document
      • Person
      • ContactList
      • Book
      • Library
      • BookCollection

      Vanliga prefix till metodnamn¶

      • Använd samma prefix för metoder som gör liknande saker.
      • get_*: använd för metoder som returnerar ett värde från ett objekt (antingen beräknat eller direkt från ett attribut). T.ex. .get_max(), .get_friendlist()
      • set_*: använd för metoder som tar emot argument och lagrar dessa i objektet.
        • T.ex. .set_filename(filename)
      • add_: använd för metoder som tar emot argument och lägger till dessa till
        • objektet. T.ex. .add_friend(name)
      • remove_: använd för metoder som tar bort data från ett objekt.
        • T.ex. .remove_last_task()
      • load_: använd för metoder som laddar data från fil. T.ex. .load_data(filename)
      • save_: använd för metoder som sparar data till fil. T.ex. .save_data()

      * Generellt sett bör man undvika "getters" och "setters" då de bryter inkapslingen och abstraktionen som är själva poängen med OOP, men ibland är de oundvikliga.

      Undantag (Exceptions)¶

      Exempel på kod som utlöser undantag¶

      In [49]:
      # Exempel 1
      number = 5
      name = "Ada"
      print(number + name)
      
      ---------------------------------------------------------------------------
      TypeError                                 Traceback (most recent call last)
      Cell In[49], line 4
            2 number = 5
            3 name = "Ada"
      ----> 4 print(number + name)
      
      TypeError: unsupported operand type(s) for +: 'int' and 'str'
      In [50]:
      # Exempel 2
      ans = 5 / (42 * 0)
      
      ---------------------------------------------------------------------------
      ZeroDivisionError                         Traceback (most recent call last)
      Cell In[50], line 2
            1 # Exempel 2
      ----> 2 ans = 5 / (42 * 0)
      
      ZeroDivisionError: division by zero
      In [51]:
      # Exempel 3
      printt("Hello world")
          
      
      ---------------------------------------------------------------------------
      NameError                                 Traceback (most recent call last)
      Cell In[51], line 2
            1 # Exempel 3
      ----> 2 printt("Hello world")
      
      NameError: name 'printt' is not defined

      Exception - exempel på OOP¶

      • Varje typ av undantag är definerad som en klass i Python
      • När ett undantag inträffar skapas en instans av den undantagstypen
      • Undantaget innehåller information om vad som hänt (data)

      Undantag - Exceptions¶

      • Undantag (eng. Exception) är ett sätt att hantera fel i kod som kan utlösas (eng. throw/raise) när ett program kör (körfel).
      • Vid fel kan ett undantag utlösas. Programmet avbryter sitt normala flöde för att gå in i "undantagsläge".
      • Om kod för att hantera ett undantag saknas, kraschar programmet.
      • Skillnad mot if/else:
        • Villkor: if/else - kontrollera först, utför sen
        • Undantagshantering: try/except - utför först, hantera ev. fel
      • Undantag ska inte ses som ett alternativ till villkorssatser utan just som ett sätt att hantera undantag.

      Exempel på olika undantag i Python¶

      • ZeroDivisionError: inträffar när man försökt dividera ett tal med 0
      • IOError: inträffar t.ex. om man försöker öppna en fil som inte finns
      • IndexError: inträffar när man använder ett ogiltigt index för en specifik lista
      • KeyError: inträffar när man försöker komma åt värdet för en nyckel som inte finns i ett dictionary
      • TypeError: inträffar t.ex. när man försöker använda operatorn + på en sträng och ett heltal

      Hantera undantag i Python¶

      • Om man vill hantera undantag i Python lägger man koden som kan utlösa undantag i ett try-block.
      • Undantag hanteras sedan i ett except-block.
      • Vid behov kan ett finally-block kan läggas till sist. Koden i detta block körs sist och alltid; dvs efter felfritt utförande av try-blocket samt efter att något except-block uförts.
      • Minst ett except-block, eller ett finally-block måste defineras för varje try-block.

      Mönster för undandagshantering i Python¶

      # nyckelordet try används för att påbörja ett try-block
      try:
          # här lägger man koden som kan utlösa ett undantag
          # "försök utföra dessa satser"
      
      # nyckelordet except används för att påbörja ett except-block
      except [typ av undantag] [as <namn på undantagsinstans>]:
          # här lägger man koden för vad som ska hända vid undantag
      
      except [typ av undantag] [as <namn på undantagsinstans>]:
          # flera olika typer av undandtag kan hanteras
          # genom att lägga till flera except-block
      
      # koden i finally-blocket utförs både efter felfritt try-block
      # och efter except-block körts vid fel i try-block
      finally:
          # kod för att städa upp, t.ex. stänga fil eller nätverks-
          # anslutning
      

      Hakparenteserna ska inte skrivas ut, de visar att det som står innanför dem är valfritt att skriva. Det som står inom mindre än/större än-tecken måste vara med. Mindre än/större än-tecknena ska inte heller skrivas i din kod.

      Hakparenteserna ska inte skrivas ut, de visar att det som står innanför dem är valfritt att skriva. Det som står inom mindre än/större än-tecken måste vara med. Mindre än/större än-tecknena ska inte heller skrivas i din kod.

      Exempel: Fånga undantag¶

      In [52]:
      # krasch.py
      # Exempel på kod som kraschar
      letters = ["a", "b", "c", "d", "e", "f"]
      
      index = 0
      while True:
          print(f"letters[index]: {letters[index]}")
          index += 1
      print("Program done.")
      
      letters[index]: a
      letters[index]: b
      letters[index]: c
      letters[index]: d
      letters[index]: e
      letters[index]: f
      
      ---------------------------------------------------------------------------
      IndexError                                Traceback (most recent call last)
      Cell In[52], line 7
            5 index = 0
            6 while True:
      ----> 7     print(f"letters[index]: {letters[index]}")
            8     index += 1
            9 print("Program done.")
      
      IndexError: list index out of range

      Exempel: Fånga undantag¶

      In [53]:
      # undantag1.py
      # Undantag hanterat, kraschar inte
      letters = ["a", "b", "c", "d", "e", "f"]
      
      try:
          index = 0
          while True:
              print("letters[index]: {}".format(letters[index]))
              index += 1
          print("Loop done.")
      except:
          print("An exception was raised.")
      print("Program done.")
      
      letters[index]: a
      letters[index]: b
      letters[index]: c
      letters[index]: d
      letters[index]: e
      letters[index]: f
      An exception was raised.
      Program done.
      
      • Notera att detta inte är något man bör göra i verkligheten.
      • Per definition svårt att göra riktiga exempel, då förväntade undantag inte är några verkliga undantag.

      Exempel: Fånga och namnge undantag¶

      In [54]:
      # undantag2.py
      letters = ["a", "b", "c", "d", "e", "f"]
      try:
          index = 0
          while True:
              print(letters[index])
              index += 1
      # Om undantag inträffar kommer variabeln error referera till en
      # instans av klassen Exception
      except Exception as error:
          print("An exception was raised.")
          print(error)
      print("Program done.")
      
      a
      b
      c
      d
      e
      f
      An exception was raised.
      list index out of range
      Program done.
      

      Exempel: Fånga och hantera specifika undantag på olika sätt¶

      In [56]:
      # undantag3.py
      import random
      
      def run_until_exception():
          values = ["a", 2, "c", 4, "e", "f"]
          try:
              while True:
                  # slumpa fram två index som ev. kan vara ogiltiga
                  index1 = random.randint(0, len(values))
                  index2 = random.randint(0, len(values))
                  # använd operatorn +. Fel om operanderna inte är samma datatyp.
                  print(f"{values[index1]} + {values[index2]} -> {values[index1] + values[index2]}")
          except TypeError as exception:
              print(f"TypeError: {exception}. Tried {values[index1]} + {values[index2]}")
          except IndexError as exception:
              print(f"IndexError: {exception}, {len(values)=}, {index1=}, {index2=}")
      
      run_until_exception()
      print("Program done.")
      
      c + a -> ca
      f + a -> fa
      TypeError: unsupported operand type(s) for +: 'int' and 'str'. Tried 4 + a
      Program done.
      

      Anledningar till att använda undantag istället för villkorssatser¶

      • Man skriver ett bibliotek som andra programmerare ska använda och man vill lämna vissa beslut till dem.
      • Ibland kan man inte förebygga fel, t.ex.
        • filen fanns först, men sedan tog någon bort den
        • vi har en fungerande internet-anslutning, men den dör medan programmet kör
      • Läsbarhet, i de få fall där en korrekt villkorssats blir oläslig kan det vara enklare att använda ett undantag. Detta är i sig ett undantag! I 99 fall av 100 bör förväntade omständigheter hanteras med villkorssatser.

      • I denna kurs är det inget krav att hantera fel med hjälp av undantag.
      • Överanvändning av undantag kan tvärt om ge komplettering på labbarna.

      Kodstandarder¶

      PEP 8 & PEP 257¶

      Kodstandarder¶

      • Ökad läsbarhet och lättare att orientera sig i koden
      • Lättare att underhålla kod

      • För Python är PEP 8, PEP 257 och PEP 287 de policy-dokument som har med kodstandard och dokumentation av kod.
      • Vi koncentrerar oss på PEP 8 och PEP 257
        • https://www.python.org/dev/peps/pep-0008/
        • https://www.python.org/dev/peps/pep-0257/

      Krav på PEP 8 och PEP 257¶

      • Från och med laboration 5 kommer det att vara krav på att den kod ni skriver följer PEP 8 och PEP 257.
      • För att underlätta kontroll av att er kod följer dessa kan ni antingen
        • installera ett plugin till er texteditor som kontrollerar PEP 8 och PEP 257 medan ni skriver er kod, eller
        • installera paket pycodestyle som används för att kontrollera PEP 8 och paketet pydocstyle som används för att kontrollera PEP 257

      PEP 8¶

      • Radlängd, indentering och whitespace
        • Indentering: 4 mellanslag
        • Radlängd: 79 tecken
        • Rader bryts på rätt sätt med rekommenderad indentering
        • Två tomma rader mellan funktioner
        • En tom rad mellan metoder
      • Namnkonventioner för variabelnamn, funktionsnamn, klassnamn

      PEP 257¶

      • Konventioner för docstrings
      • Docstrings används för att dokumentera moduler, klasser, metoder och funktioner
      • Skriv det som är viktigt för den som ska använda modulen/klassen/metoden/funktionen.
      • Börjar och slutar med tre citationstecken (""")
      • Skrivs på följande ställen
        • först i en modul
        • raden efter class Klassnamn(object):
        • raden efter def funktionsnamn(): eller def metodnamn():

      Utformning av en docstring¶

      • Två varianter:
        • docstring som endast använder en rad
        • docstring som använder flera rader
      • Docstring på en rad
        • en mening som slutar på punkt
        • ska vara formulerad i imperativ (som en "order")
      • Exempel
        • OK: """Returnera alla udda tal i argumentet value_list."""
        • Inte OK: """Funktionen returnerar alla udda tal i argumentet value_list"""

      Docstring på flera rader¶

      • Sammanfattande en-radsbeskrivning som första rad.
        • Formuleras på samma sätt som en docstring på en rad.
      • En tom rad, följt av resterande docstring-rader.
      • Avslutande trippel-citationstecknena skrivs på en egen rad.
      • Ingen tom rad mellan avslutande trippel-citationstecken och första raden kod.

      Detaljnivå på docstrings¶

      • En docstring är till för användare av modulen/klassen/funktionen/metoden.
      • I den sammanfattande första meningen är syftet att berätta vad som görs, inte hur det görs.
      • Exempel på "vad"-beskrivning (bra):
        • Returnerar en sorterad lista med innehållet från value_list.
      • Exempel på "hur"-beskrivning" (dåligt):
        • Använder en temporär lista för att lagra alla element som tupler som innehåller elementet som en sträng följt av det faktiska elementet innan de sorteras med hjälp av en bubbelsort

      Användning av docstrings kontra inlinekommentarer¶

      • Docstrings är till för att läsas av personer som vill veta hur de ska använda modulen/klassen/metoden/funktionen.
      • Inline-kommentarer (som börjar med #) är till för personer som utvecklar/underhåller modulen/klassen/metoden/funktionen.

      Automatisk kontroll av PEP8 & PEP257¶

      • Kommandoradsverktyg
        • pycodestyle för att kontrollera PEP8
        • pydocstyle för att kontroller PEP257
      • Tillägg för "linting" i Visual Studio Code

      • Mer information på kurshemsidan
        • https://www.ida.liu.se/~TDDE44/ord-och-begrepp/pep/
        • https://www.ida.liu.se/~TDDE44/kurslogistik/vscode2/

      Testning och felsökning¶

      Att testa sin kod¶

      • Testdriven programmering - utanför omfånget för denna kurs
      • Vi använder spårutskrifter och ipythons interaktiva läge.
      • Vad borde funktionen/metoden returnera? Gör den det?

      • Interaktivt läge: ipython3 -i <filnamn>
      • Kommandon:
        • ls, cd, cat etc. fungerar i ipython
        • %run <filnamn> - kör fil
        • %reset - rensa minnet från användarens variabler
      • Ctrl-D för att avsluta (Ctrl-D är även kontrolltecknet för EOT, End of Transmission)

      Använda python interaktivt¶

      • Anropa funktioner, skapa instanser av klasser från fil(er) som laddats in i minnet.
      • Allt man kan göra från en textfil.
      • Kom dock ihåg att allt ligger kvar i minnet:
        • globala variabler
        • moduler som laddats in
        • funktioner som definierats
        • etc.

      Rätta buggar¶

      • syntaxfel
      • runtime-fel (inträffar under körning)
      • logiska fel

      Övning¶

      • flatten_and_sort_without_duplicates()
      • Ska platta till, sortera och ta bort dubletter från en nästlad lista med siffror (högst två nivåer)
      • T.ex. [[2], 1, 3, [3, 2]] → [1, 2, 3]

      • https://trinket.io/python3/10b5ddc7a4

      Kod. https://trinket.io/python3/10b5ddc7a4¶

      In [ ]:
      def flatten_and_sort_without_duplicates(list):
          """Return flattened and sorted values of list without duplicates."""
          # flatten contents of list, i.e. [1, [2]] -> [1, 2]
          new = []
          for e in list:
              if type(e) == list:
                  for e2 in e:
                      new.append(e2)
             else:
                  new.append(e)
      
          # sort values in new, [2, 3, 1, 3] -> [1, 2, 3, 3]
          sorted(new)
      
          # remove duplicates from sorted values e.g. [1, 2, 2, 3] -> [1, 2, 3]
          i = 0
          while i < len(new):
              # remove new[i] if it is equal to the subsequent value
              if new[i] = new[i+1]:
                  new.remove(new[i])
              i += 1
      
          return new