# Dependances pre-provisionnees (rdflib) : voir SemanticWeb/requirements.txt ; imports dans les cellules suivantes.SW-3b-Python-GraphOperations
Navigation : Index | << SW-3 C# | SW-4b Python >>
Opérations sur les graphes RDF en Python avec rdflib
Ce notebook est le twin Python du notebook SW-3-CSharp-GraphOperations (dotNetRDF). Il manipule des graphes RDF au moyen de la bibliotheque rdflib (open-source,
rdflib.readthedocs.io), qui implémente le modèle de données abstrait de RDF 1.1 (Cyganiak, Wood, Lanthaler (eds.), RDF 1.1 Concepts and Abstract Syntax, Recommandation W3C, 25 fevrier 2014). Les formats de sérialisation couverts (Turtle, RDF/XML, N-Triples, Notation 3, JSON-LD, TriG, N-Quads) sont tous des sérialisations normalisées de ce modèle abstrait.
Duree estimee : 50 minutes
A la fin de ce notebook, vous saurez : 1. Lire des fichiers RDF dans différents formats (Turtle, RDF/XML, N-Triples) avec rdflib 2. Ecrire des graphes RDF vers plusieurs formats via g.serialize() 3. Fusionner des graphes RDF avec l’opérateur += et | (sémantique d’ensemble) 4. Sélectionner des triplets selon différents critères avec g.triples() et les accesseurs dedies 5. Manipuler des listes RDF (créer, lire, modifier, supprimer) avec rdflib.collection.Collection
Prerequis
- Python 3.10+
- Avoir complete SW-2b-Python-RDFBasics
Note : Ce notebook est le miroir Python de SW-3 (dotNetRDF). La comparaison API dotNetRDF vs rdflib est explicitement documentee dans la section 6.
0. Installation et Imports
rdflib est l’equivalent Python direct de dotNetRDF. Elle fournit une API pythonique pour toutes les opérations RDF : parsing, serialization, sélection, listes.
from rdflib import Graph, URIRef, Literal, BNode, Namespace
from rdflib.namespace import RDF, RDFS, XSD
from rdflib.collection import Collection
import io
try:
RDFLIB_AVAILABLE = True
import rdflib
print(f"rdflib {rdflib.__version__} pret.")
print()
print("Modules charges :")
print(f" Graph, URIRef, Literal, BNode, Namespace (noeuds RDF)")
print(f" RDF, RDFS, XSD (vocabulaires W3C)")
print(f" Collection (listes RDF)")
except ImportError:
RDFLIB_AVAILABLE = False
print("rdflib non disponible. Installez avec : pip install rdflib")rdflib 7.6.0 pret.
Modules charges :
Graph, URIRef, Literal, BNode, Namespace (noeuds RDF)
RDF, RDFS, XSD (vocabulaires W3C)
Collection (listes RDF)
Interpretation : imports
| Module rdflib | Rôle | Equivalent dotNetRDF |
|---|---|---|
Graph |
Conteneur de triplets (ensemble) | VDS.RDF.Graph |
URIRef / BNode / Literal |
Types de noeuds | IUriNode / IBlankNode / ILiteralNode |
RDF, RDFS, XSD |
Namespaces W3C pre-configures | UriFactory + g.NamespaceMap |
Collection |
Listes RDF (rdf:first / rdf:rest) |
VDS.RDF.Extensions.GetListItems |
Les noeuds rdflib sont independants du graphe (
URIRef("...")est autonome), contrairement a dotNetRDF ou les noeuds sont créés viag.CreateUriNode(...)(lies au graphe).
1. Lecture de fichiers RDF
La méthode centrale de lecture est Graph.parse(), qui accepte un chemin de fichier, une URL, un fichier objet ou une chaîne. Formats supportes : turtle, xml, nt, n3, json-ld, trig, nquads. rdflib detecte automatiquement le format si l’extension du fichier est connue ou si un format= explicite est fourni.
Chaque format correspond a une Recommandation W3C distincte de la famille RDF 1.1 : Turtle (Beckett, Berners-Lee, Prud’hommeaux, 2014), N-Triples (Ackaert, Sporny, Kellogg, 2014 - la forme canonique ligne-par-triplet), Notation 3 (Berners-Lee, 2006, note non normative), RDF/XML (Beckett, 2004), JSON-LD (Sporny et al., 2014). TriG et N-Quads etendent ces formats aux datasets (graphes nommes).
# 1.1 Chargement depuis un fichier Turtle
if RDFLIB_AVAILABLE:
# Methode A : par chemin de fichier (le plus courant)
g = Graph()
g.parse("data/Example.ttl", format="turtle")
# Methode B : par flux (objet fichier en memoire)
with open("data/Example.ttl", "rb") as f:
data_bytes = f.read()
h = Graph()
h.parse(data=data_bytes, format="turtle")
print(f"Fichier : {len(g)} triplets")
print(f"Flux : {len(h)} triplets")
print(f"Identique: {len(g) == len(h)}")
else:
print("rdflib non disponible : lecture ignoree.")Fichier : 4 triplets
Flux : 4 triplets
Identique: True
Les deux méthodes sont equivalents. parse(data=...) accepte des bytes, utile quand les données RDF proviennent d’une reponse HTTP ou d’un flux in-memory.
# 1.2 Parsing depuis une chaine : format explicite vs auto-detection
if RDFLIB_AVAILABLE:
triple_nt = b"<http://example.org/a> <http://example.org/b> <http://example.org/c>."
# Auto-detection : rdflib devine le format (ici nt explicite car URI complets)
g1 = Graph()
g1.parse(data=triple_nt, format="nt")
print(f"Format explicite (nt) : {len(g1)} triplet")
# Parsing Turtle depuis une chaine
g2 = Graph()
g2.parse(data=b'<http://example.org/a> <http://example.org/b> <http://example.org/c> .', format="turtle")
print(f"Format explicite (ttl) : {len(g2)} triplet")
else:
print("rdflib non disponible : parsing chaine ignore.")Format explicite (nt) : 1 triplet
Format explicite (ttl) : 1 triplet
Lecture : une seule sémantique, deux syntaxes
La même ligne — un sujet, un prédicat, un objet — est parsée deux fois, une fois comme N-Triples (format="nt"), une fois comme Turtle (format="turtle"), et les deux graphes contiennent exactement 1 triplet. C’est la démonstration la plus courte du principe fondateur de RDF : le triplet est l’unité de sens, la syntaxe n’est qu’un habit. N-Triples est le sous-ensemble minimal de Turtle — une ligne par triplet, URI complètes, point final précédé d’une espace obligatoire ; Turtle ajoute préfixes, points-virgules et regroupements — mais une fois PARSÉ, tout retombe dans le même ensemble de triplets. Deux conséquences pratiques : un graphe chargé depuis .nt et le même chargé depuis .ttl sont indiscernables (même len(), même contenu), et choisir un format de sérialisation est un choix de lisibilité et de compaction — jamais de sémantique. Le format se discute à l’écriture ; à la lecture, seul l’ensemble de triplets compte.
# 1.3 Comptage : rdflib charge puis len(g)
# Note API : contrairement a dotNetRDF qui offre des "Handlers" (CountHandler, PagingHandler...)
# permettant de traiter un flux RDF SANS construire le graphe en memoire, rdflib construit
# toujours le graphe. Le comptage memoire-econome natif n'existe pas.
if RDFLIB_AVAILABLE:
g_animals = Graph()
g_animals.parse("data/animals.ttl", format="turtle")
print(f"animals.ttl : {len(g_animals)} triplets (comptes apres chargement en memoire)")
# Equivalent "flux sans graphe" : on peut iterer sur un Parser brut avec un sink,
# mais l'API est bas-niveau et rarement utilisee en pratique.
from rdflib.parser import Parser
print("Note : rdflib n'expose pas de Handler haut-niveau equivalent a CountHandler.")
print(" Pour les tres gros fichiers, voir rdflib.parser.Parser + sink custom.")
else:
print("rdflib non disponible : comptage ignore.")animals.ttl : 51 triplets (comptes apres chargement en memoire)
Note : rdflib n'expose pas de Handler haut-niveau equivalent a CountHandler.
Pour les tres gros fichiers, voir rdflib.parser.Parser + sink custom.
Interpretation : Méthodes de lecture
| Méthode rdflib | Avantage | Equivalent dotNetRDF |
|---|---|---|
g.parse("file.ttl", format="turtle") |
Format explicite, fiable | TurtleParser.Load(g, "file.ttl") |
g.parse(data=bytes, format="nt") |
En memoire, depuis HTTP | NTriplesParser.Load(g, StringReader(...)) |
g.parse("file.ttl") (sans format) |
Auto-detection par extension | StringParser.Parse(g, str) |
len(g) après parse() |
Comptage simple | CountHandler (sans chargement) |
Différence notable : dotNetRDF offre une API de Handlers (CountHandler, PagingHandler, FilterHandler) qui traitent les triplets en flux SANS construire le graphe en memoire - utile pour les très gros fichiers. rdflib construit systematiquement le graphe ; pour le streaming massif, il faut descendre a l’API Parser + sink bas-niveau.
Les
Graphrdflib sont reutilisables et l’opérationparse()peut etre appelee plusieurs fois (les triplets s’accumulent).
2. Ecriture de graphes RDF
La méthode centrale de serialization est Graph.serialize(format=...) qui retourne une chaîne. Pour ecrire directement dans un fichier, utiliser g.serialize(destination="file.ttl", format=...). Formats : turtle, nt, xml, json-ld, n3, trig, nquads.
# 2.1 Comparaison de formats de sortie
if RDFLIB_AVAILABLE:
g = Graph()
g.parse("data/Example.ttl", format="turtle")
nt_out = g.serialize(format="nt")
ttl_out = g.serialize(format="turtle")
print("=== N-Triples ===")
print(nt_out)
print("=== Turtle ===")
print(ttl_out)
else:
print("rdflib non disponible : ecriture ignoree.")=== N-Triples ===
_:nb41b3af932154cea84c8b1211f1ea0c5b1 <http://example.org/stuff/1.0/fullname> "Dave Beckett" .
_:nb41b3af932154cea84c8b1211f1ea0c5b1 <http://example.org/stuff/1.0/homePage> <http://purl.org/net/dajobe/> .
<http://www.w3.org/TR/rdf-syntax-grammar> <http://purl.org/dc/elements/1.1/title> "RDF/XML Syntax Specification (Revised)" .
<http://www.w3.org/TR/rdf-syntax-grammar> <http://example.org/stuff/1.0/editor> _:nb41b3af932154cea84c8b1211f1ea0c5b1 .
=== Turtle ===
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix ex: <http://example.org/stuff/1.0/> .
<http://www.w3.org/TR/rdf-syntax-grammar> ex:editor [ ex:fullname "Dave Beckett" ;
ex:homePage <http://purl.org/net/dajobe/> ] ;
dc:title "RDF/XML Syntax Specification (Revised)" .
Interpretation : Comparaison des formats de sortie
N-Triples : format verbeux - chaque triplet occupe une ligne complete avec les URIs en entier. 4 triplets = 4 lignes. Pas de prefixe, pas de compression.
Turtle : format compact - les prefixes sont définis une fois (@prefix), les noeuds blancs sont regroupes avec ; (même sujet) et les proprietes sont imbriquees avec [...]. Le même graphe tient en moins de lignes.
Le ratio de compression est ici d’environ 2:1 (N-Triples plus long que Turtle). Sur des graphes plus grands avec beaucoup de prefixes, le gain peut atteindre 5:1 ou plus.
# 2.2 RDF/XML : deux methodes pour obtenir une chaine
if RDFLIB_AVAILABLE:
# Methode 1 : serialize() retourne directement la chaine
xml_str = g.serialize(format="xml")
# Methode 2 : serialize() avec destination = fichier, puis relecture
import tempfile, os
tmp = os.path.join(tempfile.gettempdir(), "sw3b_example.rdf")
g.serialize(destination=tmp, format="xml")
with open(tmp, "r", encoding="utf-8") as f:
xml_from_file = f.read()
print(f"Methode 1 (chaine) : {len(xml_str)} car.")
print(f"Methode 2 (fichier) : {len(xml_from_file)} car.")
print(f"Identiques : {xml_str == xml_from_file}")
print(f"\n=== RDF/XML (extrait) ===\n{xml_str[:400]}...")
else:
print("rdflib non disponible : RDF/XML ignore.")Methode 1 (chaine) : 630 car.
Methode 2 (fichier) : 630 car.
Identiques : True
=== RDF/XML (extrait) ===
<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:ex="http://example.org/stuff/1.0/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
>
<rdf:Description rdf:nodeID="nb41b3af932154cea84c8b1211f1ea0c5b1">
<ex:fullname>Dave Beckett</ex:fullname>
<ex:homePage rdf:resource="http://purl.org/net/dajobe/"/>
</rdf:Description>
<r...
Les deux méthodes sont equivalents : serialize() sans destination retourne une chaîne, avec destination= ecrit dans un fichier. dotNetRDF proposait StringWriter.Write(g, writer) vs writer.Save(g, sw) - même distinction.
# 2.3 Configuration avancee des writers
# rdflib expose moins d'options que dotNetRDF (pas de IPrettyPrintingWriter / ICompressingWriter
# equivalents). Les options disponibles : indent (int) et sort_keys (booleen) sur certains formats.
if RDFLIB_AVAILABLE:
print("=== Turtle standard ===")
print(g.serialize(format="turtle"))
print("=== JSON-LD avec indentation 2 ===")
print(g.serialize(format="json-ld", indent=2))
else:
print("rdflib non disponible : configuration ignoree.")=== Turtle standard ===
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix ex: <http://example.org/stuff/1.0/> .
<http://www.w3.org/TR/rdf-syntax-grammar> ex:editor [ ex:fullname "Dave Beckett" ;
ex:homePage <http://purl.org/net/dajobe/> ] ;
dc:title "RDF/XML Syntax Specification (Revised)" .
=== JSON-LD avec indentation 2 ===
[
{
"@id": "http://www.w3.org/TR/rdf-syntax-grammar",
"http://example.org/stuff/1.0/editor": [
{
"@id": "_:nb41b3af932154cea84c8b1211f1ea0c5b1"
}
],
"http://purl.org/dc/elements/1.1/title": [
{
"@value": "RDF/XML Syntax Specification (Revised)"
}
]
},
{
"@id": "_:nb41b3af932154cea84c8b1211f1ea0c5b1",
"http://example.org/stuff/1.0/fullname": [
{
"@value": "Dave Beckett"
}
],
"http://example.org/stuff/1.0/homePage": [
{
"@id": "http://purl.org/net/dajobe/"
}
]
}
]
Interpretation : Writers et configuration
| Format rdflib | Equivalent dotNetRDF | Lisibilite | Taille |
|---|---|---|---|
g.serialize(format="nt") |
NTriplesWriter |
Faible | Verbose |
g.serialize(format="turtle") |
CompressingTurtleWriter |
Elevee | Compact |
g.serialize(format="xml") |
RdfXmlWriter |
Moyenne | Moyenne |
g.serialize(format="json-ld") |
JsonLdWriter |
Bonne (dev web) | Moyenne |
Différence notable : dotNetRDF expose des interfaces de configuration riches (IPrettyPrintingWriter, ICompressingWriter avec CompressionLevel, IHighSpeedWriter). rdflib est plus minimaliste : l’argument indent (JSON-LD) et la convention interne de compression Turtle sont les seuls leviers. Pour un contrôle fin equivalent, il faut descendre vers des sous-modules (ex : rdflib.plugins.serializers.turtle).
Tous les formats rdflib sont accessibles via la méthode unique
g.serialize(format=...)- API uniforme, plus simple que dotNetRDF ou chaque writer a sa propre classe.
3. Fusion de graphes
rdflib implemente la sémantique d’ensemble des graphes RDF : l’union (opérateur | ou +=) combine deux graphes sans dupliquer les triplets identiques. Les blank nodes sont isoles par graphe (pas de collision).
# 3.1 Fusion de deux graphes
if RDFLIB_AVAILABLE:
g1 = Graph()
g2 = Graph()
g1.parse("data/Example.ttl", format="turtle")
g2.parse("data/animals.ttl", format="turtle")
somme = len(g1) + len(g2)
print(f"Graphe 1 (Example.ttl) : {len(g1)} triplets")
print(f"Graphe 2 (animals.ttl) : {len(g2)} triplets")
# Operateur += : union en place (equivalent de g1.Merge(g2) en dotNetRDF)
g1 += g2
print(f"Apres fusion (+=) : {len(g1)} triplets (somme theorique: {somme})")
print(f"Doublons supprimes : {somme - len(g1)}")
# Variante : operateur | retourne un NOUVEAU graphe (non destructif)
g_a = Graph(); g_a.parse("data/Example.ttl", format="turtle")
g_b = Graph(); g_b.parse("data/animals.ttl", format="turtle")
g_union = g_a | g_b
print(f"\nOperateur | (new graph): {len(g_union)} triplets (sources intactes: {len(g_a)}, {len(g_b)})")
else:
print("rdflib non disponible : fusion ignoree.")Graphe 1 (Example.ttl) : 4 triplets
Graphe 2 (animals.ttl) : 51 triplets
Apres fusion (+=) : 55 triplets (somme theorique: 55)
Doublons supprimes : 0
Operateur | (new graph): 55 triplets (sources intactes: 4, 51)
Interpretation : verification manuelle
Somme théorique : 4 (Example.ttl) + 51 (animals.ttl) = 55. Aucun triplet commun (les deux fichiers utilisent des namespaces disjoints : http://example.org/stuff/1.0/ vs http://example.org/animals#), donc 0 doublon supprime. Le résultat 55 confirme la sémantique d’ensemble.
| Aspect | rdflib | dotNetRDF |
|---|---|---|
| Union en place | g1 += g2 |
g1.Merge(g2) |
| Union non destructif | g1 \| g2 (nouveau graphe) |
g1.Merge(g2) copie explicite requise |
| Dédoublonnage | Automatique (set semantics) | Automatique |
| Blank nodes | Isolés par graphe | Renommés pour éviter collision |
- Les triplets identiques ne sont pas dupliques (sémantique d’ensemble RDF)
- Les blank nodes sont isoles par graphe d’origine (pas de collision)
+=modifieg1en place ;|retourne un nouveau graphe (API plus pythonique queMerge)
4. Sélection de triplets
Graph propose la méthode generique g.triples((s, p, o)) ou chaque composante peut etre None (joker). Des accesseurs dediees existent : g.subjects(p, o), g.predicates(s, o), g.objects(s, p), g.subject_instances, g.value() (singleton). Les résultats sont des générateurs composables avec les comprehensions Python (equivalent LINQ).
# 4.1 Selection par predicat et par sujet
if RDFLIB_AVAILABLE:
g = Graph()
g.parse("data/animals.ttl", format="turtle")
EX = Namespace("http://example.org/animals#")
# Selection par predicat : tous les triplets rdf:type
print("=== Triplets rdf:type ===")
type_count = 0
for s, p, o in g.triples((None, RDF.type, None)):
print(f" {s.n3(g.namespace_manager)} -> {o.n3(g.namespace_manager)}")
type_count += 1
print(f" ({type_count} triplets rdf:type)")
# Selection par sujet : toutes les proprietes de ex:rex
rex = URIRef("http://example.org/animals#rex")
print("\n=== Proprietes de ex:rex ===")
for s, p, o in g.triples((rex, None, None)):
print(f" {p.n3(g.namespace_manager)} -> {o.n3(g.namespace_manager)}")
else:
print("rdflib non disponible : selection ignoree.")=== Triplets rdf:type ===
ex:Animal -> rdfs:Class
ex:Mammal -> rdfs:Class
ex:Bird -> rdfs:Class
ex:Dog -> rdfs:Class
ex:Cat -> rdfs:Class
ex:Parrot -> rdfs:Class
ex:name -> rdf:Property
ex:age -> rdf:Property
ex:sound -> rdf:Property
ex:canFly -> rdf:Property
ex:rex -> ex:Dog
ex:buddy -> ex:Dog
ex:minou -> ex:Cat
ex:coco -> ex:Parrot
(14 triplets rdf:type)
=== Proprietes de ex:rex ===
rdf:type -> ex:Dog
ex:name -> "Rex"
ex:age -> "5"^^xsd:integer
ex:sound -> "Woof"
Interpretation : Résultats de la sélection
Sélection par predicat rdf:type : 14 triplets trouves - 6 declarations de classes (Animal, Mammal, Bird, Dog, Cat, Parrot), 4 declarations de proprietes (name, age, sound, canFly) et 4 instances (rex/Dog, minou/Cat, coco/Parrot, buddy/Dog). Verification manuelle sur animals.ttl : 6 lignes a rdfs:Class + 4 lignes a rdf:Property + 4 lignes a ex:<Classe> = 14. Confirme.
Sélection par sujet ex:rex : 4 proprietes - type (Dog), name (Rex), age (5), sound (Woof). Rex est un chien de 5 ans qui fait “Woof”.
Ces résultats illustrent que g.triples((None, RDF.type, None)) retourne tous les usages d’un predicat (types, instances), tandis que g.triples((rex, None, None)) decrit un noeud complet.
# 4.2 Selection combinee et comprehension Python (equivalent LINQ)
if RDFLIB_AVAILABLE:
EX = Namespace("http://example.org/animals#")
name_prop = EX.name
print("=== Noms des animaux ===")
for s, p, o in g.triples((None, name_prop, None)):
print(f" {s.n3(g.namespace_manager)} -> {o}")
# Comprehension Python : animaux de plus de 5 ans (equivalent du .Where(...) LINQ)
age_prop = EX.age
older = [(s, int(o)) for s, p, o in g.triples((None, age_prop, None)) if int(o) > 5]
print("\n=== Animaux > 5 ans (comprehension) ===")
for s, age in older:
print(f" {s.n3(g.namespace_manager)} : {age}")
# Animal le plus age (equivalent du .Max(...) LINQ)
all_ages = [(s, int(o)) for s, p, o in g.triples((None, age_prop, None))]
oldest = max(all_ages, key=lambda x: x[1])
print(f"\n=== Animal le plus age ===")
print(f" {oldest[0].n3(g.namespace_manager)} : {oldest[1]} ans")
else:
print("rdflib non disponible : selection combinee ignoree.")=== Noms des animaux ===
ex:rex -> Rex
ex:minou -> Minou
ex:coco -> Coco
ex:buddy -> Buddy
=== Animaux > 5 ans (comprehension) ===
ex:coco : 12
ex:buddy : 7
=== Animal le plus age ===
ex:coco : 12 ans
Interpretation : Méthodes de sélection
| Méthode rdflib | Pattern | Equivalent dotNetRDF | Equivalent SPARQL |
|---|---|---|---|
g.triples((s, None, None)) |
s ? ? |
GetTriplesWithSubject(s) |
SELECT ?p ?o WHERE { s ?p ?o } |
g.triples((None, p, None)) |
? p ? |
GetTriplesWithPredicate(p) |
SELECT ?s ?o WHERE { ?s p ?o } |
g.triples((None, None, o)) |
? ? o |
GetTriplesWithObject(o) |
SELECT ?s ?p WHERE { ?s ?p o } |
g.triples((s, p, None)) |
s p ? |
GetTriplesWithSubjectPredicate(s,p) |
SELECT ?o WHERE { s p ?o } |
g.triples((None, p, o)) |
? p o |
GetTriplesWithPredicateObject(p,o) |
SELECT ?s WHERE { ?s p o } |
g.value(s, p) |
singleton | (via GetTriplesWithSubjectPredicate) |
SELECT ?o WHERE { s p ?o } LIMIT 1 |
Verification manuelle : la sélection rdf:type renvoie 14 triplets (cf. cellule précédente), et le filtre “age > 5” doit retourner coco (12) et buddy (7) - ce que confirme la sortie. L’animal le plus age est coco a 12 ans. Les résultats sont des générateurs (lazy evaluation), composables avec les comprehensions Python exactement comme IEnumerable<Triple> se compose avec LINQ.
5. Listes RDF
Les listes RDF (RDF collections) sont des structures chainees encodees avec rdf:first et rdf:rest, terminant par rdf:nil. rdflib fournit la classe rdflib.collection.Collection qui encapsule cette structure avec une API de liste Python standard (append, index, len, itération, del).
# 5.1 Creer et lire une liste RDF
if RDFLIB_AVAILABLE:
g = Graph()
rdf_data = """
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix ex: <http://example.org/ns#> .
ex:subj ex:pred _:b1 .
_:b1 rdf:first 1 . _:b1 rdf:rest _:b2 .
_:b2 rdf:first 2 . _:b2 rdf:rest _:b3 .
_:b3 rdf:first 3 . _:b3 rdf:rest rdf:nil .
"""
g.parse(data=rdf_data, format="turtle")
# Le predicat ex:pred pointe vers la tete de liste
predicate = URIRef("http://example.org/ns#pred")
head = list(g.objects(subject=URIRef("http://example.org/ns#subj"), predicate=predicate))[0]
# Collection encapsule l'acces haut-niveau
col = Collection(g, head)
print(f"Items (Collection) : [{', '.join(str(i) for i in col)}]")
print(f"Longueur : {len(col)}")
# Noeuds intermediaires = cells de la liste chainee
list_nodes = []
current = head
while current != RDF.nil:
list_nodes.append(current)
rest = list(g.objects(current, RDF.rest))[0]
current = rest
print(f"Noeuds intermediaires : {len(list_nodes)}")
# Comptage des triplets rdf:first + rdf:rest
first_triples = list(g.triples((None, RDF.first, None)))
rest_triples = list(g.triples((None, RDF.rest, None)))
print(f"rdf:first count : {len(first_triples)}")
print(f"rdf:rest count : {len(rest_triples)}")
print(f"Total liste : {len(first_triples) + len(rest_triples)} triplets")
else:
print("rdflib non disponible : listes RDF ignorees.")Items (Collection) : [1, 2, 3]
Longueur : 3
Noeuds intermediaires : 3
rdf:first count : 3
rdf:rest count : 3
Total liste : 6 triplets
Interpretation : Structure interne
head -> [1 | rest] -> [2 | rest] -> [3 | rest] -> rdf:nil
| Élément | Méthode rdflib | Exemple |
|---|---|---|
Valeurs (rdf:first) |
list(Collection(g, head)) |
[1, 2, 3] |
| Noeuds intermediaires | parcours manuel via rdf:rest |
_:b1, _:b2, _:b3 |
| Tous les triplets | g.triples((None, RDF.first, None)) + rdf:rest |
6 triplets (3 first + 3 rest) |
Verification manuelle : une liste de 3 éléments produit 3 rdf:first + 3 rdf:rest (le dernier rdf:rest pointe vers rdf:nil) = 6 triplets. Confirme par la sortie. Chaque élément coute 2 triplets (rdf:first + rdf:rest), sauf le dernier dont le rdf:rest pointe vers rdf:nil.
# 5.2 Ajout et suppression d'elements : Collection.append + del col[index]
if RDFLIB_AVAILABLE:
# Repartons d'une liste fraiche [1, 2, 3]
g2 = Graph()
head2 = BNode()
Collection(g2, head2, [Literal(1), Literal(2), Literal(3)])
col2 = Collection(g2, head2)
def fmt(c): return '[' + ', '.join(str(x) for x in c) + ']'
print(f"Avant : {fmt(col2)}")
# append = AddToList equivalent (ajoute en fin de liste)
col2.append(Literal(4))
col2.append(Literal(5))
print(f"Apres append : {fmt(col2)}")
# suppression par valeur : trouver l'index puis del (rdflib n'expose pas .remove(value))
target = Literal(3)
items = list(col2)
if target in items:
del col2[items.index(target)]
print(f"Apres del '3' : {fmt(col2)}")
else:
print("rdflib non disponible : ajout/suppression ignores.")Avant : [1, 2, 3]
Apres append : [1, 2, 3, 4, 5]
Apres del '3' : [1, 2, 4, 5]
# 5.3 Cycle complet : creation, ajout, suppression, destruction
if RDFLIB_AVAILABLE:
g3 = Graph()
head3 = BNode()
# AssertList equivalent : creation depuis une liste de valeurs
Collection(g3, head3, [Literal("Alice"), Literal("Bob"), Literal("Charlie")])
col3 = Collection(g3, head3)
def fmt(c): return '[' + ', '.join(str(x) for x in c) + ']'
print(f"Initial : {fmt(col3)}")
# Ajout
col3.append(Literal("Diana"))
print(f"Apres ajout Diana : {fmt(col3)}")
# Suppression par valeur (index lookup)
items = list(col3)
del col3[items.index(Literal("Bob"))]
print(f"Apres del Bob : {fmt(col3)}")
# RetractList equivalent : clear() detruit tous les triplets de la liste
avant = len(g3)
col3.clear()
print(f"Apres clear() : {fmt(col3)} | triplets {avant} -> {len(g3)}")
else:
print("rdflib non disponible : cycle complet ignore.")Initial : [Alice, Bob, Charlie]
Apres ajout Diana : [Alice, Bob, Charlie, Diana]
Apres del Bob : [Alice, Charlie, Diana]
Apres clear() : [] | triplets 6 -> 0
Interpretation : API des listes
| Opération | Méthode rdflib | Comportement | Équivalent dotNetRDF |
|---|---|---|---|
| Créer | Collection(g, head, [items]) |
Génère blank nodes + triplets rdf:first/rdf:rest |
g.AssertList(éléments) |
| Lire | list(Collection(g, head)) |
Itère les valeurs dans l’ordre | g.GetListItems(root) |
| Ajouter | col.append(item) |
Ajoute en fin de liste (gère le chaînage rdf:rest) |
g.AddToList(root, éléments) |
| Retirer par valeur | del col[col.index(item)] |
Recherche d’index manuelle puis suppression | g.RemoveFromList(root, éléments) |
| Supprimer tout | col.clear() |
Supprime récursivement tous les triplets | g.RetractList(root) |
Différence notable : RemoveFromList (dotNetRDF) retire par valeur directement ; rdflib exige de trouver l’index puis del col[index] — visible dans les sorties 5.2 (« Apres del ‘3’ ») et 5.3 (« Apres del Bob »). API légèrement moins ergonomique pour ce cas, mais conforme à l’idiome Python del list[i].
Quand utiliser les listes RDF ? : Collections ordonnées (séquences, étapes). Pour des ensembles non ordonnés, préférez des triplets individuels — la liste RDF est coûteuse (2 triplets par élément) et peu query-friendly (pas de
SELECT ?x WHERE { list ?p ?x }direct).
6. Comparaison cross-langage dotNetRDF vs rdflib
| Aspect | dotNetRDF (C#) | rdflib (Python) |
|---|---|---|
| Style API | OOP verbeux, typage fort, interfaces multiples | Pythonique, concis, tuples natifs |
| Noeuds | Lies au graphe (g.CreateUriNode(...)) |
Independants (URIRef(...)) |
| Triple | Objet new Triple(s, p, o) |
Tuple Python (s, p, o) |
| Lecture | Classes par format (TurtleParser, NTriplesParser) + Handlers |
API unique g.parse(format="...") |
| Ecriture | Classes par format + interfaces (IPrettyPrintingWriter, ICompressingWriter) |
API unique g.serialize(format="...") (options limitees) |
| Fusion | g1.Merge(g2) (en place) |
g1 += g2 (en place) ou g1 \| g2 (nouveau) |
| Sélection | GetTriplesWithXxx(...) (5 méthodes) |
g.triples((s, p, o)) (une méthode, None = joker) |
| Listes RDF | Extensions.GetListItems, AssertList, AddToList, RetractList |
rdflib.collection.Collection (API liste Python) |
| Streaming massif | Handlers (CountHandler, PagingHandler) - pas de graphe en memoire |
Construit systematiquement le graphe (gap) |
| LINQ / comprehensions | IEnumerable<Triple> + LINQ (.Where, .Select, .Max) |
Générateurs + comprehensions Python ([x for x in ...]) |
Différences philosophiques
- dotNetRDF privilegie l’explicite : un type distinct par parser/writer, interfaces de configuration fines, handlers pour le streaming. Avantage : contrôle précis, typage compile. Inconvenient : verbosite.
- rdflib privilegie l’uniformite : une seule méthode
parse/serializeavec un argumentformat, tuples Python natifs pour les triples. Avantage : concision, courbe d’apprentissage faible. Inconvenient : moins d’options de configuration, pas de streaming memoire-econome natif.
A retenir : Les deux bibliotheques implementent fidelement les mêmes standards W3C (RDF 1.1, Turtle, N-Triples, RDF/XML, JSON-LD). Les fichiers produits par l’un sont consommables par l’autre. Le choix depend de l’ecosysteme : .NET pour l’integration C# typée, Python pour la data science et l’IA.
7. Exercices pratiques
Mettez en pratique les concepts appris. Chaque Exemple guide est une solution complete commentee ; chaque Exercice est un squelette a completer.
Exemple guide 1 : Charger et explorer
Chargez data/animals.ttl et affichez : le nombre total de triplets, les triplets rdf:type, et les proprietes de ex:rex.
# Exemple guide 1 : Charger animals.ttl et explorer
if RDFLIB_AVAILABLE:
g = Graph()
g.parse("data/animals.ttl", format="turtle")
print(f"Total triplets : {len(g)}")
# Selection de tous les triplets dont le predicat est rdf:type
# rdf:type indique la classe d'appartenance d'une ressource (ex : rex est de type Dog)
print("Triplets rdf.type :")
for s, p, o in g.triples((None, RDF.type, None)):
print(f" {s.n3(g.namespace_manager)} -> {o.n3(g.namespace_manager)}")
# Toutes les proprietes de ex:rex
rex = URIRef("http://example.org/animals#rex")
print("Proprietes de rex :")
for s, p, o in g.triples((rex, None, None)):
print(f" {p.n3(g.namespace_manager)} -> {o}")
else:
print("rdflib non disponible : exemple 1 ignore.")Total triplets : 51
Triplets rdf.type :
ex:Animal -> rdfs:Class
ex:Mammal -> rdfs:Class
ex:Bird -> rdfs:Class
ex:Dog -> rdfs:Class
ex:Cat -> rdfs:Class
ex:Parrot -> rdfs:Class
ex:name -> rdf:Property
ex:age -> rdf:Property
ex:sound -> rdf:Property
ex:canFly -> rdf:Property
ex:rex -> ex:Dog
ex:buddy -> ex:Dog
ex:minou -> ex:Cat
ex:coco -> ex:Parrot
Proprietes de rex :
rdf:type -> http://example.org/animals#Dog
ex:name -> Rex
ex:age -> 5
ex:sound -> Woof
Exercice 4 : Recherche avancee dans un graphe
Chargez data/animals.ttl et trouvez :
- Tous les animaux qui sont des chiens (
rdf:type->ex:Dog) en utilisantg.triples((None, RDF.type, ex:Dog)) - Le nombre d’animaux qui peuvent voler (predicat
ex:canFlyavec valeurTrue) - L’animal le plus age (maximum de la valeur du predicat
ex:age)
Indices : utilisez g.triples((None, RDF.type, URIRef("...Dog"))) pour la question 1, et une comprehension Python avec max(..., key=lambda ...) pour la question 3.
# TODO etudiant : Chargez data/animals.ttl et trouvez :
# 1. Tous les animaux qui sont des chiens (rdf:type -> ex:Dog)
# 2. Le nombre d'animaux qui peuvent voler (predicat ex:canFly avec valeur True)
# 3. L'animal le plus age (maximum de ex:age)
#
# Indice : utilisez g.triples((None, RDF.type, URIRef("http://example.org/animals#Dog"))) pour (1)
# et max(..., key=lambda x: x[1]) sur la liste des (sujet, age) pour (3)
print("Exercice a completer")Exercice a completer
Exemple guide 2 : Fusionner et serialiser
Chargez data/Example.ttl et data/animals.ttl, fusionnez-les, et ecrivez le résultat en Turtle compact.
# Exemple guide 2 : Fusion + serialization Turtle
if RDFLIB_AVAILABLE:
g1 = Graph()
g2 = Graph()
g1.parse("data/Example.ttl", format="turtle")
g2.parse("data/animals.ttl", format="turtle")
print(f"Avant fusion - g1: {len(g1)}, g2: {len(g2)}")
# Union : les triplets identiques ne sont pas dupliques (semantique d'ensemble RDF)
# Les blank nodes sont isoles par graphe (pas de collision)
g1 += g2
print(f"Apres fusion : {len(g1)} triplets")
result = g1.serialize(format="turtle")
print(result)
else:
print("rdflib non disponible : exemple 2 ignore.")Avant fusion - g1: 4, g2: 51
Apres fusion : 55 triplets
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix ex: <http://example.org/stuff/1.0/> .
@prefix ns1: <http://example.org/animals#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ns1:Animal a rdfs:Class ;
rdfs:label "Animal"@fr ;
rdfs:comment "Classe racine de tous les animaux"@fr .
ns1:Bird a rdfs:Class ;
rdfs:label "Oiseau"@fr ;
rdfs:subClassOf ns1:Animal .
ns1:Cat a rdfs:Class ;
rdfs:label "Chat"@fr ;
rdfs:subClassOf ns1:Mammal .
ns1:Dog a rdfs:Class ;
rdfs:label "Chien"@fr ;
rdfs:subClassOf ns1:Mammal .
ns1:Mammal a rdfs:Class ;
rdfs:label "Mammifere"@fr ;
rdfs:subClassOf ns1:Animal .
ns1:Parrot a rdfs:Class ;
rdfs:label "Perroquet"@fr ;
rdfs:subClassOf ns1:Bird .
ns1:age a rdf:Property ;
rdfs:label "age"@fr ;
rdfs:domain ns1:Animal ;
rdfs:range xsd:integer .
ns1:buddy a ns1:Dog ;
ns1:age 7 ;
ns1:name "Buddy" ;
ns1:sound "Woof woof" .
ns1:canFly a rdf:Property ;
rdfs:label "peut voler"@fr ;
rdfs:domain ns1:Animal ;
rdfs:range xsd:boolean .
ns1:coco a ns1:Parrot ;
ns1:age 12 ;
ns1:canFly true ;
ns1:name "Coco" ;
ns1:sound "Coco veut un gateau" .
ns1:minou a ns1:Cat ;
ns1:age 3 ;
ns1:name "Minou" ;
ns1:sound "Miaou" .
ns1:name a rdf:Property ;
rdfs:label "nom"@fr ;
rdfs:domain ns1:Animal ;
rdfs:range xsd:string .
ns1:rex a ns1:Dog ;
ns1:age 5 ;
ns1:name "Rex" ;
ns1:sound "Woof" .
ns1:sound a rdf:Property ;
rdfs:label "cri"@fr ;
rdfs:domain ns1:Animal ;
rdfs:range xsd:string .
<http://www.w3.org/TR/rdf-syntax-grammar> ex:editor [ ex:fullname "Dave Beckett" ;
ex:homePage <http://purl.org/net/dajobe/> ] ;
dc:title "RDF/XML Syntax Specification (Revised)" .
Exercice 5 : Fusion avec données en memoire
Chargez data/animals.ttl dans un graphe et la chaîne N-Triples suivante dans un autre graphe avec g.parse(data=..., format="nt") :
<http://example.org/ns#dave> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/ns#Person> .
<http://example.org/ns#dave> <http://example.org/ns#name> "Dave" .
<http://example.org/ns#dave> <http://example.org/ns#age> "40" .
Fusionnez les deux graphes et serialisez le résultat en N-Triples (pas Turtle).
# TODO etudiant : Chargez data/animals.ttl dans un graphe g1
# Chargez la chaine N-Triples ci-dessus dans un graphe g2 avec g2.parse(data=..., format="nt")
# Fusionnez g2 dans g1 (g1 += g2) et affichez le nombre total de triplets
# Serialisez le resultat en N-Triples (pas Turtle) avec g1.serialize(format="nt")
#
# Indice : utilisez g.serialize(format="nt") pour la serialisation
print("Exercice a completer")Exercice a completer
Exemple guide 3 : Liste RDF
Créez une liste ["Alice", "Bob", "Charlie"] avec Collection, ajoutez "Diana", supprimez "Bob", affichez le résultat.
# Exemple guide 3 : Liste RDF avec Collection
if RDFLIB_AVAILABLE:
g = Graph()
head = BNode()
# Creation d'une liste RDF ordonnee [Alice, Bob, Charlie]
# Collection genere une chaine de blank nodes relies par rdf:first et rdf:rest
Collection(g, head, [Literal("Alice"), Literal("Bob"), Literal("Charlie")])
col = Collection(g, head)
# Fonction utilitaire pour afficher uniquement la valeur textuelle
def fmt(c): return '[' + ', '.join(str(n) for n in c) + ']'
print(f"Initial : {fmt(col)}")
# append insere "Diana" en fin de liste
col.append(Literal("Diana"))
print(f"Apres ajout : {fmt(col)}")
# suppression par valeur : on cherche l'index puis del col[index]
items = list(col)
del col[items.index(Literal("Bob"))]
print(f"Apres suppression: {fmt(col)}")
else:
print("rdflib non disponible : exemple 3 ignore.")Initial : [Alice, Bob, Charlie]
Apres ajout : [Alice, Bob, Charlie, Diana]
Apres suppression: [Alice, Charlie, Diana]
Exercice 6 : Liste RDF personnalisee
Créez une liste RDF ["Paris", "Lyon", "Marseille"] avec Collection, ajoutez "Toulouse" avec col.append(...), supprimez "Lyon" (index lookup + del). Affichez la liste après chaque opération.
# TODO etudiant : Creez une liste RDF ["Paris", "Lyon", "Marseille"] avec Collection
# Ajoutez "Toulouse" avec col.append(Literal("Toulouse"))
# Supprimez "Lyon" avec del col[list(col).index(Literal("Lyon"))]
# Affichez la liste apres chaque operation avec list(col)
print("Exercice a completer")Exercice a completer
References savantes
- RDF 1.1 Concepts and Abstract Syntax - Cyganiak, Wood, Lanthaler (eds.), Recommandation W3C, 25 fevrier 2014. Modèle de données abstrait (triplets sujet-predicat-objet, IRIs, blank nodes, litteraux) dont derivent tous les formats manipules dans ce notebook.
- RDF 1.1 Turtle - Beckett, Berners-Lee, Prud’hommeaux, Recommandation W3C, 25 fevrier 2014. Format de serialization compact lisible par l’humain, le plus utilise en pratique.
- RDF 1.1 N-Triples - Ackaert, Sporny, Kellogg, Recommandation W3C, 25 fevrier 2014. Forme canonique ligne-par-triplet, base d’echange interoperaable.
- Notation 3 (N3) - Berners-Lee, A Rough Guide to Notation 3, note non normative 2006. Sur-ensemble de Turtle ajoutant règles et citerables.
- rdflib - bibliotheque open-source Python pour la manipulation de graphes RDF (
rdflib.readthedocs.io), implemente les parsers/serializers uniformesg.parse()/g.serialize()utilises tout au long de ce notebook.
Resume
| Section | Concepts cles | APIs rdflib principales | Equivalent dotNetRDF |
|---|---|---|---|
| 1. Lecture | parse, formats, gap Handlers | g.parse(format=...), len(g) |
TurtleParser, CountHandler |
| 2. Ecriture | serialize, multi-formats | g.serialize(format=...) |
NTriplesWriter, CompressingTurtleWriter |
| 3. Fusion | Union, deduplication | g1 += g2, g1 \| g2 |
IGraph.Merge() |
| 4. Sélection | triples pattern, comprehensions | g.triples((s,p,o)), g.value() |
GetTriplesWithSubject, LINQ |
| 5. Listes | Collections ordonnees | Collection(g, head, items) |
AssertList, GetListItems, RetractList |
Prochaine étape
Dans SW-4b-Python-SPARQL, nous apprendrons a interroger les graphes RDF avec le langage SPARQL cote Python (equivalent de SW-4 C#).
Navigation : Index | << SW-3 C# | SW-4b Python >>