Föreläsningsöversikt¶
- Kort om laboration 4
- Python i verkligheten
- paket
- moduler, namespaces
- virtuella miljöer
- disposition
- defaultvärden för funktionsargument
- Bryta ut funktioner
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)
Python i verkligheten¶
Python 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)
- Namnrymd: En samling namn (variabler, funktioner, etc.) som alla måste vara unika.
- Built-ins
- Inbyggda funktioner och variabler, t.ex.
printochlen. - Tillgängliga överallt i all Python-kod.
- Inbyggda funktioner och variabler, t.ex.
- 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
globalsresp.locals.- Båda funktionerna returnerar en
dictdär nycklarna är strängrepresentationer av de namn som finns i namnrymden och värdena är de värden som namnen refererar till.
- Båda funktionerna returnerar en
- På toppnivå i en modul (dvs utanför en funktionskropp) är den lokala och den globala namnrymden samma.
- Dvs om vi anropar
localsfrån toppnivå i modulen (eller i interaktivt läge) får vi alltså sammadictsom om vi anropatglobals.
- Dvs om vi anropar
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
- 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 randombinder namnrymdenrandomtill prefixetrandom.- Vi kan nu komma åt namnrymden med hjälp av prefixet, t.ex.
In [1]:
import random
print(random.randint(1, 10))
8
- 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 ä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
requestssom ni installerade i Lektion 3 är ett sådant paket. - På PyPI (Python Package Index) kan man hitta de flesta 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 i en kurs som denna.)
- 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
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
requestsunder Lektion 3.)
Alias och underpaket
- När vi importerar kan vi använda
asfö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
- 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 [ ]:
# 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])=}")
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.pysom 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.pyinnehåller kod som kan användas för ta fram information om en Pokémon via PokeAPI - När
pokedex.pykö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.
- Filen
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()
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¶
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
- Användar- och/eller projektspecifika virtuella miljöer med t.ex. modulen
- Överkurs (alternativ till
venvsom inte är inbyggda i Python):pyenvvirtualenv(tredjepartsbibliotek som inspireratvenv)pipenvpoetryconda
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¶
- importer
- globala variabler
- funktioner
- kod utanför funktioner (ofta i ett
__name__ == "__main__"-block).
In [2]:
import math
PRECISION = 3
def rounded_sqrt(value):
"""Compute the square root rounded to PRECISION decimal places."""
return round(math.sqrt(value), PRECISION)
if __name__ == "__main__":
print(rounded_sqrt(float(input(">>> "))))
>>> 7 2.646
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
- Används av många av funktionerna i
matplotlib(Laboration 4, Del 1) men även av funktioner vi använt tidigare - Exempel
open(filnamn) # är samma sak som
open(filnamn, 'r', -1, None, None, None, True, None)
Default-värden¶
- Syntax
In [3]:
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 [7]:
funktionsnamn(1, param3=2)
Cell In[7], line 1 funktionsnamn(1, param3=2, 3) ^ SyntaxError: positional argument follows keyword argument
Funktionsdefinitioner, defaultvärden¶
In [10]:
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("Yoda", "Tjena")
Hej Yoda! Hej Guido! Tjena Eggbert! Hej Tjena! Tjena Yoda!
Använd inte muterbara värden som defaultvärden!¶
In [11]:
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 [40]:
print(get_short_names(["1234", "12345", "12"]))
['12', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', '12', '12', '12', '12', '12', '12', '12', '12', '12', '12', '12', '12', '12']
In [27]:
print(get_short_names(["abcd", "abcde", "ab"]))
['12', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab', 'ab']
Lösning: använd istället None som defaultvärde, hantera i funktionen¶
In [41]:
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 [42]:
print(get_short_names(["1234", "12345", "12"]))
['12']
In [43]:
print(get_short_names(["abcd", "abcde", "ab"]))
['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
globalanvä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 - Varför blir det fel:
localvars.py - Varför blir det fel:
globalvars.py
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.
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()
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.