237 lines
7.6 KiB
HTML
237 lines
7.6 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Réflexivité avec Python: Introduction à importlib et inspect — PyConFr 2025</title>
|
||
<link rel="stylesheet" href="slides.css">
|
||
<link rel="stylesheet" href="highlight/default.min.css">
|
||
<script src="highlight/highlight.min.js"></script>
|
||
</head>
|
||
<body>
|
||
<section class="title">
|
||
<div class="content">
|
||
<h1>Réflexivité avec Python: Introduction à <code>importlib</code> et <code>inspect</code></h1>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<!-- Présentation -->
|
||
</section>
|
||
|
||
<section>
|
||
<!-- Problématique + démo -->
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Définition</h2>
|
||
<div class="content">
|
||
<div>
|
||
<blockquote>
|
||
En programmation informatique, la réflexion est la capacité d'un programme à <b>examiner</b> et éventuellement à <b>modifier</b> ses propres structures internes de haut niveau lors de son exécution.
|
||
</blockquote>
|
||
<p>— Wikipédia</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="content">
|
||
<h2>La gestion des imports en Python: <code>importlib</code></h2>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>Le rôle d’<code>importlib</code></h3>
|
||
<div class="content">
|
||
<ul>
|
||
<li>Implémente la logique d’import de Python: <code>__import__(name, **args)</code></li>
|
||
<li>Méchanisme en deux étapes:
|
||
<ul>
|
||
<li>trouver les modules (<code>abc.MetaPathFinder</code>, <code>abc.PathEntryFinder</code>),</li>
|
||
<li>charger les modules (<code>abc.Loader</code>),</li>
|
||
<li>des fois implémentées par la même classe (<code>BuiltinImporter</code>, <code>zipimporter</code>…).</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>Liens avec le module <code>sys</code></h3>
|
||
<div class="content">
|
||
<ul>
|
||
<li><b><code>sys.modules</code></b>: dictionnaire des modules importés</li>
|
||
<li>
|
||
<b><code>sys.meta_path</code></b>: liste de <code>MetaPathFinder</code>
|
||
<ul>
|
||
<li><code>BuiltinImporter</code></li>
|
||
<li><code>FrozenImporter</code></li>
|
||
<li><code>PathFinder</code></li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
<b><code>sys.path_hook</code></b>: liste de fonctions (ou <i>callables</i>) retournant un <code>PathEntryFinder</code>
|
||
<ul>
|
||
<li><code>zipimporter</code></li>
|
||
<li><code>path_hook_for_FileFinder</code></li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
<b><code>sys.path</code></b>: liste de chemins utilisés par <code>PathFinder</code> pour tenter de trouver le <code>PathEntryFinder</code> qui importera le module
|
||
<pre><code class="language-python">for hook in sys.path_hook:
|
||
for path in sys.path:
|
||
if finder := hook(path):
|
||
return finder</code></pre>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>Exemple: configurer un nouvel <i>importer</i></h3>
|
||
<div class="content">
|
||
<pre><code class="language-python">import sys
|
||
import dns_importer
|
||
|
||
# DnsImporter implémente PathEntryFinder
|
||
# et hérite de importlib._bootstrap_external._LoaderBasics
|
||
# pour l'implémentation de Loader
|
||
sys.path_hooks.append(dns_importer.DnsImporter)
|
||
|
||
# Ajout d'un chemin qui sera utilisé par l'importer
|
||
sys.path.append('dns+pylib://_pylib.hannaeko.eu')
|
||
|
||
from test_pkg.test_module import hello</code></pre>
|
||
<pre><code>test_module.test_pkg._pylib TXT "def hello():\010 print(\"hello world\")\010"
|
||
__init__.test_pkg._pylib TXT ""</code></pre>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="content">
|
||
<h2><code>import_module(name, package=None)</code></h2>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>Utilisation</h3>
|
||
<div class="content">
|
||
<pre>
|
||
<code class="language-python-repl">>>> from importlib import import_module
|
||
>>>
|
||
>>> mon_module = import_module('dns.resolver')
|
||
>>> autre_module = import_module('..resolver', package='dns.name')
|
||
>>>
|
||
>>> mon_module is autre_module
|
||
True</code>
|
||
</pre>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="content">
|
||
<pre><code class="language-python-repl">>>> from importlib import import_module
|
||
>>>
|
||
>>> dns_resolver = import_module('dns.resolver')
|
||
>>>
|
||
>>> msg = dns_resolver.resolve('pycon.fr', 'A')
|
||
>>> print(msg.response.answer)
|
||
[<DNS pycon.fr. IN A RRset: [<185.34.33.85>]>]</code></pre>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="content">
|
||
<pre><code class="language-python-repl">>>> from importlib import import_module
|
||
>>>
|
||
>>> module = import_module('dns.resolver')
|
||
>>> function = getattr(module, 'resolve')
|
||
>>>
|
||
>>> msg = function('pycon.fr', 'A')
|
||
>>> print(msg.response.answer)
|
||
[<DNS pycon.fr. IN A RRset: [<185.34.33.85>]>]</code></pre>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>Exemple: un système de plugins</h3>
|
||
<div class="content">
|
||
<pre><code class="language-yaml">plugins:
|
||
- name: extra_plugins:Greetings
|
||
config:
|
||
who: PyConFr</code></pre>
|
||
<pre><code class="language-python-repl">>>> import demo
|
||
>>> plugins = demo.load_plugins()
|
||
>>> demo.fire_hook(plugins)
|
||
Hello PyConFr</code></pre>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="content">
|
||
<pre><code class="language-python"># extra_plugins.py
|
||
|
||
class Greetings(Plugin):
|
||
def __init__(self, who: str) -> None:
|
||
self.who = who
|
||
|
||
def hook(self) -> None:
|
||
print(f'Greetings {self.who}')</code></pre>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<div class="content">
|
||
<pre><code class="language-python">def load_plugins() -> Sequence[Plugin]:
|
||
# …
|
||
plugins = []
|
||
for plugin_def in config['plugins']:
|
||
plugin_name = plugin_def['name']
|
||
plugin_config = plugin_def['config']
|
||
|
||
module_name, class_name = plugin_name.split(':')
|
||
|
||
module = import_module(module_name)
|
||
plugin_class = getattr(module, class_name)
|
||
plugin = plugin_class(**plugin_config)
|
||
|
||
plugins.append(plugin)
|
||
|
||
return plugins</code></pre>
|
||
</div>
|
||
</section>
|
||
|
||
<script>hljs.highlightAll();</script>
|
||
<script>
|
||
let pages = [];
|
||
|
||
const goToSlide = (relativeIndex) => {
|
||
const pageHeight = window.innerHeight;
|
||
const position = window.pageYOffset;
|
||
const page = pages[Math.ceil(position / pageHeight) + relativeIndex];
|
||
if (page) {
|
||
page.scrollIntoView();
|
||
}
|
||
}
|
||
|
||
const onLoad = () => {
|
||
pages = [...document.querySelectorAll('section')];
|
||
}
|
||
|
||
const onKeyUp = (e) => {
|
||
if (e.key === 'ArrowRight' || e.key === ' ') {
|
||
goToSlide(1);
|
||
e.preventDefault();
|
||
return false;
|
||
} else if (e.key === 'ArrowLeft') {
|
||
goToSlide(-1);
|
||
e.preventDefault();
|
||
}
|
||
}
|
||
|
||
window.addEventListener('load', onLoad, false );
|
||
window.addEventListener('keydown', onKeyUp, false);
|
||
</script>
|
||
</body>
|
||
</html>
|