De `io`-module in Python

De `io`-module in Python

Dit artikel legt de io-module in Python uit.

We zullen de io-module in Python uitleggen met praktische voorbeelden.

YouTube Video

De io-module in Python

Invoer/uitvoer-verwerking vormt de basis voor allerlei dataoperaties, zoals bestanden, netwerken en standaard I/O. De io-module van Python biedt een set abstracte klassen die deze invoer/uitvoer-operaties verenigen. Het belangrijkste concept om deze module te begrijpen is het idee van een 'stream'.

Wat is een stream?

Een stream is een abstracte stroom voor het sequentieel en continu lezen en schrijven van data.

Bij het byte voor byte lezen van bestandsinhoud of het verzenden en ontvangen van gegevens via een netwerk, kan dit allemaal worden behandeld als datastromen.

Door dit mechanisme te abstraheren, kunnen bestanden, geheugen en netwerken—verschillende bronnen van I/O—worden behandeld met gemeenschappelijke bewerkingen zoals lezen en schrijven.

De io-module van Python biedt een uniforme interface voor streams, waardoor efficiënte verwerking van zowel tekst als binaire data mogelijk is.

Basisstructuur van de io-module

De io-module heeft een drie-lagen hiërarchie, afhankelijk van het type stream.

  1. Ruwe laag (RawIOBase)

    RawIOBase behandelt de laagst-niveau byte I/O, zoals bestandsdescriptors van het besturingssysteem en apparaten.

  2. Gebufferde laag (BufferedIOBase)

    BufferedIOBase biedt een cache (buffer) om de I/O-efficiëntie te verbeteren. BufferedReader en BufferedWriter zijn typische voorbeelden.

  3. Tekstlaag (TextIOBase)

    TextIOBase zet byte-reeksen om naar strings en regelt encoding. Meestal wordt bij het openen van een bestand met de functie open(), TextIOWrapper uit deze laag gebruikt.

Dankzij deze structuur scheidt de io-module tekst- en binaire I/O duidelijk, terwijl flexibele combinaties mogelijk zijn.

Basisstructuur van de io-module

RawIOBase behandelt besturingssysteem-bestandsdescriptors op de onderste laag, met BufferedIOBase die een buffer toevoegt, en de bovenste laag TextIOBase die stringconversies afhandelt.

1import io
2
3# Check the core base classes hierarchy
4print(io.IOBase.__subclasses__())
  • Deze code is bedoeld om de groep abstracte klassen die erven van IOBase te controleren. Je ziet TextIOBase, BufferedIOBase en RawIOBase, waarmee de hiërarchische structuur wordt bevestigd.

io.IOBase: De basisklasse van alles

IOBase is de abstracte basisklasse voor alle I/O-objecten, die gemeenschappelijke methoden zoals close(), flush() en seekable() definieert. Het wordt zelden direct gebruikt en meestal benaderd via afgeleide klassen.

1import io
2
3f = io.StringIO("data")
4print(f.seekable())   # True
5print(f.readable())   # True
6print(f.writable())   # True
7f.close()
  • Dit voorbeeld toont aan dat de gemeenschappelijke methoden van IOBase ook in hogere klassen kunnen worden gebruikt. seekable() en readable() zijn handig om de eigenschappen van een stream te controleren.

io.RawIOBase: De laagste laag

RawIOBase is de laag die het dichtst bij het besturingssysteem-bestandsdescriptor ligt en voert geen buffering uit. De typische implementatie is FileIO, die per byte leest en schrijft.

1import io, os
2
3# Create a low-level FileIO object (no buffering)
4fd = os.open('raw_demo.bin', os.O_RDWR | os.O_CREAT)
5raw = io.FileIO(fd, mode='w+')
6raw.write(b'abc123')
7raw.seek(0)
8print(raw.read(6))  # b'abc123'
9raw.close()
  • FileIO is een concrete implementatie van RawIOBase; alle lees- en schrijfoperaties gebeuren als bytes. De efficiëntie kan worden verbeterd door het te combineren met de bovenliggende BufferedIOBase-laag.

io.BufferedIOBase: Tussenlaag (met buffering)

BufferedIOBase is een tussenlaag die buffering uitvoert, waardoor schijf-toegang efficiënter wordt. De belangrijkste implementaties zijn BufferedReader, BufferedWriter, BufferedRandom en BufferedRWPair.

1import io
2
3# Create a buffered binary stream on top of a BytesIO (simulate file)
4base = io.BytesIO()
5buffered = io.BufferedWriter(base)
6buffered.write(b'Python IO buffering')
7buffered.flush()
8base.seek(0)
9print(base.read())  # b'Python IO buffering'
  • In dit voorbeeld worden gegevens die via BufferedWriter zijn geschreven tijdelijk in een geheugenbuffer opgeslagen, en worden ze daadwerkelijk naar de onderste laag geschreven bij het aanroepen van flush().

Voorbeeld van BufferedReader

BufferedReader is een alleen-lezen gebufferde stroom die efficiënt lezen ondersteunt met peek() en read().

1import io
2
3stream = io.BytesIO(b"1234567890")
4reader = io.BufferedReader(stream)
5print(reader.peek(5))   # b'12345' (non-destructive)
6print(reader.read(4))   # b'1234'
7print(reader.read(3))   # b'567'
  • peek() 'gluurt' alleen naar de data en verplaatst de pointer niet. Door het te combineren met read(), kun je buffering flexibel aansturen.

io.TextIOBase: Alleen-tekstlaag

TextIOBase is een abstractielaag voor het verwerken van tekenreeksen, waarbij intern decodering en codering wordt uitgevoerd. Een typische implementatieklasse is TextIOWrapper.

 1import io
 2
 3# Wrap a binary stream to handle text encoding
 4binary = io.BytesIO()
 5text_stream = io.TextIOWrapper(binary, encoding='utf-8')
 6text_stream.write("\u3053\u3093\u306B\u3061\u306F")
 7text_stream.flush()
 8
 9# Reset stream position
10binary.seek(0)
11
12# Read bytes once
13data = binary.read()
14
15# Show both raw bytes and decoded text
16print("Raw bytes:", data)
17print("Decoded text:", data.decode('utf-8'))
  • In dit voorbeeld encodeert TextIOWrapper de string naar UTF-8 en schrijft deze naar de onderliggende binair-stream.

Voorbeeld van lezen met TextIOWrapper

Decodering wordt automatisch uitgevoerd tijdens het lezen.

1import io
2
3binary_data = io.BytesIO("Python I/O".encode('utf-8'))
4text_reader = io.TextIOWrapper(binary_data, encoding='utf-8')
5print(text_reader.read())  # 'Python I/O'
  • TextIOWrapper dient als de basisklasse voor tekst-I/O en vormt de basis voor vrijwel alle bestandsoperaties op hoog niveau.

io.StringIO: In-geheugen tekststream

StringIO is een klasse waarmee je strings in het geheugen kunt behandelen alsof het bestanden zijn. Het is handig voor I/O-testen en tijdelijke gegevensgeneratie.

1import io
2
3text_buf = io.StringIO()
4text_buf.write("In-memory text stream")
5text_buf.seek(0)
6print(text_buf.read())  # 'In-memory text stream'
  • StringIO maakt bestandsachtige bewerkingen mogelijk zonder de schijf te gebruiken en wordt veel gebruikt bij unittests.

io.BytesIO: In-geheugen binair-stream

BytesIO is een in-geheugen bestandsklasse voor het verwerken van bytereeksen (bytes). Het is handig voor situaties zoals binaire verwerking of gegevenscompressie waarbij je geen bestanden wilt gebruiken.

1import io
2
3buf = io.BytesIO()
4buf.write(b'\x01\x02\x03')
5buf.seek(0)
6print(list(buf.read()))  # [1, 2, 3]
  • BytesIO heeft dezelfde interface als BufferedIOBase en kan als vervanger worden gebruikt voor veel bestand-API's.

Aangepaste streams (originele klassen maken)

De klassen in io zijn uitbreidbaar, zodat je je eigen streamklassen kunt maken. Hieronder staat een voorbeeld van een TextIOBase-subklasse die alle tekst in hoofdletters omzet bij het schrijven.

 1import io
 2
 3class UpperTextIO(io.TextIOBase):
 4    def __init__(self):
 5        self.buffer = ""
 6    def write(self, s):
 7        self.buffer += s.upper()
 8        return len(s)
 9
10u = UpperTextIO()
11u.write("hello io")
12print(u.buffer)  # "HELLO IO"
  • Zolang je je houdt aan het contract van TextIOBase, kun je gedrag naar wens op deze manier definiëren. Het is ook eenvoudig om streams uit te breiden voor specifiek gebruik, zoals bestanden en netwerken.

Samenvatting

De io-module organiseert invoer/uitvoer-verwerking in een hiërarchie van abstracte en concrete klassen.

  • RawIOBase is een klasse voor byte-I/O op besturingssysteemniveau.
  • BufferedIOBase is een klasse die een efficiënte bufferlaag biedt.
  • TextIOBase is een klasse die het lezen en schrijven van strings aanstuurt.
  • StringIO en BytesIO zijn klassen die in-geheugen streams leveren.

Door deze klassen te begrijpen kun je precies doorzien hoe het I/O-systeem van Python werkt en deze toepassen bij bestandsoperaties, netwerkcommunicatie en het ontwerpen van teststreams.

Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.

YouTube Video