tag:linuxfr.org,2005:/tags/owl/publicLinuxFr.org : les contenus étiquetés avec « owl »2017-09-18T10:43:48+02:00/favicon.pngtag:linuxfr.org,2005:News/381672017-09-03T21:39:58+02:002017-09-10T12:54:05+02:00Owlready : un module Python pour manipuler les ontologies OWLLicence CC By‑SA http://creativecommons.org/licenses/by-sa/4.0/deed.fr<div><p>Les ontologies formelles sont un moyen de modéliser des connaissances. Elles sont de plus en plus utilisées en intelligence artificielle. Cependant, bien qu’elles partagent de nombreux aspects avec les modèles objet, elles restent peu connues des programmeurs.</p>
<p>Owlready est un module sous licence LGPL permettant de faire de la programmation orientée ontologie en Python, c’est‐à‐dire de manipuler les entités d’une ontologie formelle comme s’il s’agissait d’objets Python classiques. La version 2 - 0.4 intègre un <em>quadstore</em> RDF optimisé et une syntaxe de haut niveau pour manipuler les ontologies, qui a fait l’objet d’un <a href="http://www.lesfleursdunormal.fr/_downloads/article_owlready_aim_2017.pdf">article récent</a> de la revue <a href="https://www.journals.elsevier.com/artificial-intelligence-in-medicine/"><em>Artificial Intelligence In Medicine</em> (AIM)</a>.</p>
<p>Dans la suite de cet article de la série « que peut‐on faire dans le Libre quand on est maître de conférence ? », les ontologies seront rapidement présentées, puis je décrirai Owlready et la programmation orientée ontologie. Enfin, j’en profiterai pour donner mon point de vue personnel sur le libre accès aux articles scientifiques.</p></div><ul><li>lien nᵒ 1 : <a title="https://pypi.python.org/pypi/Owlready2" hreflang="en" href="https://linuxfr.org/redirect/100528">Owlready sur PyPI (Python Package Index)</a></li><li>lien nᵒ 2 : <a title="https://bitbucket.org/jibalamy/owlready2" hreflang="en" href="https://linuxfr.org/redirect/100529">Owlready sur BitBucket</a></li><li>lien nᵒ 3 : <a title="http://owlready2.readthedocs.io/en/latest/" hreflang="en" href="https://linuxfr.org/redirect/100530">Documentation d’Owlready</a></li><li>lien nᵒ 4 : <a title="https://doi.org/10.1016/j.artmed.2017.07.002" hreflang="en" href="https://linuxfr.org/redirect/100531">Article AIM chez Elsevier (accès payant)</a></li><li>lien nᵒ 5 : <a title="http://www.lesfleursdunormal.fr/_downloads/article_owlready_aim_2017.pdf" hreflang="en" href="https://linuxfr.org/redirect/100532">Article AIM en « version auteur » (accès libre)</a></li></ul><div><h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#questce-quune-ontologie-formelle">Qu’est‐ce qu’une ontologie formelle ?</a></li>
<li>
<a href="#owlready-et-la-programmation-orient%C3%A9e-ontologie">Owlready et la programmation orientée ontologie</a><ul>
<li><a href="#les-api"> Les API</a></li>
<li><a href="#les-langages-de-requ%C3%AAtes"> Les langages de requêtes</a></li>
<li><a href="#la-programmation-orient%C3%A9e-ontologie">La programmation orientée ontologie</a></li>
</ul>
</li>
<li><a href="#exemple-avec-owlready">Exemple avec Owlready</a></li>
<li><a href="#%C3%80-propos-de-lacc%C3%A8s-libre-aux-articles-scientifiques">À propos de l’accès libre aux articles scientifiques</a></li>
</ul><h2 id="questce-quune-ontologie-formelle">Qu’est‐ce qu’une ontologie formelle ?</h2>
<p>Les ontologies formelles sont un moyen de modéliser des connaissances. Par certains côtés, les ontologies ressemblent beaucoup aux modèles objets : on y retrouve les notions de classes, de propriétés et d’instances (appelées individus).</p>
<p>Les ontologies ont deux finalités principales :</p>
<ol>
<li>
<strong>le raisonnement automatique</strong> : les ontologies définissent des concepts (telles que des classes) de manière logique et formelle. En utilisant un raisonneur, il est donc possible d’effectuer des déductions logiques. En particulier, le raisonneur peut « reclasser » les classes et les instances, c’est‐à‐dire calculer l’arbre d’héritage des classes et la (ou les) classe(s) de chaque instance, à partir de leurs propriétés ;</li>
<li>
<strong>les données liées</strong> (<em>linked data</em>) : toutes les ontologies partagent le même espace de nommage. Elles permettent donc de lier entre elles toutes les données existantes. En particulier, la définition d’une classe n’est pas nécessairement contenue dans un seul fichier : une ontologie peut très bien compléter la définition d’une classe issue d’une autre ontologie.</li>
</ol><p>Par rapport aux modèles objet habituels, les ontologies possèdent une expressivité supérieure : elles permettent d’exprimer des contraintes logiques sur les classes, en s’appuyant sur les <a href="https://fr.wikipedia.org/wiki/logiques%20de%20description" title="Définition Wikipédia">logiques de description</a>. On peut par exemple créer la classe des « licences » et la classe des « systèmes d’exploitation ». La classe des « licences libres » est une sous‐classe de la classe « licence » (héritage). Nous pouvons ensuite définir la classe des « systèmes d’exploitation libres » comme équivalente à « un systèmes d’exploitation qui a une licence libre ». Tout système d’exploitation ayant (au moins) une licence libre pourra alors être automatiquement reclassé comme « système d’exploitation libre ».</p>
<p>La grande majorité des ontologies utilisent le langage <a href="https://fr.wikipedia.org/wiki/Web_Ontology_Language">OWL</a> (<em>Web Ontology Language</em>, actuellement en version 2.0). Ce langage peut s’enregistrer en plusieurs formats, le plus employé étant RDF/XML. RDF sérialise l’ontologie sous forme de triplets (sujet, prédicat, objet), par exemple (individu, type, classe) pour renseigner la classe d’un individu ou (individu, propriété, valeur) pour renseigner ses attributs. Les contraintes logiques mentionnées ci‐dessus sont décomposées en plusieurs triplets RDF.</p>
<p>De nombreux outils existent pour traiter les ontologies OWL. Le plus connu est l’éditeur <a href="https://protege.stanford.edu/">Protégé</a>, qui permet de créer et d’éditer une ontologie en OWL.</p>
<p>En Python, le principal module existant est <a href="https://github.com/RDFLib/rdflib/">RDFLIB</a>. Mais RDFLIB présente deux défauts :</p>
<ol>
<li>RDFLIB fonctionne au niveau RDF mais pas au niveau OWL. Il permet donc de gérer des triplets et des ressources (c’est‐à‐dire des objets), mais il n’est pas adapté pour gérer les classes et les contraintes logiques. Il n’intègre pas non plus de raisonneur ;</li>
<li>en pratique, les performances de RDFLIB ne permettent pas de manipuler de grosses ontologies (plusieurs centaines de mégaoctets ou plusieurs millions de triplets).</li>
</ol><h2 id="owlready-et-la-programmation-orientée-ontologie">Owlready et la programmation orientée ontologie</h2>
<p>Trois approches existent pour intégrer une ontologie formelle dans un programme :</p>
<h3 id="les-api"> Les API</h3>
<p>Les API, comme OWLAPI en Java, permettent d’accéder aux ontologies à l’aide de classes correspondant aux éléments d’OWL. Par exemple, avec Java + OWLAPI, pour obtenir la propriété « prop » de l’objet « obj », qui est de type « <em>float</em> », on écrira :</p>
<pre><code class="java"><span class="n">OWLDataProperty</span> <span class="n">prop</span> <span class="o">=</span> <span class="n">owlDataFactory</span><span class="o">.</span><span class="na">getOWLDataProperty</span><span class="o">(</span><span class="n">IRI</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="s">"onto.owl#prop"</span><span class="o">));</span>
<span class="kt">float</span> <span class="n">valeur</span> <span class="o">=</span> <span class="o">((</span><span class="n">Float</span><span class="o">)</span> <span class="n">obj</span><span class="o">.</span><span class="na">getPropertyValue</span><span class="o">(</span><span class="n">prop</span><span class="o">)).</span><span class="na">floatValue</span><span class="o">();</span></code></pre>
<h3 id="les-langages-de-requêtes"> Les langages de requêtes</h3>
<p>Les langages de requêtes s’inspirent de SQL et l’adaptent à RDF. Le plus courant est <a href="https://fr.wikipedia.org/wiki/SPARQL" title="Définition Wikipédia">SPARQL</a>. Si l’on reprend l’exemple précédent en SPARQL, cela donnera :</p>
<pre><code>SELECT ?valeur WHERE { ?obj :prop ?valeur . }
</code></pre>
<p>Ensuite, il faut exécuter la requête dans le langage de programmation, par exemple en Python avec RDFLIB, nous aurons :</p>
<pre><code class="python"><span class="n">valeur</span> <span class="o">=</span> <span class="n">graph</span><span class="o">.</span><span class="n">query</span><span class="p">(</span><span class="s2">"SELECT ?valeur WHERE { ?obj :prop ?valeur . }"</span><span class="p">)</span></code></pre>
<h3 id="la-programmation-orientée-ontologie">La programmation orientée ontologie</h3>
<p>La programmation orientée ontologie permet de manipuler les classes et les individus de l’ontologie comme s’il s’agissait de classes et d’instances du langage de programmation. Si l’on reprend l’exemple précédent avec Python + Owlready, il suffira d’écrire :</p>
<pre><code class="python"><span class="n">valeur</span> <span class="o">=</span> <span class="n">obj</span><span class="o">.</span><span class="n">prop</span></code></pre>
<p>On comprend donc rapidement que cette troisième approche est de loin la plus facile à utiliser, c’est donc celle que j’ai choisie pour Owlready. Owlready est un module pour Python 3 sous licence LGPL v3+, qui permet la programmation orientée ontologie. La version 2 d’Owlready intègre :</p>
<ul>
<li>le raisonneur <a href="http://www.hermit-reasoner.com/">HermiT</a> (N. B. : celui‐ci étant programmé en Java, il faut une machine virtuelle Java pour utiliser le raisonneur) ;</li>
<li>un <em>quadstore</em> RDF optimisé utilisant <a href="http://www.sqlite.org/">SQLite3</a>. Un <em>quadstore</em> est une base de triplets RDF, auquel on ajoute un quatrième élément qui permet d’identifier de quelle ontologie provient le triplet. Ce <em>quadstore</em> peut être stocké en mémoire ou bien dans un fichier. De plus, le <em>quadstore</em> est compatible avec RDFLIB ;</li>
<li>des analyseurs pour les formats de fichiers RDF/XML, OWL/XML et NTriples.</li>
</ul><p>Au final, Owlready cherche à obtenir le meilleur de trois mondes :</p>
<ol>
<li>la programmation orienté objet, pour l’encapsulation (c’est‐à‐dire la capacité à rassembler les données et les traitements associés : les méthodes) ;</li>
<li>les ontologies formelles, pour l’expressivité (les contraintes logiques et les capacités de raisonnement automatique associées) ;</li>
<li>les bases de données relationnelles, pour les performances (la capacité de stockage et la rapidité d’accès).</li>
</ol><p>L’architecture, la syntaxe et les algorithmes utilisés dans Owlready ont été publiés dans <a href="http://www.lesfleursdunormal.fr/_downloads/article_owlready_aim_2017.pdf">un article récent</a> de la revue <a href="https://www.journals.elsevier.com/artificial-intelligence-in-medicine/"><em>Artificial Intelligence In Medicine</em></a>, que l’on peut trouver sur mon site perso (je reviendrai plus bas sur la délicate question du libre accès aux articles scientifiques).</p>
<p>Notons qu’Owlready peut aussi être utilisé en lieu et place d’un ORM (<em>Object Relational Mapper</em>). Un ORM est une surcouche objet à une base de données (généralement SQL) et permet la persistance des objets, comme par exemple <a href="https://www.sqlalchemy.org/">SQLAlchemy</a> ou <a href="http://www.sqlobject.org/">SQLObject</a> en Python. Les <a href="http://www.lesfleursdunormal.fr/static/informatique/ormithorynque/benchmark_fr.html">tests</a> montrent qu’Owlready conduit à un niveau de performance équivalent voire supérieur.</p>
<h2 id="exemple-avec-owlready">Exemple avec Owlready</h2>
<p>Nous allons reprendre l’exemple précédent sur les licences libres et les systèmes d’exploitation, et le créer avec Owlready. La première ligne importe le module, la seconde crée une ontologie, la troisième (bloc <code>with</code>) indique que tout ce qui sera créé dans ce bloc (classes, propriétés, individus, etc.) sera défini dans l’ontologie « onto ». Ensuite nous définissons les classes, en héritant de <em>Thing</em>, qui est la classe la plus générale en OWL.</p>
<pre><code class="python"><span class="kn">from</span> <span class="nn">owlready2</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">onto</span> <span class="o">=</span> <span class="n">get_ontology</span><span class="p">(</span><span class="s2">"http://test.org/onto.owl"</span><span class="p">)</span>
<span class="k">with</span> <span class="n">onto</span><span class="p">:</span>
<span class="k">class</span> <span class="nc">Licence</span><span class="p">(</span><span class="n">Thing</span><span class="p">):</span> <span class="k">pass</span>
<span class="k">class</span> <span class="nc">LicenceLibre</span><span class="p">(</span><span class="n">Licence</span><span class="p">):</span> <span class="k">pass</span>
<span class="k">class</span> <span class="nc">LicenceProprietaire</span><span class="p">(</span><span class="n">Licence</span><span class="p">):</span> <span class="k">pass</span>
<span class="n">licence_proprio</span> <span class="o">=</span> <span class="n">LicenceProprietaire</span><span class="p">(</span><span class="s2">"licence_proprio"</span><span class="p">)</span>
<span class="n">gpl</span> <span class="o">=</span> <span class="n">LicenceLibre</span><span class="p">(</span><span class="s2">"gpl"</span><span class="p">)</span>
<span class="n">lgpl</span> <span class="o">=</span> <span class="n">LicenceLibre</span><span class="p">(</span><span class="s2">"lgpl"</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">SystemeDExploitation</span><span class="p">(</span><span class="n">Thing</span><span class="p">):</span> <span class="k">pass</span>
<span class="k">class</span> <span class="nc">a_pour_licence</span><span class="p">(</span><span class="n">ObjectProperty</span><span class="p">):</span>
<span class="n">domain</span> <span class="o">=</span> <span class="p">[</span><span class="n">SystemeDExploitation</span><span class="p">]</span>
<span class="nb">range</span> <span class="o">=</span> <span class="p">[</span><span class="n">Licence</span><span class="p">]</span>
<span class="n">gnu_linux</span> <span class="o">=</span> <span class="n">SystemeDExploitation</span><span class="p">(</span><span class="s2">"gnu_linux"</span><span class="p">)</span>
<span class="n">gnu_linux</span><span class="o">.</span><span class="n">a_pour_licence</span> <span class="o">=</span> <span class="p">[</span><span class="n">gpl</span><span class="p">]</span>
<span class="n">windows</span> <span class="o">=</span> <span class="n">SystemeDExploitation</span><span class="p">(</span><span class="s2">"windows"</span><span class="p">)</span>
<span class="n">windows</span><span class="o">.</span><span class="n">a_pour_licence</span> <span class="o">=</span> <span class="p">[</span><span class="n">licence_proprio</span><span class="p">]</span>
<span class="k">class</span> <span class="nc">SystemeDExploitationLibre</span><span class="p">(</span><span class="n">Thing</span><span class="p">):</span>
<span class="n">equivalent_to</span> <span class="o">=</span> <span class="p">[</span> <span class="n">SystemeDExploitation</span> <span class="o">&</span> <span class="n">a_pour_licence</span><span class="o">.</span><span class="n">some</span><span class="p">(</span><span class="n">LicenceLibre</span><span class="p">)</span> <span class="p">]</span></code></pre>
<p>La propriété <em>a_pour_licence</em> est créée comme une classe fille d’<em>ObjectProperty</em> et nous définissons son domaine et son <em>range</em>. Le <em>range</em> correspond au « type » de la propriété, c’est‐à‐dire au type de valeur qu’elle peut prendre. Le domaine correspond à la classe qui possède cette propriété : contrairement aux modèles objet habituels, les propriétés ne sont pas définies pour une classe donnée mais indépendamment. Cela permet à une ontologie d’ajouter des propriétés aux classes définies dans une autre ontologie.<br>
Enfin, l’exemple crée la classe <em>SystemeDExploitationLibre</em>, qui est définie comme équivalente à <em>SystemeDExploitation</em> et « <em>a_pour_licence</em> SOME <em>LicenceLibre</em> » (au moins une licence libre, donc).</p>
<p>Nous pouvons ensuite exécuter le raisonneur et afficher le résultat :</p>
<pre><code class="python"><span class="n">sync_reasoner</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="n">gnu_linux</span><span class="o">.</span><span class="vm">__class__</span><span class="p">)</span>
<span class="c1"># => onto.SystemeDExploitationLibre</span></code></pre>
<p>Nous constatons que l’individu « gnu_linux » a été reclassé.</p>
<p>Et si nous voulons faire le même raisonnement pour les systèmes d’exploitation non libres ? C’est plus compliqué ! Nous pouvons créer la classe des « systèmes d’exploitation non libres » ainsi (notez le « Not » par rapport à tout à l’heure) :</p>
<pre><code class="python"><span class="k">with</span> <span class="n">onto</span><span class="p">:</span>
<span class="k">class</span> <span class="nc">SystemeDExploitationNonLibre</span><span class="p">(</span><span class="n">Thing</span><span class="p">):</span>
<span class="n">equivalent_to</span> <span class="o">=</span> <span class="p">[</span> <span class="n">SystemeDExploitation</span> <span class="o">&</span> <span class="n">Not</span><span class="p">(</span><span class="n">a_pour_licence</span><span class="o">.</span><span class="n">some</span><span class="p">(</span><span class="n">LicenceLibre</span><span class="p">))</span> <span class="p">]</span></code></pre>
<p>Mais si vous exécutez le raisonneur, vous constaterez que Windows n’est pas reclassé en <em>SystemeDExploitationNonLibre</em> ! En effet, les raisonneurs fonctionnent selon l’assomption du monde ouvert : tout ce qui n’est pas défini est considéré comme possible. Nous avons défini que Windows avait une licence propriétaire, cependant nous n’avons pas dit que Windows n’avait pas d’autres licences (oui, certains logiciels ont plusieurs licences). Le raisonneur a donc considéré qu’il n’était pas impossible que Windows possède une autre licence, et que celle‐ci soit libre.</p>
<p>Nous devons donc indiquer que Windows possède seulement pour licence la licence propriétaire, ce qui peut se faire en ajoutant une contrainte OWL :</p>
<pre><code class="python"> <span class="n">windows</span><span class="o">.</span><span class="n">is_a</span><span class="o">.</span><span class="n">append</span><span class="p">(</span> <span class="n">a_pour_licence</span><span class="o">.</span><span class="n">only</span><span class="p">(</span><span class="n">OneOf</span><span class="p">([</span><span class="n">licence_proprio</span><span class="p">]))</span> <span class="p">)</span></code></pre>
<p>Ou plus simplement, avec Owlready, en utilisant la fonction <code>close_world()</code> qui crée automatiquement les contraintes nécessaires pour considérer un individu ou une classe en « monde fermé » (c’est‐à‐dire pour asserter que tout est connu à leur sujet) :</p>
<pre><code class="python"> <span class="n">close_world</span><span class="p">(</span><span class="n">windows</span><span class="p">)</span>
<span class="n">close_world</span><span class="p">(</span><span class="n">gnu_linux</span><span class="p">)</span></code></pre>
<p>Enfin, nous devons également définir que les classes <em>LicenceLibre</em> et <em>LicencePropriétaire</em> sont disjointes, c’est‐à‐dire qu’il n’existe pas de classe fille héritant des deux (une licence ne peut pas être à la fois libre et propriétaire). Cela se fait ainsi :</p>
<pre><code class="python"> <span class="n">AllDisjoint</span><span class="p">([</span><span class="n">LicenceLibre</span><span class="p">,</span> <span class="n">LicenceProprietaire</span><span class="p">])</span></code></pre>
<p>Nous pouvons ensuite exécuter de nouveau le raisonneur et afficher le résultat :</p>
<pre><code class="python"><span class="n">sync_reasoner</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="n">windows</span><span class="o">.</span><span class="vm">__class__</span><span class="p">)</span>
<span class="c1"># => onto.SystemeDExploitationNonLibre</span></code></pre>
<p>Voilà, c’était un exemple simple de raisonnement logique, avec quelques pièges classiques.</p>
<h2 id="À-propos-de-laccès-libre-aux-articles-scientifiques">À propos de l’accès libre aux articles scientifiques</h2>
<p>La question du libre accès à la connaissance étant à la base du logiciel libre, j’aimerais revenir sur l’accès aux articles scientifiques. Les revues scientifiques font intervenir plusieurs acteurs : les auteurs des articles, les relecteurs (<em>reviewers</em>, chargés de corriger et d’évaluer l’article), l’éditeur (<em>publisher</em>, qui édite la revue) et les lecteurs. Historiquement, les auteurs et les relecteurs sont bénévoles (ce sont souvent des chercheurs payés par l’état), et le lecteur paie l’éditeur pour accéder à l’article (en général, il s’agit d’un abonnement institutionnel : un laboratoire ou une université paie un éditeur pour que ses chercheurs aient accès à telle ou telle revue). Donc, pas d’accès libre.</p>
<p>Il y a une vingtaine d’années sont apparues des revues en accès libre, avec un modèle différent : ce sont les auteurs qui paient l’éditeur, et le lecteur accède gratuitement à la revue (en ligne). Ce modèle a notamment été lancé par <a href="https://www.plos.org/">PLOS</a>, avec un certain succès. La qualité des revues se mesure notamment à leur facteur d’impact, c’est‐à‐dire le nombre de fois où les articles publiés sont cités dans les deux ans qui suivent la publication. L’idée derrière les revues en accès libre est la suivante : les articles étant librement accessibles, ils seront davantage lus et donc plus cités.</p>
<p>Mais ce n’est pas si simple : le nombre de citations ne suffit pas à faire la qualité… Dans une revue où l’auteur paie, l’éditeur a tout intérêt à faire en sorte qu’un maximum d’articles soit accepté, même si certains sont de mauvaise qualité, puisqu’il est payé à chaque article accepté. En fait, l’éditeur n’a qu’à dire « oui » et il est payé… On voit donc exploser le nombre de revues et d’éditeurs de ce type, avec un niveau de qualité souvent très faible. Et du coup les chercheurs sont régulièrement sollicités (pour ne pas dire « spammés ») par ce type d’éditeurs…</p>
<p>Les éditeurs « classiques » ont auss réagi par rapport au libre accès. Toutes sortes « d’exceptions » ont été mises en place. Par exemple pour la revue <a href="https://www.journals.elsevier.com/artificial-intelligence-in-medicine/"><em>Artificial Intelligence In Medicine</em></a> (édité par <a href="https://www.elsevier.com/">Elsevier</a>) où j’ai publié :</p>
<ol>
<li>les auteurs peuvent payer pour avoir leur article en libre accès (N. B. : hors de prix, mais certains projets de recherche, notamment européen, exigent que ce soit le cas) ;</li>
<li>un lien est fourni aux auteurs qui permet de télécharger gratuitement l’article pendant 50 jours, et ce lien peut être diffusé à volonté ;</li>
<li>après six mois d’embargo, une « version auteur » (même contenu que le vrai article, mais sans la mise en forme de l’éditeur) peut être mise à disposition librement sur des serveurs comme <a href="https://hal.archives-ouvertes.fr/">HAL (<em>Hyper‐Archives en Ligne</em>)</a> ;</li>
<li>les auteurs ont le droit de mettre une « version auteur » sur leur site personnel dès la parution de l’article, sous licence <em>Creative Commons Attribution Non‐Commercial No Derivatives</em> ».</li>
</ol><p>Nous ne sommes donc pas loin d’un accès libre (surtout grâce au dernier point), à condition que les auteurs fassent l’effort de produire et mettre en ligne cette « version auteur ».</p></div><div><a href="https://linuxfr.org/news/owlready-un-module-python-pour-manipuler-les-ontologies-owl.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/112557/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/news/owlready-un-module-python-pour-manipuler-les-ontologies-owl#comments">ouvrir dans le navigateur</a>
</p>
JibaZeroHeurePierre JarillonDavy Defaudpalm123https://linuxfr.org/nodes/112557/comments.atom