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>
 |