Hjelp til Python

Kjøring av pythonprogrammer

Python startes fra terminalen med kommandoen:

 python3

For å avslutte Python:

 exit()

For å kjøre et Python-program rett fra terminalen:

 python3 filnavn.py

For å redigere filen skriver man:

 vi filename.py

For å gjøre endringer trykker man på i (som står for insert). Etter at en har gjort de nødvendige endringer trykker man på Esc og så taster inn

  :wq //for å gå ut og lagre
  :q! //for å gå ut uten å lagre

Hvis man velger å kjøre scriptet i terminalen og scriptet skal produsere plots, er måten å gjøre det på å skrive

  plt.savefig('filename.png')

Lange beregninger

Hvis man har program som tar lang tid å kjøre kan man bruke Markov (se mer i menyen til venstre under 'Markov'). Hvis man ønsker å lagre plot fra scriptet som kjører i tmux må man skrive en ekstra linje med kode før man importerer pyplot men etter å ha importert matplotlib.

     matplotlib.use('Agg')
     

Man kan lese mer om dette her.

Filbehandling

Informasjonen er i bunn og grunn remser av heltall, som i seg selv kan representere så mangt. Vi trenger et tegnsett (eng: encoding) for å angi hvordan informasjonen skal tolkes, og det er viktig at tekstdataen leses med samme tegnsett som det ble skrevet med. For å sikre at programmet vårt kommuniserer på riktig vis bruker programmeringsspråkene såkalte fil-strømmer/fil-håndtak. (eng: file stream / file handle). Stdin, stdout og stderr er eksempler på disse:

  • Stdin brukes når vi ønsker å gi input til programmet fra tastaturet.
  • Stdout brukes når vi ønsker å skrive fra programmet til skjerm.
  • Stderr brukes når vi ønsker å skrive feilmeldinger fra programmet.

Stderr og stdout skriver gjerne begge til terminalen, men ved å ha to separate strømmer har vi større handlefrihet i å skille mellom feilmeldinger og vanlig output.

Disse overføringskanalene skriver til faste mottakere hver gang, og er gjerne automatisk åpne ettersom det å skrive til/fra skjerm er noe man gjør forholdsvis ofte. I python kan de også fås tilgang til gjennom sys-modulen. I python3 vil input() automatisk ta imot data fra sys.stdin, mens print som default skriver til sys.stdout. Her kunne vi imidlertid godt ha oppgitt en alternativ strøm ved f.eks.

 print(*objects, file=sys.stderr).

Hvis man ønsker å kommunisere med noe annet enn de faste strømmene, som f.eks. en fil, så må denne overføringskanalen åpnes manuelt. I python3 gjøres dette typisk ved

 f=open('filename','w')

Her returnerer open() et filhåndtak som vi tilegner f. 'w' angir modusen vi ønsker å håndtere filen under. I dette tilfellet vil vi skrive til filen med plassering angitt av 'filename'. Dette er en relativ eller absolutt filsti. Hvis vi bare skriver filnavnet tolker python det som at filen ligger i samme mappe som programmet. Om vi gir hele stien leter python relativt det aktuelle hjemmeområdet.

På Windows skrives filstier med \ som seperator, mens Unixsystemer bruker /. Hvis du ønsker at programmet ditt skal virke på begge typer systemer kan du bruke os.path.join()for å bruke systemspesifikk seperator:

 import os
 >>> os.path.join('usr', 'bin', 'spam')
 'usr/bin/spam'

Hvis filen ikke eksisterer fra før vil den lages under kallet til open(), og hvert kall med 'w' vil overskrive tidligere informasjon. Hvis vi heller ønsker å tilføye informasjon under hvert funksjonskall bruker vi 'a' i stedet. Om vi ikke spesifiserer modusen tolker open() det som at vi ønsker lese-modus 'r'. Til slutt kan vi åpne for både lesing og skriving med 'r+'.

open() antar i utgangspunktet at vi jobber med tekstfiler. Om vi arbeider med binærfiler må vi oppgi dette ved å legge til en 'b' i modusen som brukes.

Når vi er ferdig med å behandle filen er det god praksis å kalle f.close() for å frigi allokerte ressurser. Allokering/deallokering kan imidlertid automatiseres ved å utnytte at klassen til filhåndtaket definerer context-manager-protokollen. Dette betyr simpelten at f har tilgjengelig metodene f.enter() og f.exit(), som kalles ved henholdsvis inngang og utgang av en with-kodeblokk.

 with open('filename', 'w') as f:
   pass
 f.closed()

I det aktuelle tilfellet vil f.closed() returnere true, ettersom with-kallet sørger for automatisk allokering/deallokering.

Nyttige metoder for arbeid med tekstfiler:

  • Lese fra fil:
    • f.read(size):
      • Leser size karakterer og returnerer en tekststreng. Hvis size ikke spesifiseres leses hele filen.
      • Hvis man har nådd enden av filen returnerer read() en tom streng ""
    • list(f) eller f.readlines():
      • Returnerer en liste av linjene i filen.
    • f.readline():
      • Leser en enkelt linje fra tekstfilen inklusive newline '\n'.
    • Man kan også iterere gjennom hver linje i en fil:
 for line in f:
    print(line, end='')
  • Skrive til fil:
    • f.write(string):
      • Skriver string til filen, og returner det skrevne antallet karakterer.
      • Objekter av andre typer enn string må konverteres før de kan skrives til en tekstfil, likedans må de konverteres til bytes-objekter før de kan skrives til en binærfil.

Pickle-modulen og objektorientering

Hvis man ønsker å skrive kompliserte datastrukturer til en fil kan dette gjøres ved å serialisere objektet, som betyr å representere objektet som en binerstreng. Denne kan lagres som et eget bytes-objekt i python, eller skrives til en fil. I begge tilfeller behandles serealiseringen ved hjelp av pickle-modulen, som sørger for binærkonverteringen. Å skrive en binærrepresentasjonen av et objekt til en fil kan gjøres ved

pickle.dump(obj, file)

Her er file et filhåndtak som f.eks. er åpnet i 'wb'

Hvis du heller ønsker det serialiserte objektet som et eget bytes-objekt kan du bruke

bytes_obj=pickle.dumps(obj)

For å konvertere en lagret binærrepresentasjon tilbake til et objekt, brukes henholdsvis

 pickle.load(file)
 

og

 pickle.loads(bytes_obj). 
 

Her må file være åpnet med 'rb'.

Den foregående diskusjonen antar at objektene du prøver å pickle kan serialiseres. Brukerdefinerte klasser og funksjoner vil være serialiserbare, så lenge de defineres i top-scopet, dvs. ikke nestet i andre funksjoner eller klasser. I python regnes både funksjoner og klassedefinisjoner som objekter som kan ha state, dvs. at selve "definisjonene" A og func regnes som egne objekter som henholdsvis har en A.__dict__ og en func.__dict__. Pickle kopierer imidlertid ikke disse definisjonsobjektene by value, men kun by reference, hvilket innebærer at pickle i virkeligheten lagrer en peker til disse objektene. Dette innebærer at dicten som assosieres med disse definisjonsobjektene ikke kopieres. Hvis du har statiske variabler som alle instanser av en klasse har tilgang til, så vil dermed ikke disse kopieres, og du vil være avhengig av at de er tilgjengelig i scopet det serialiserte objektet unpickles i. En god regel, som nok de fleste følger, er å definere funksjoner og klasser som skal pickles i top-scopet av koden din. På denne måten har alle scopes i koden din tilgang til definisjonsobjektene og deres dictionaries. Hvis du vil unpickle et serialisert objekt i en annen modul, må du sørge for å importere modulen som definerer funksjonen/klassene du har serialisert, slik at A.__dict__ eller func.__dict__ også er tilgjengelig i den nye koden.

import module importerer som standard alle funksjoner definert i top-scopet, men man kan selvfølgelig også bruke from module import A, func

Vi ser nærmere på

 a=A(*args, **kwargs) 

Dette utfører implisitt

 a=object.__new__(A).__init(*args, **kwargs)

A er i dette tilfellet klassedefinisjonsobjektet, og A(*args, **kwargs) gjennomfører først av et konstruksjonskall og siden et initialiseringskall. Føst kalles object.__new__(cls), der A gis som parameter. Her returnerer funksjonen en anonym instans av A, som det automatisk kalles

 A.__init__(self, *args, **kwargs)

på.

object representerer den globale superklassen i python som definerer defaultmetodene som alle brukerdefinerte klasser arver. En brukerdefinert klasse redefinerer sjeldent __new__(), siden metoden i utgangspunktet returnerer instanser av klassen, som jo typisk er det man er interessert i. self-parameteren som gis til __init__() refererer til den nye anonyme instansen, mens *args og **kwargs er argumentene du ønsker å initialisere instansen med.

Når man ønsker å pickle instanser av en klasse, (det vi generelt refererer til som objekter av en klasse, og som ikke må forveksles med selve klassedefinisjonsobjektet) er situasjonen litt annerledes. Ettersom instanser typisk har state som ikke er ment å være tilgjengelig globalt, eller som bare gir mening for det aktuelle objektet, er det hensiktsmessig at disse kopieres by value, ettersom det kan være at vi ønsker å endre på objectets dictionary uten å endre state i alle andre instanser. For å pickle instanser er vi derfor avhengig av at objektene inneholder state som pickle-modulen kan serialisere. Generelt initialiseres instansers state i A.__init__(self, *args, **kwargs), og denne staten lagres i a.__dict__.

Det vil heves exceptions som PicklingError og UnpicklingError hvis man prøver å dumpe/loade ugyldige objekter/pickles. Det er verdt å merke seg at built-in types/funksjoner er serialiserbare, og built-in beholdere som inneholder serialiserbare objects vil også være serialiserbare. Videre vil objekter som bruker andre brukerdefinerte klasser/funksjoner være serialiserbare så lenge disse klassene/funksjonene som brukes er definert i top-scopet, dvs. kan refereres til.

Objekter som inneholder andre klasseinstanser vil være serialiserbare så lenge disse instansene igjen er serialiserbare. Det er klart at hvis man pickler en rekursiv datastruktur som inneholder referanser til seg selv, eller en datastruktur med objekt sharing, der det å kopiere et objekt innebærer å kopiere et objekt som videre må kopiere et objekt som videre må kopiere et objekt.., så må man passe på at dette ikke fører til en RecursionError eller lignende, dvs. vi må kreve at rekken av objekter som kopieres terminerer. Pickle holder styr på hvilke objekter som allerede er kopiert, slik at objektene ikke kopieres mer enn nødvendig.

Det er verdt å merke seg at ved å pickle en instans, og siden unpickle denne til en ny variabel, vil vi effektivt ha en ny instans, uten referanser til det forrige objektet. Pickle kan altså brukes til å ta deep copies av objekter, selv om det nok er bedre å bruke copy-modulen til dette. Når en serialisert instans unpickles vil ikke __init__() kalles på den nye instansen. Pickle vil først gjøre et implisitt kall til A.__new__() som returnerer en ikke-initialisert klasseinstans. Videre vil pickle direkte oppdatere det anonyme objektet med staten som ble lagret i den serialiserte instansen, dvs. det som lå i a.__dict__() når vi brukte pickle.dumps(a). Dette vil typisk være behandlet state som gjerne er forskjellig fra det du ville initialisert a med.

Hvis du ønsker å pickle en instans som inneholder ikke-serialiserbar state kan du spesifisere hvilken state som skal lagres ved å definere __getstate__() i klassedefinisjonen. Dette vil override default oppførsel som er å kopiere hele a.__dict__.

En kan f.eks. skrive

 class A:
 ...
   def __getstate__(self):
	   del self.__dict__['b']          
	   return self.__dict__

hvis a.b er ikke-serialiserbar for alle a av typen A.

2022-09-19, Per Kristian Hove