Ga naar hoofdinhoud

Een uitgebreide gids voor het gebruik van pathlib in Python voor bestands­systeem­manipulatie

Ontdek de voordelen van Python pathlib ten opzichte van de os-module door padobjecten, padcomponenten en veelvoorkomende padbewerkingen voor interactie met het bestandssysteem te verkennen.
· 9 min lezen

Verken met AI

Tot voor kort was werken met het bestandssysteem in Python berucht lastig. Ontwikkelaars worstelden vaak met onjuiste paden, die foutgevoelig waren omdat ze lange strings als invoer vereisten. Ook meldden ontwikkelaars dat hun code vaak stukging door inconsistenties tussen verschillende besturingssystemen.

Gelukkig is in Python 3.4 de module pathlib aan de standaardbibliotheek toegevoegd. pathlib biedt een elegante oplossing voor het omgaan met paden in het bestandssysteem via een langverwachte objectgeoriënteerde aanpak, en zorgt bovendien voor platformonafhankelijk gedrag.

In deze uitgebreide tutorial leer je de functies van de module pathlib kennen om je dagelijkse interactie met je bestandssysteem te ondersteunen. Met pathlib profiteer je van efficiënte workflows en eenvoudige gegevensopvraag. pathlib is in de loop der jaren flink volwassen geworden, en wij zijn bijgebleven zodat jij dat niet hoeft te doen. Laten we beginnen.

Python os-module vs. pathlib

Vóór Python 3.4 was de meer traditionele manier om met paden om te gaan via de module os. Hoewel de module os ooit zeer effectief was, begint hij zijn leeftijd te tonen.

We kunnen de unieke waarde van pathlib laten zien aan de hand van een veelvoorkomende taak in data science: hoe vind je alle png-bestanden in een bepaalde map en al zijn submappen.

Als we de module os zouden gebruiken, zouden we ongeveer de volgende code schrijven:

import os
dir_path = "/home/user/documents"
files = [
os.path.join(dir_path, f)
for f in os.listdir(dir_path)
if os.path.isfile(os.path.join(dir_path, f)) and f.endswith(".png")
]

Hoewel deze code de directe taak oplost om onze png-bestanden te vinden, toont hij een aantal grote nadelen van de module os. Om te beginnen is de code lang en bijna onleesbaar, wat zonde is, want dit is een relatief eenvoudige bewerking. Ten tweede gaat onze code uit van kennis van list comprehensions, wat je niet als vanzelfsprekend moet beschouwen. Ten derde bevat de code string-bewerkingen, die foutgevoelig zijn. En bovendien is de code niet erg beknopt. 

Als we in plaats daarvan de module pathlib zouden gebruiken, zou onze code veel eenvoudiger zijn. Zoals we al noemden, biedt pathlib een objectgeoriënteerde aanpak voor het omgaan met paden in het bestandssysteem. Kijk maar:

from pathlib import Path

# Create a path object
dir_path = Path(dir_path)

# Find all text files inside a directory
files = list(dir_path.glob("*.png"))

Deze objectgeoriënteerde programmering organiseert code rond objecten en hun interacties, wat leidt tot meer modulaire, herbruikbare en onderhoudbare code. Als je niet bekend bent met objectgeoriënteerd programmeren, is het de moeite waard om het te leren met onze cursus Object-Oriented Python Programming. 

Werken met Path-objecten in Python

De bibliotheek pathlib draait om zogenaamde Path-objecten, die paden in het bestandssysteem op een gestructureerde en platformonafhankelijke manier weergeven.

Eerder in deze tutorial haalden we de klasse Path uit de module pathlib naar onze huidige namespace met de volgende regel code:

from pathlib import Path

Nadat we de klasse Path uit pathlib hebben aangeroepen, kunnen we op meerdere manieren Path-objecten maken, onder andere op basis van strings, van andere Path-objecten, vanuit de huidige werkmap en vanuit de thuismap.

Laten we ze één voor één bekijken.

Padobjecten maken op basis van strings

We kunnen een Path-object maken door een string die een pad in het bestandssysteem voorstelt aan een variabele door te geven. Dit zet de stringrepresentatie van het bestandspad om in een Path-object.

file_path_str = "data/union_data.csv"

data_path = Path(file_path_str)

Padobjecten maken op basis van andere padobjecten

Bestaande Path-objecten kunnen dienen als bouwstenen voor het creëren van nieuwe paden. 

Dit doen we door een basismap, een datamap en een bestandsnaam te combineren tot één bestandspad. We moeten eraan denken om waar nodig een slash te gebruiken om onze Path-objecten uit te breiden.

base_path = Path("/home/user")
data_dir = Path("data")

# Combining multiple paths
file_path = base_path / data_dir / "prices.csv"  
print(file_path)
'/home/user/data/prices.csv'

Padobjecten maken vanuit de huidige werkmap

Hier kennen we de huidige werkmap toe aan de variabele cwd met de methode Path.cwd(). Zo kunnen we het pad ophalen van de werkmap waarin ons script draait. 

cwd = Path.cwd()

print(cwd)
'/home/bexgboost/articles/2024/4_april/8_pathlib'

Padobjecten maken vanuit de thuismap

We kunnen een pad opbouwen door onze thuismap te combineren met extra submappen. Hier combineren we onze thuismap met de submappen "downloads" en "projects". 

home = Path.home()

home / "downloads" / "projects"
PosixPath('/home/bexgboost/downloads/projects')

Belangrijk om te weten: De klasse Path zelf voert geen bewerkingen op het bestandssysteem uit, zoals padvalidatie of het aanmaken van mappen of bestanden. Ze is ontworpen om paden te representeren en te manipuleren. Om daadwerkelijk met het bestandssysteem te interacteren (bestaan controleren, lezen/schrijven), moeten we speciale methoden van Path-objecten gebruiken en in sommige geavanceerde gevallen hulp inroepen van de module os.

Werken met padcomponenten in Python

Attribuutwaarden van bestandspaden zijn verschillende eigenschappen en componenten van een pad die helpen bij het identificeren en beheren van bestanden en mappen binnen een bestandssysteem. Net zoals een fysiek adres verschillende onderdelen heeft, zoals huisnummer, stad, land en postcode, kan een pad in het bestandssysteem worden opgesplitst in kleinere componenten. pathlib laat ons deze componenten benaderen en manipuleren via padattributen met dotnotatie.

Werken met de rootmap

De root is de bovenste map in een bestandssysteem. In Unix-achtige systemen wordt deze weergegeven met een slash (/). In Windows is het doorgaans een stationsletter gevolgd door een dubbele punt, zoals C:.

image_file = home / "downloads" / "midjourney.png"

image_file.root
'/'

Werken met de bovenliggende map

De parent (bovenliggende map) bevat het huidige bestand of de huidige map. Hij staat één niveau hoger ten opzichte van de huidige map of het huidige bestand. 

image_file.parent
PosixPath('/home/bexgboost/downloads')

Werken met de bestandsnaam

Dit attribuut geeft de volledige bestandsnaam, inclusief extensie, als string terug.

image_file.name
'midjourney.png'

Werken met de bestandsextensie

Het attribuut suffix geeft de bestandsextensie terug, inclusief de punt, als string (of een lege string als er geen extensie is).

image_file.suffix
'.png'

Werken met de filenaam (stem)

De stem geeft de bestandsnaam zonder de extensie. Werken met de stem kan handig zijn bij het converteren van bestanden naar andere formaten.

image_file.stem
'midjourney'

Opmerking: Op een Mac zijn paden hoofdlettergevoelig, dus /Users/username/Documents en /users/username/documents zijn verschillend. 

Het attribuut parts van pathlib

We kunnen het attribuut .parts gebruiken om een Path-object in zijn componenten op te splitsen.

image_file.parts
('/', 'home', 'bexgboost', 'downloads', 'midjourney.png')

Het attribuut parents van pathlib

Het attribuut parents, dat een generator teruggeeft, zet deze componenten om in Path-objecten. 

list(image_file.parents)
[PosixPath('/home/bexgboost/downloads'),
PosixPath('/home/bexgboost'),
PosixPath('/home'),
PosixPath('/')]

Veelvoorkomende padbewerkingen met pathlib

Path-objecten hebben veel methoden waarmee je efficiënt met mappen en hun inhoud kunt werken. Laten we eens kijken hoe je enkele van de meest voorkomende bewerkingen uitvoert.

Mappen weergeven

De methode iterdir() laat je itereren over alle bestanden en submappen in een map. Deze is bijzonder handig om alle bestanden in een map te verwerken of per item een bewerking uit te voeren.

cwd = Path.cwd()
 for entry in cwd.iterdir():
# Process the entry here
...
# print(entry)

Omdat iterdir() een iterator teruggeeft, worden items on-demand opgehaald terwijl je door de lus gaat.

De methode is_dir()

De methode is_dir() geeft True terug als het pad naar een map wijst, anders False.

for entry in cwd.iterdir():
   if entry.is_dir():
      print(entry.name)
.ipynb_checkpoints
data
images

De methode is_file()

De methode .is_file() geeft True terug als het pad naar een bestand wijst, anders False.

for entry in cwd.iterdir():
   if entry.is_file():
      print(entry.suffix)
.ipynb
.txt

De methode exists()

Omdat Path-objecten alleen paden representeren, moet je soms controleren of een pad bestaat met de methode .exists():

De methode .exists() controleert of een pad bestaat. Dit is handig omdat Path-objecten bestanden en mappen kunnen representeren die misschien wel of niet daadwerkelijk in het bestandssysteem aanwezig zijn.

image_file.exists()
False

Paden maken en verwijderen

pathlib biedt ook functionaliteit voor het maken en verwijderen van bestanden en mappen. Zo werkt het.

De methode mkdir()

De methode mkdir() maakt een nieuwe map op het opgegeven pad. Standaard wordt de map in de huidige werkmap aangemaakt.

from pathlib import Path

data_dir = Path("new_data_dir")

# Create the directory 'new_data_dir' in the current working directory
data_dir.mkdir()

De methode mkdir(parents=True)

De methode mkdir(parents=True) is vooral handig wanneer je een mappenstructuur wilt maken waarbij sommige bovenliggende mappen nog niet bestaan. Door parents=True in te stellen, worden alle benodigde bovenliggende mappen onderweg aangemaakt.

sub_dir = Path("data/nested/subdirectory")

# Create 'data/nested/subdirectory', even if 'data' or 'nested' don't exist
sub_dir.mkdir(parents=True)

Houd er rekening mee dat mkdir() een uitzondering geeft als er al een map met dezelfde naam bestaat.

Path('data').mkdir()
FileExistsError: [Errno 17] File exists: 'data'

De methode unlink()

De methode unlink() verwijdert een bestand dat wordt weergegeven door het Path-object permanent. Het is aan te raden te controleren of een bestand bestaat voordat je deze methode uitvoert om een fout te voorkomen.

to_delete = Path("data/prices.csv")

if to_delete.exists():
   to_delete.unlink()
   print(f"Successfully deleted {to_delete.name}")
Successfully deleted prices.csv

De methode rmdir()

De methode rmdir() verwijdert een lege map. Onthoud dat rmdir() alleen werkt om lege mappen te verwijderen. De eenvoudigste manier om een niet-lege map te verwijderen is met de shutil-bibliotheek of via de terminal.

empty_dir = Path("new_data_dir")

empty_dir.rmdir()

Let op: Wees voorzichtig met unlink() of rmdir(), want de resultaten zijn permanent.

Geavanceerde padmanipulatie

Laten we doorgaan met enkele geavanceerde concepten voor padmanipulatie en hoe je die in de praktijk toepast met pathlib.

Relatieve vs. absolute paden

We beginnen met het verschil tussen absolute en relative paden, want dat komt vaak terug.

Relatieve paden

Relatieve paden geven de locatie van een bestand of map aan ten opzichte van de huidige map, vandaar de term relatief. Ze zijn kort en flexibel binnen je project, maar kunnen verwarrend zijn als je de werkmap wijzigt.

Ik heb bijvoorbeeld een map images in mijn huidige werkmap, met daarin het bestand midjourney.png.

image = Path("images/midjourney.png")

image
PosixPath('images/midjourney.png')

De bovenstaande code werkt nu, maar als ik het notebook dat ik gebruik naar een andere locatie verplaats, zal dit fragment stukgaan omdat de map images niet met het notebook is meeverplaatst.

Absolute paden

Absolute paden geven de volledige locatie van een bestand of map aan vanaf de root van het bestandssysteem. Ze zijn onafhankelijk van de huidige map en bieden een duidelijk referentiepunt voor elke gebruiker, overal op het systeem.

image_absolute = Path("/home/bexgboost/articles/2024/4_april/8_pathlib/images/midjourney.png")

image_absolute
PosixPath('/home/bexgboost/articles/2024/4_april/8_pathlib/images/midjourney.png')

Zoals je ziet kunnen absolute paden behoorlijk lang zijn, vooral in complexe projecten met geneste boomstructuren. Daarom geven de meeste mensen de voorkeur aan kortere relatieve paden.

Methode resolve

pathlib biedt methoden om relatieve paden om te zetten naar absolute met de methode resolve().

relative_image = Path("images/midjourney.png")

absolute_image = relative_image.resolve()

absolute_image
PosixPath('/home/bexgboost/articles/2024/4_april/8_pathlib/images/midjourney.png')

We kunnen ook de andere kant op: als we een absoluut pad hebben, kunnen we het op basis van een referentiemap omzetten naar een relatief pad.

relative_path = Path.cwd()

absolute_image.relative_to(relative_path)
PosixPath('images/midjourney.png')

Globbing

Om globbing te illustreren, keren we terug naar het voorbeeld aan het begin van het artikel, waar we code schreven om alle png-bestanden in een bepaalde map te vinden.

files = list(dir_path.glob("*.png"))

pathlib gebruikt de ingebouwde module .glob() om efficiënt te zoeken naar bestanden die overeenkomen met een specifiek patroon in een willekeurige map. Deze module is erg handig bij het verwerken van bestanden met vergelijkbare namen of extensies.

De glob-methode werkt door een patroonstring met wildcards als invoer te accepteren en geeft een generatorobject terug dat overeenkomende Path-objecten on-demand oplevert:

  • *: Komt overeen met nul of meer tekens.

  • ?: Komt overeen met precies één willekeurig teken.

  • []: Komt overeen met een bereik aan tekens tussen blokhaken (bijv. [a-z] komt overeen met elke kleine letter).

Om te illustreren: laten we proberen alle Jupyter-notebooks in mijn articles-map te vinden.

articles_dir = Path.home() / "articles"

# Find all scripts
notebooks = articles_dir.glob("*.ipynb")

# Print how many found
print(len(list(notebooks)))
0

De methode .glob() vond geen notebooks, wat op het eerste gezicht verrassend lijkt omdat ik meer dan 150 artikelen heb geschreven. De reden is dat .glob() alleen in de opgegeven map zoekt, niet in de submappen.

We kunnen dit oplossen met een recursieve zoekopdracht. Daarvoor gebruiken we de methode rglob(), die een vergelijkbare syntax heeft:

notebooks = articles_dir.rglob("*.ipynb")

print(len(list(notebooks)))
357

Deze keer heeft onze code alle 357 bestanden gevonden.

Werken met bestanden

Zoals we hebben gezien, representeren Path-objecten alleen bestanden, maar voeren ze geen bewerkingen op die bestanden uit. Ze hebben echter wel bepaalde methoden voor veelvoorkomende bestandsbewerkingen. In deze sectie laten we zien hoe je die gebruikt.

Bestanden lezen

De inhoud van bestanden lezen is een fundamentele handeling in veel Python-toepassingen. pathlib biedt handige kortere methoden om bestanden te lezen als tekst of als ruwe bytes.

De methode read_text() laat ons de inhoud van een tekstbestand lezen en het bestand sluiten.

file = Path("file.txt")

print(file.read_text())
This is sample text.

Voor binaire bestanden kunnen we in plaats daarvan de methode read_bytes() gebruiken.

image = Path("images/midjourney.png")

image.read_bytes()[:10]
b'\x89PNG\r\n\x1a\n\x00\x00'

Onthoud: bij gebruik van een read_*-methode is foutafhandeling belangrijk:

nonexistent_file = Path("gibberish.txt")

try:
   contents = nonexistent_file.read_text()
except FileNotFoundError:
   print("No such thing.")
No such thing.

Bestanden schrijven

Schrijven naar bestanden is net zo eenvoudig als lezen. Om te schrijven hebben we de methode write_text().

file = Path("file.txt")

file.write_text("This is new text.")
17
file.read_text()
'This is new text.'

Zoals we zien, overschrijft de methode write_text() de tekst. Hoewel er geen append-modus is voor write_text(), kunnen we read_text() en write_text() samen gebruiken om tekst aan het einde van het bestand toe te voegen.

old_text = file.read_text() + "\n"
final_text = "This is the final text."

# Combine old and new texts and write them back
file.write_text(old_text + final_text)

print(file.read_text())
This is new text.

This is the final text.

write_bytes() werkt op een vergelijkbare manier. Om te illustreren: laten we eerst de afbeelding midjourney.png dupliceren met een nieuwe naam.

original_image = Path("images/midjourney.png")

new_image = original_image.with_stem("duplicated_midjourney")

new_image
PosixPath('images/duplicated_midjourney.png')

De methode with_stem() geeft een bestandspad terug met een andere bestandsnaam (de suffix blijft wel hetzelfde). Zo kunnen we een originele afbeelding lezen en de inhoud naar een nieuwe afbeelding schrijven. 

new_image.write_bytes(original_image.read_bytes())
1979612

Bestanden hernoemen en verplaatsen

Naast de functie with_stem() om de stem van een bestand te hernoemen, biedt pathlib de methode rename() om vollediger te hernoemen. 

file = Path("file.txt")

target_path = Path("new_file.txt")

file.rename(target_path)
PosixPath('new_file.txt')

rename() accepteert een doelpad, dat een string of een ander padobject kan zijn.

Om bestanden te verplaatsen kun je de functie replace() gebruiken, die ook een bestemmingspad accepteert:

# Define the file to be moved
source_file = Path("new_file.txt")

# Define the location to put the file
destination = Path("data/new/location")

# Create the directories if they don't exist
destination.mkdir(parents=True)

# Move the file
source_file.replace(destination / source_file.name)
PosixPath('data/new/location/new_file.txt')

Lege bestanden aanmaken

pathlib laat ons lege bestanden aanmaken met de methode touch:

# Define new file path
new_dataset = Path("data/new.csv")

new_dataset.exists()
False
new_dataset.touch()

new_dataset.exists()
True

De methode touch is oorspronkelijk bedoeld om de modificatietijd van een bestand bij te werken, dus je kunt hem ook op bestaande bestanden gebruiken.

original_image.touch()

Wanneer je een bestandsnaam wilt reserveren voor later gebruik maar nog geen inhoud hebt om erin te schrijven, kun je met touch een leeg bestand maken. De methode is geïnspireerd op het Unix-commando touch.

Rechten en informatie over het bestandssysteem

Tot slot leren we hoe we bestandskenmerken kunnen opvragen met de methode .stat(). Als je bekend bent met os, zul je zien dat deze nieuwe methode dezelfde output heeft als os.stat().

image_stats = original_image.stat()

image_stats
os.stat_result(st_mode=33188, st_ino=1950175, st_dev=2080, st_nlink=1, st_uid=1000, st_gid=1000, st_size=1979612, st_atime=1714664562, st_mtime=1714664562, st_ctime=1714664562)

We kunnen via dotnotatie ook de bestandsgrootte ophalen.

image_size = image_stats.st_size

# File size in megabytes
image_size / (1024**2)
1.8879051208496094

Conclusie

De introductie van de module pathlib in Python 3.4 heeft het werken met het bestandssysteem voor ontwikkelaars aanzienlijk vereenvoudigd. Door een objectgeoriënteerde aanpak voor het omgaan met paden te bieden, levert pathlib een gestructureerde en duidelijke manier om paden in het bestandssysteem te representeren. pathlib biedt bovendien platformonafhankelijkheid, wat betekent dat pathlib scheidingstekens in paden consistent afhandelt op verschillende besturingssystemen, zodat onze code niet stukgaat op een nieuwe machine. Tot slot biedt pathlib een groot aantal beknopte en expressieve methoden voor veelvoorkomende bewerkingen in het bestandssysteem, zoals we hebben gezien. 

Onthoud: pathlib is één van de vele krachtige ingebouwde bibliotheken in Python. Door onze Data Scientist With Python Career Track, Python Programming Skill Track en Intro to Python for Data Science-cursussen te volgen, beheers je een brede set ingebouwde bibliotheken en word je een sterke Python-programmeur. 

Bedankt voor het lezen!


Bexruz (Bex) Tuychiev

Auteur

Bexruz (Bex) Tuychiev

Ik ben een contentmaker op het gebied van data science met meer dan 2 jaar ervaring en een van de grootste achterbannen op Medium. Ik schrijf graag uitgebreide artikelen over AI en ML met een vleugje sarcastische stijl, want je moet toch íets doen om ze wat minder saai te maken. Ik heb meer dan 130 artikelen en een DataCamp-cursus gemaakt, en er is nog een nieuwe in de maak. Mijn content is door meer dan 5 miljoen paar ogen gezien, van wie 20k volgers werden op zowel Medium als LinkedIn. 

Onderwerpen

Gerelateerd