TDDE44 Programmering, grundkurs¶

Föreläsning 4.1¶

Johan Falkenjack, johan.falkenjack@liu.se¶

Föreläsningsöversikt¶

  • Kort om laboration 4
  • Kort om terminologilektionen 12 mars
  • Parprogrammering
  • Skript i verkligheten
    • paket
    • virtuella miljöer
    • moduler, namespaces
    • disposition
    • defaultvärden för funktionsargument
  • Bryta ut funktioner
  • Mer om stränghantering och filläsning

Laboration 4¶

  • Tillämpning av det mesta ni lärt er hittills
    • skriva funktioner
    • bearbeta listor/dictionaries
    • läsa data från fil
    • strukturera kod
  • Rita diagram med matplotlib
  • Övning i att bryta ut funktioner och strukturera kod (Del 2)

Terminologilektion 12e mars¶

  • Uppdelat i 3 pass
    • 13.15 - 14.30 Y1.a och Y1.b
    • 14.30 - 15.45 Y1.c och MAT
    • 15.45 - 17.00 MED och TMA
  • Samling i P42 på utsatt tid
    • Kort genomgång av mig
    • Sedan följer ni en av assistenterna till anvisad sal

Parprogrammering¶

  • Det har vi, översiktligt.
  • Flera saker återkommer.
  • Antingen värt att upprepa eller värt att fördjupa.

Parprogrammering¶

  • Programmeringsmetod där två programmerare programmerar tillsammans i samma stycke kod samtidigt.
  • Populariserades genom utvecklingsmetodiken Extreme Programming (XP).
    • http://www.extremeprogramming.org/
  • Vanligt speciellt inom organisationer som jobbar med sk. agila utvecklingsmetodiker.
    • XP, Scrum, Kanban, etc.
    • TDDC88 - Programutvecklingsmetodik
  • Det räcker inte att två programmerare delar kod med varandra.
  • Idag vill alla kalla sig agila, oavsett om de är det eller ej.

Varför parprogrammering?¶

  • Fördelar:
    • Enskilda uppgifter slutförs snabbare än av en enskild programmerare
    • Mindre komplex kod som är enklare att underhålla
    • Speciellt komplexa problem där lösningen inte är given från start löses bättre av par
    • Lagom jämna par lär sig av varandra och utvecklas snabbare
  • Nackdelar:
    • Fler programmerartimmar för att slutföra projekt
    • $\;\;\;\;\rightarrow$ Mindre tid till testning och debuggning
    • Par av noviser skriver sällan produktionsvärdig kod
  • Nackdelarna inte så relevanta för undervisning.
  • Alla ska göra samma projekt, inte olika delar av ett större projekt.
  • Koden som produceras ska inte sättas i produktion utan är till för att ni ska lära er.

Parprogrammering i undervisning¶

  • Diskutera och bestäm er för vilket litet delproblem som ska göras
  • Föraren jobbar på det lilla delproblemet och skriver koden
    • Dvs, det är främst Föraren som har tangentbordet.

  • Navigatörens jobb är att aktivt granska koden som skrivs
  • Navigatörens jobb är inte att berätta för föraren exakt vad hen ska skriva, men kan komma med förslag

  • Byt roller ofta! Minst en gång varje halvtimme men helst oftare, använd timer om ni tenderar att glömma.
  • Ni behöver inte lösa delproblemet innan ni byter roller.
  • Fira när ni löst delproblem.

Tre faser och mönster¶

  • Planering: innan ni kör iväg
  • Kodproduktion: när koden skrivs
  • Omstart: när ni har kört fast
  • Tänk rekursivt här.

Planering¶

  • Både förslag och genomgång av existerande kod / instruktioner
  • Det är bra att säga "Jag förstår inte" eller "Jag vet inte"
  • Tveka inte om att be varandra förtydliga (om ni är oeniga, fråga assistent)
  • Uttryck med egna ord hur du uppfattar situationen
  • Undvik fraser som "du får se om en stund", försök att förklara för din partner på en gång
  • Anteckna så att ni kommer ihåg det ni kommer fram till

Kodproduktion¶

  • Föraren
    • berätta hur och vad du tänker
    • du kan stanna upp och prata med/fråga navigatören
    • undvik att bara sitta tyst och vara i din egna värld (soloprogrammering med åskådare)
  • Navigatören läser koden som skrivs och säger till direkt när hen
    • upptäcker enklare fel
    • inte förstår vad föraren gör, namngivning, etc.

Kodproduktion¶

  • Navigatören antecknar och sparar större funderingar till senare, t.ex.
    • potentiella buggar (t.ex. extremvärden som kan orsaka en krasch)
    • funderingar idéer på övergripande design
    • saker som ni missat vid planeringen
  • Det är OK att skicka tangentbordet mellan er
    • vissa saker kan vara lättare för navigatören att uttrycka i kod
    • byt roller om det känns bra att göra det men kom ihåg att balansera

Omstart¶

  • Ni kan byta fokus och prata om annat en kort stund för att kunna tackla problemet med öppen inställning
  • Gå igenom er kod se över det ni gjort
  • Försök ha målet i sikte medan ni identifierar hur ni ska göra för att komma vidare
  • Hitta lämpligt ställe att börja om på
  • Om ni inte kommer överrens, kan det vara lämpligt att ta en kort paus, gå på toa/fika etc, lämna arbetsplatsen
  • Ge varandra tid att tänka/gå igenom koden

Varning¶

  • Få par är perfekt matchade.
  • Om du är den (åtminstone just nu) svagare programmeraren, lämna inte över ansvaret på din partner.
  • Om du är den (åtminstone just nu) starkare programmeraren, kör inte över din partner.

  • Glöm inte att byta roller, men det är okej att det inte är 100 % balans, speciellt om den svagare eller mindre erfarna programmeraren får vara Förare en större del av tiden.
  • Skräckexemplet: När en student satt knäpptyst som navigatör och sedan gick igenom koden och "rättade" den.
  • Extremfallet: Raderade all kod som skrivits sedan han senast var förare och skrev om den.
  • Nej, koden han skrev var inte bättre. Alls.

Vad gör vi om det inte fungerar att parprogrammera?¶

  1. Gör uppgifterna enskilt.
  2. När båda är klara: Byt kod med varandra och granska.
  3. Diskutera eventuella skillnader.
  4. Sätt ihop en gemensam slutgiltig version som båda förstår.
  5. Redovisa gemensam version.
  6. Skicka in gemensam version i Sendlab.
  • Behöver inte bero på vare sig att man är ovänner eller för nära vänner.
  • Den här approachen funkar särskilt bra om båda är erfarna sedan tidigare, men kanske har väldigt olika mycket förkunskaper.

Skript i verkligheten¶

Skript i verkligheten¶

  • moduler, namnrymder och paket
  • paket
  • virtuella miljöer
  • moduler, namespaces
  • disposition: struktur på kod, returnera och använd. mönster för skript
  • defaultvärden för argument

Namnrymder¶

Namnrymd (eng. Namespace)

namespaces.svg
  • Namnrymd: En samling namn (variabler, funktioner, etc.) som alla måste vara unika.
  • Built-ins
    • Inbyggda funktioner och variabler, t.ex. print och len.
    • Tillgängliga överallt i all Python-kod.
  • Globala namnrymden
    • Alla namn som finns i den aktuella modulen, dvs. i den aktuella kodfilen.
    • Dvs. alla namn vi skapat på toppnivå i modulen (dvs. utanför funktioner).
  • Lokala namnrymden
    • Alla namn som finns inne i en funktion när den exekveras.
    • Dvs. alla parametrar och alla variabler som tilldelats inne i funktionen.
  • På toppnivå i en modul gäller att globala och lokala namnrymden är samma.

Inspektera namnrymder¶

  • Vi kan inspektera globala och lokala namnrymderna med funktionerna globals resp. locals.
    • Båda funktionerna returnerar en dict där nycklarna är strängrepresentationer av de namn som finns i namnrymden och värdena är de värden som namnen refererar till.
  • På toppnivå i en modul (dvs utanför en funktionskropp) är den lokala och den globala namnrymden samma.
    • Dvs om vi anropar locals från toppnivå i modulen (eller i interaktivt läge) får vi alltså samma dict som om vi anropat globals.

Exempel: globals och locals på toppnivå¶

  • Notera att de blir samma:
>>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}

Exempel: globals och locals i en funktion¶

  • Notera att de blir olika:
>>> def my_fun(param):
...     print(f"{locals()=}")
...     print(f"{globals()=}")
...
>>> my_fun("arg")
locals()={'param': 'arg'}
globals()={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'my_fun': <function my_fun at 0x7fd0f6d9f380>}

Exempel: globals och locals i en funktion igen¶

>>> def my_fun(param):
...     local_variable = "a local string"
...     print(f"{globals()=}")
...     print(f"{locals()=}")
...
>>> my_fun("arg")
globals()={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'my_fun': <function my_fun at 0x7fd0f6d9f380>}
locals()={'param': 'arg', 'local_variable': 'a local string'}

Import av modul

namespaces_import.svg
  • När vi importerar en modul med en vanlig import-sats så binder vi dess namnrymd till ett prefix i den lokala namnrymden.
    • (Ett prefix är bara en variabel vars värde är en sådan namnrymd.)
  • import random binder namnrymden random till prefixet random.
    • Vi kan nu komma åt namnrymden med hjälp av prefixet, t.ex.
In [ ]:
import random
print(random.randint(1, 10))
  • Testa att köra print(globals()) före och efter import-satsen.

Exempel: Efter import i en funktion¶

>>> def my_fun(param):
...     import math
...     print(f"{locals()=}")
...     print(f"{globals()=}")
...
>>> my_fun("arg")
locals()={'param': 'arg', 'math': <module 'math' from '/home/johsj47/miniconda3/envs/py3_11/lib/python3.11/lib-dynload/math.cpython-311-x86_64-linux-gnu.so'>}
globals()={'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'my_fun': <function my_fun at 0x7fd0f6d9f560>}

Paket

paket.svg
  • Paket är ett sätt att strukturera större namnrymder hierarkiskt.
  • Paket består av moduler och underpaket (som i sin tur kan bestå av moduler och underpaket, osv.)
    • Vi kan jämföra ett paket med en katalogstruktur.
  • Ofta används paket också synonymt med verktyg som skapats av andra Python-utvecklare och som utökar Python med ny funktionalitet.
    • Man kan jämföra dessa med expansioner eller DLCs till spel (fast oftast är de gratis och skapade av tredjepartsutvecklare, så det är kanske mer korrekt att jämföra dem med moddar).
    • Paketet requests som ni installerade i Lektion 3 är ett sådant paket.
    • På PyPI (Python Package Index) kan man hitta alla sådana paket som kan installeras med pip.

Paket för Python¶

  • Det finns många fritt tillgängliga paket att använda med Python (se t.ex. PyPI - the Python Package Index).
  • Finns ingen anledning att återuppfinna hjulet när det finns färdiga hjul att installera och använda.
    • (I riktiga projekt, dvs. Det finns självklart goda pedagogiska skäl att återuppfinna alla möjliga olika hjul.)
  • Exempel
    • Requests (hämta data från webben). https://requests.readthedocs.io/en/master/
    • NumPy (matrisberäkningar m.m.). https://numpy.org/
    • TensorFlow (maskininlärning). https://www.tensorflow.org/
    • Pandas (databearbetning). https://pandas.pydata.org/
    • Pillow (bildbehandling). https://python-pillow.org/
    • Flask (webbserver). https://palletsprojects.com/p/flask/
    • Beautiful Soup (extrahera data från webbsidor). https://www.crummy.com/software/BeautifulSoup/
    • OpenCV (bildbehandling). https://docs.opencv.org/master/index.html
  • När de väl är installerade är det ingen skillnad mellan inbyggda moduler och installerade paket från programmerarens perspektiv.

Installera paket (repetition)¶

  • Enkelt att installera paket:
    • $ python3 -m pip install <namn på paket>
    • eller
    • $ pip3 install <namn på paket>
    • (Detta bör ni ha gjort med requests under Lektion 3.)

Alias och underpaket

namespaces_alias.svg
  • När vi importerar kan vi använda as för att skapa ett lokalt alias, dvs ett alternativt prefix.
    • Ett sådant alias kan vara vad som helst, men det finns ofta starkt etablerade konventioner.
  • När vi skapar ett alias kan vi använda punktnotation för att importera bara ett specifikt underpaket:
import matplotlib.pyplot as plt

Några standardalias man kan stöta på¶

  • Inbyggda moduler
MODULE                  ALIAS       IMPORT STATEMENT
datetime                dt          import datetime as dt
multiprocessing         mp          import multiprocessing as mp
  • Externa paket
NumPy                   np          import numpy as np
SciPy                   sp          import scipy as sp
Pandas                  pd          import pandas as pd
Seaborn                 sns         import seaborn as sns 
TensorFlow              tf          import tensorflow as tf
Tkinter                 tk          import tkinter as tk

Importera till lokala namnrymden

namespaces_from.svg
  • Vi kan importera enskilda namn till den lokala namnrymden med hjälp av nyckelordet from, t.ex:
from math import sqrt
  • Vi behöver inget prefix, men varning för namnkonflikter.
In [1]:
# import the randint function from the random module to the local namespace
from random import randint, choice

# import the plot function from the matplotlib.pyplot module to the local namespace
from matplotlib.pyplot import plot

print(f"{randint(0, 100)=}")
print(f"{choice(['a', 'b', 'c'])=}")


# anrop till funktionen plot() från modulen matplotlib.pyplot
print(f"{plot([0, 1, 2, 3])=}")
randint(0, 100)=13
choice(['a', 'b', 'c'])='b'
plot([0, 1, 2, 3])=[<matplotlib.lines.Line2D object at 0x7f032e2248e0>]
No description has been provided for this image

Varning!!¶

  • Använd inte konstruktionen
from modulename import *
  • Stor risk för namnkonflikter.
  • Importerade namn kan överskugga (eng. shadow) inbyggda och globala namn och orsaka svårhittade buggar.
  • Exempel:
>>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}
>>> from math import *
>>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'acos': <built-in function acos>, 'acosh': <built-in function acosh>, 'asin': <built-in function asin>, 'asinh': <built-in function asinh>, 'atan': <built-in function atan>, 'atan2': <built-in function atan2>, 'atanh': <built-in function atanh>, 'cbrt': <built-in function cbrt>, 'ceil': <built-in function ceil>, 'copysign': <built-in function copysign>, 'cos': <built-in function cos>, 'cosh': <built-in function cosh>, 'degrees': <built-in function degrees>, 'dist': <built-in function dist>, 'erf': <built-in function erf>, 'erfc': <built-in function erfc>, 'exp': <built-in function exp>, 'exp2': <built-in function exp2>, 'expm1': <built-in function expm1>, 'fabs': <built-in function fabs>, 'factorial': <built-in function factorial>, 'floor': <built-in function floor>, 'fmod': <built-in function fmod>, 'frexp': <built-in function frexp>, 'fsum': <built-in function fsum>, 'gamma': <built-in function gamma>, 'gcd': <built-in function gcd>, 'hypot': <built-in function hypot>, 'isclose': <built-in function isclose>, 'isfinite': <built-in function isfinite>, 'isinf': <built-in function isinf>, 'isnan': <built-in function isnan>, 'isqrt': <built-in function isqrt>, 'lcm': <built-in function lcm>, 'ldexp': <built-in function ldexp>, 'lgamma': <built-in function lgamma>, 'log': <built-in function log>, 'log1p': <built-in function log1p>, 'log10': <built-in function log10>, 'log2': <built-in function log2>, 'modf': <built-in function modf>, 'pow': <built-in function pow>, 'radians': <built-in function radians>, 'remainder': <built-in function remainder>, 'sin': <built-in function sin>, 'sinh': <built-in function sinh>, 'sqrt': <built-in function sqrt>, 'tan': <built-in function tan>, 'tanh': <built-in function tanh>, 'trunc': <built-in function trunc>, 'prod': <built-in function prod>, 'perm': <built-in function perm>, 'comb': <built-in function comb>, 'nextafter': <built-in function nextafter>, 'ulp': <built-in function ulp>, 'pi': 3.141592653589793, 'e': 2.718281828459045, 'tau': 6.283185307179586, 'inf': inf, 'nan': nan}

Använda pythonfil som modul¶

  • Filer med pythonkod är moduler
  • I filen uppg1.py:
def load_csv(filename):
    # satser i funktionen
  • Import och användning av uppg1.py som modul från annan pythonfil i samma katalog
import uppg1
uppg1.load_csv("data.csv")

Vad händer när man importerar en modul?¶

  • Vid import körs alla rader från filen som importeras.
  • Eventuella problem
    • vi vill att viss kod körs när vi använder filen som ett skript
    • vi vill inte att skript-koden körs när vi importerar filen som en modul
  • Exempel
    • Filen pokedex.py innehåller kod som kan användas för ta fram information om en Pokémon via PokeAPI
    • När pokedex.py körs kommer den att titta på argumenten som skriptet fått
    • Vi skriver ett annat program som ska använda sig av Pokémon-data, vi vill därför använda funktionerna i pokedex.py, men vill inte att pokedex-skriptet körs.

Den nuvarande namnrymnden¶

  • Speciella variabeln __name__ innehåller namnet på namnrymden för koden som körs
  • Namnrymden för ett skript (huvudprogram) som körs är "__main__"
  • Namnrymden för en kod i en modul är namnet på modulen

  • Exempelanvändning: kör inte viss kod när en fil importeras:
# om vi kör filen som ett skript, kör programmet
# annars utvärderas endast funktionerna m.m. i filen
if __name__ == "__main__":
    main()

Exempel¶




utskrifter.py och skript.py¶

utskrifter.py¶

In [ ]:
print(f"\n{'#'*10} utskrifter.py körs {'#'*10}")

print(f"* __name__ i början av utskrifter.py: {__name__}")

def skriv_ut_namnrymd():
    print(f"* __name__ i funktionen skriv_ut_namnrymd(): {__name__}")

print("\nAnropar skriv_ut_namnrymd() från utskrifter.py")
skriv_ut_namnrymd()

if __name__ == "__main__":
    print("\n__name__ har värdet '__main__' i utskrifter.py, kör skriv_ut_namnrymd()!")
    skriv_ut_namnrymd()
    
print(f"{'#'*10} utskrifter.py klar {'#'*10}\n")

skript.py¶

In [ ]:
#!/usr/bin/env python3
print(f"\n{'#'*10} skript.py körs {'#'*10}")

import utskrifter

print("__name__ i skript.py: {}".format(__name__))

print("Anropar utskrifter.skriv_ut_namnrymd() från skript.py")
utskrifter.skriv_ut_namnrymd()

if __name__ == "__main__":
    print("Anropar utskrifter.skriv_ut_namnrymd() IGEN från skript.py")
    utskrifter.skriv_ut_namnrymd()
    
print(f"\n{'#'*10} skript.py klar {'#'*10}\n")

Virtuella miljöer¶

No description has been provided for this image
  • Har inget med Virtual Reality att göra.

Problem vid installation av paket¶

  • Vissa projekt ska köras i miljöer med begränsade resurser
    • Dvs vi kan inte installera hur många onödiga paket som helst
  • Olika projekt kan behöva olika versioner av samma paket
  • Rättigheter kan saknas för att installera paket på systemnivå

  • Lösning
    • Användar- och/eller projektspecifika virtuella miljöer med t.ex. modulen venv

  • Överkurs (alternativ till venv som inte är inbyggda i Python):
    • pyenv
    • virtualenv (tredjepartsbibliotek som inspirerat venv)
    • pipenv
    • poetry
    • conda

venv - inbyggd modul¶

  • Skapar virtuell Python-miljö som kan aktiveras/avaktiveras.
  • Olika virtuella miljöer kan skapas för olika uppsättningar av Python-paket.
  • Endast virtuella miljöer för aktuell version av Python 3 kan skapas, t.ex. 3.10.12 på LiUs system.
    • (Med överkursverktygen kan även flera olika Python-versioner existera parallellt.)

venv¶

  • Skapa virtuell miljö:
    • $ python3 -m venv <katalog>
  • Aktivera virtuell miljö
    • $ source venv_mittprojekt/bin/activate
  • Av-aktivera virtuell miljö
    • $ deactivate
  • Installera paket i aktiv miljö
    • $ python3 -m pip install <paket>

Är det inte bättre att installera allt man behöver i en och samma miljö?¶

  • I verkligheten jobbar man ofta parallellt i flera olika projekt.
  • Alla projekt kan inte ha en produktionsmiljö där där massor onödiga saker finns.
  • Utvecklingsmiljön bör spegla produktionsmiljön i möjligaste mån.

Vanlig disposition för skript¶

  1. importer
  2. globala variabler
  3. funktioner
  4. kod utanför funktioner (ofta i ett __name__ == "__main__"-block).
In [3]:
import math

PRECISION = 3

def rounded_sqrt(value):
    return round(math.sqrt(value), PRECISION)

if __name__ == "__main__":
    print(rounded_sqrt(float(input(">>> "))))
>>> 5
2.236

Default-värden för parametrar¶

Default-värden¶

  • Funktioner med valfria argument; parametrar med defaultvärden
  • Används när man vill ha möjligheten att ange fler argument för att ändra en funktions beteende, men vill göra det möjligt att använda funktionen utan att ange alla argument
  • Kommer att användas av många av funktionerna i matplotlib (Laboration 4, Del 1)
  • Exempel
open(filnamn) # är samma sak som
open(filnamn, 'r', -1, None, None, None, True, None)

Default-värden¶

  • Syntax
In [ ]:
def funktionsnamn(param1, param2=10, param3=20):
    print(f"param1: {param1}, param2: {param2}, param3: {param3}")
    
  • Argument utan default-värde måste alltid anges
  • Argument med default-värde kan hoppas över eller anges explicit (nyckelordsargument)
  • Positionella argument måste användas före nyckelordsargument.
In [ ]:
funktionsnamn(1, param3=2)

Funktionsdefinitioner, defaultvärden¶

In [ ]:
def print_greeting1(name):
    print(f"Hej {name}!")
    
def print_greeting2(name="Guido"):
    print(f"Hej {name}!")
    
def print_greeting3(name="Eggbert", greeting="Hej"):
    print(f"{greeting} {name}!")
    
print_greeting1("Yoda")
print_greeting2()
print_greeting3(greeting="Tjena")
print_greeting3("Tjena")
print_greeting3("Eggbert", greeting="Tjena")

Använd inte muterbara värden som defaultvärden!¶

In [17]:
def get_short_names(name_candidates, short_names=[]):
    for name in name_candidates:
        if len(name) < 4:
            short_names.append(name)
    return short_names
In [25]:
print(get_short_names(["1234", "12345", "12"]))
['12', 'ab', 'ab', 'ab', 'ab', '12', '12', '12']
In [26]:
print(get_short_names(["abcd", "abcde", "ab"]))
['12', 'ab', 'ab', 'ab', 'ab', '12', '12', '12', 'ab']

Lösning: använd istället None som defaultvärde, hantera i funktionen¶

In [ ]:
def get_short_names(name_candidates, short_names=None):
    if short_names is None:
        short_names = []
    for name in name_candidates:
        if len(name) < 4:
            short_names.append(name)
    return short_names
In [ ]:
print(get_short_names(["1234", "12345", "12"]))
In [ ]:
print(get_short_names(["abcd", "abcde", "ab"]))

Bryta ut funktioner¶

Varför bryta ut funktion?¶

  • Minska upprepad kod
    • lättare att underhålla kod och uppdatera kod
    • lättare att läsa kod, inte lika lång
  • Minska väldigt snarlik kod
    • ersätt snarlika kodavsnitt med funktion som anpassar beteende givet argumenten
  • Underlätta läsning av kod – givet att bra funktionsnamn används.

Bryta ut funktioner, saker att tänka på¶

  • Scope och namespace: globala variabler och lokala variabler
  • Lokala variabler
    • alla variabler som definieras inne i en funktion (inkl. argumenten) är bara tillgängliga inuti funktionen
  • Globala variabler
    • variabler som definieras utanför funktioner är globala
    • om de ska användas inuti en funktion bör nyckelordet global användas
  • Undvik att använda globala variabler. Konstanter är ok, använd i så fall variabelnamn med endast versaler

Namngivning¶

  • Bra namn hjälper läsaren och programmeraren förstå vad koden gör.
  • Dåliga namn är intetsägande eller, ännu värre, vilseledande.
  • Använd verbliknande namn till funktioner
    • show_commands(), get_shortest_name(), load_data()
  • Använd substantivliknande namn till variabler; singular om det är ett värde, plural om det handlar flera värden (t.ex. listor och dictionaries)
    • command, commands, name, names

Exempel, scope¶

  • Globala och lokala variabler: localglobal.py
    • https://trinket.io/python3/923b3c723d
  • Varför blir det fel: localvars.py
    • https://trinket.io/python3/cfcddbf3ce
  • Varför blir det fel: globalvars.py
    • https://trinket.io/python3/f27c4c8c94

Bryta ut funktioner, exempel på tillvägagångssätt¶

  • Ta kod som upprepas och stoppa in den i en funktion
  • Lägg till argument till funktionen efter behov.
  • Byt ut upprepande kod mot anrop till funktionen du skapat.

  • Vid parprogrammering är en typisk uppgift för navigatören att påpeka när kod bör brytas ut till en egen funktion.

treasure_finder.py¶

In [ ]:
def treasure_finder():
    map1 = [55, 94, 39, 38, 57, 25, 53, 13, 57, 51]
    map2 = [71, 44, 98, 26, 18, 64, 37, 91, 81, 14]
    
    treasure_value = 81
    
    treasure_found = False
    pos = 0
    while pos < len(map1):
        if map1[pos] == treasure_value:
            treasure_found = True
            break
        pos += 1

    if treasure_found:
        print(f"Yay! Found treasure in map1 at position {pos}!")
    else:
        print("No treasure found in map1 :(")
        
    treasure_found = False
    pos = 0
    while pos < len(map2):
        if map2[pos] == treasure_value:
            treasure_found = True
            break
        pos += 1
        
    if treasure_found:
        print(f"Yay! Found treasure in map2 at position {pos}!")
    else:
        print("No treasure found in map2 :(")

treasure_finder()

treasure_finder_funcs.py¶

In [ ]:
def get_treasure_position(lst, treasure_value):
    pos = 0
    while pos < len(lst):
        if lst[pos] == treasure_value:
            return pos
        pos += 1
    return -1

def print_result(map_name, pos):
    if pos >= 0:
        print(f"Yay! Found treasure in {map_name} at position {pos}!")
    else:
        print(f"No treasure found in {map_name} :(")

def treasure_finder():
    map1 = [55, 94, 39, 38, 57, 25, 53, 13, 57, 51]
    map2 = [71, 44, 98, 26, 18, 64, 37, 91, 81, 14]
    treasure_value = 81
    treasure_pos = get_treasure_position(map1, treasure_value)
    print_result("map1", treasure_pos)
    treasure_pos = get_treasure_position(map2, treasure_value)
    print_result("map2", treasure_pos)

treasure_finder()

Riktlinjer för vad och hur man ska bryta ut¶

  • Försöka att se till att varje funktion endast gör en sak.
  • Försök att hålla funktioner korta (olika beroende på kodstil).
    • Ex. ~10 rader.

  • Låt funktioner ha verbliknande namn.
  • Låt variabler ha substantivliknande namn.
  • Använd förklarande namn.
  • Använd namn i plural för listor och dictionaries.

Bra heurestiker för att bryta ut funktioner¶

  • Försök se till så att en funktion bara gör en sak.
  • Är det svårt att skriva en lättförståelig men utförlig dokumentationssträng?
    • I så fall bör funktionen antagligen brytas upp i mindre delar som kan beskrivas enkelt.
  • Sätt en maxlängd på hur många rader en funktion får ha.
  • Sätt ett maxdjup på hur många nivåer av indentering en funktion får ha.
  • Undantag: Logiken i en villkorssats med många fall bör inte brytas ut, bryt istället ut kropparna till respektive klausul.

Återbesök: filläsning och stränghantering¶

Läsa från textfil¶

  • Öppna fil: file = open(filename)
  • Läs rader från öppen fil: lines = file.readlines()
  • Stäng öppen fil: file.close()
  • Bearbeta rader: t.ex. for line in lines:

  • Viktigt: Vad för datastruktur får vi från file.readlines()?
  • file.readlines() -> Lista av strängar

with-satsen - ett bättre sätt att öppna filer¶

  • with-satsen används för att förenkla resurs- och felhantering.
  • De flesta användningsområdena för with ligger utanför den här kursens omfattning, men:
with open('file_path') as file:
    lines = file.readlines()
  • with-satsen hanterar automatiskt stängning av öppnade filer när blocket avslutas, även om programmet skulle råka krascha.

Praktiska strängmetoder¶

str.split()¶

  • Skapar en lista av delsträngar, skapade genom att dela strängen vid en viss sekvens (blanktecken som default)
    • Blanktecken (eng. whitespace) representerar något av de olika blanka tecken som förekommer i text, t.ex. mellanslag, radbrytning och tab.
  • str.split()
In [ ]:
"""hej, hopp, nepp""".split()
  • För att ange en specifik sekvens som ska dela strängen skickar man med ett argument till den första parametern, sep.
    • Dvs. str.split(separator) eller str.split(sep=separator)
In [ ]:
"hej, hopp, nepp".split(", ")

str.join(lista)¶

  • Eftersom strängar är immutable så är det ineffektivt att genomföra många konkateneringar på raken.
  • Istället kan vi använda str.join(lista) som tar en lista av delsträngar och slår ihop dem till en sträng med strängen som metoden anropas på mellan varje delsträng.
In [ ]:
"".join(["hej", "hopp", "nepp"])
In [ ]:
" - ".join(["hej", "hopp", "nepp"])

str.join(lista), forts.¶

  • För att stegvis bygga upp en sträng i en loop kan vi alltså ersätta:
result = ""
for string in iterable:
    result += string
  • med:
partials = []
for string in iterable:
    partials.append(string)
result = "".join([partials])
  • Vilket, om vi ska göra många konkateneringar, kan ha mycket stor betydelse för hur snabbt programmet kör.

str.join(lista), exempel¶

  • En funktion som givet en sträng och en dictionary med ersättningsregler returnerar en ny sträng med tecken utbytta enligt reglerna.
    • Reglerna anges som t.ex. {'a': 'ä', 'o': 'ö'} för att byta ut alla 'a' mot 'ä' och alla 'o' mot 'ö'
In [30]:
def replacer(original, rules):
    partials = []
    for char in original:
        if char in rules.keys():
            partials.append(rules[char])
        else:
            partials.append(char)
    return "".join(partials)

print(replacer("nästäppa", {'t': 'd', 'p': 'b'}))

Jämförelse mellan direkt konkatenering och str.join()¶

In [27]:
def replacer_concat(original, rules):
    result = ""
    for char in original:
        if char in rules.keys():
            result += rules[char]
        else:
            result += char
    return result
In [32]:
import timeit
test_var = 'nästäppa'*100000
In [33]:
print("concat:  ", timeit.timeit("replacer_concat(test_var, {'t': 'd', 'p': 'b'})", number=100, globals=globals()))
print("join:    ", timeit.timeit("replacer(test_var, {'t': 'd', 'p': 'b'})", number=100, globals=globals()))
concat:   7.02377389796311
join:     5.890890292997938

Ännu snabbare lösning med split och join¶

In [34]:
def replacer_split_inplace(original, rules):
    result = original.split()
    for i, char in enumerate(result):
        if char in rules.keys():
            result[i] = rules[char]
    return ''.join(result)
In [35]:
print("concat:  ", timeit.timeit("replacer_concat(test_var, {'t': 'd', 'p': 'b'})", number=100, globals=globals()))
print("join:    ", timeit.timeit("replacer(test_var, {'t': 'd', 'p': 'b'})", number=100, globals=globals()))
print("inplace: ", timeit.timeit("replacer_split_inplace(test_var, {'t': 'd', 'p': 'b'})", number=100, globals=globals()))
concat:   7.28358013095567
join:     6.345866783056408
inplace:  0.0842719619977288
  • Inte alltid möjligt att göra så här.
  • Bygga upp stegvis är ibland nödvändigt, men join är fortfarande bättre.

str.strip(), str.lstrip(), str.rstrip()¶

  • Ta bort alla blanktecken i början och/eller slutet av strängar.
  • str.rstrip()
    • r för right, dvs tar bort blanktecken till höger, dvs i slutet av strängen
  • str.lstrip()
    • l för left, dvs tar bort blanktecken till vänster, dvs i början av strängen
  • str.strip()
    • Tar bort blanktecken i både början och slutet.

str.strip(), str.lstrip(), str.rstrip() exempel¶

In [ ]:
" hej vad heter du? \n".rstrip()
In [ ]:
" hej vad heter du? \n".lstrip()
In [ ]:
" hej vad heter du? \n".strip()

Läsa från textfil¶

  • Öppna fil
with open('file_path') as datafile:
  • Läs rader från öppen fil, t.ex.
for row in datafile:
  • eller
datarows = datafile.readlines()
for row in datarows:
  • För varje rad, bearbeta raden, t.ex.
    • hoppa över rad (använd continue)
    • ta bort radbryt från slutet av raden med str.rstrip()
    • konvertera från sträng till annan datatyp
    • dela upp raden med str.split()
    • stoppa in värden i lista