2017-04-23 2 views
2

Mit dem veralteten Modul imp kann ich einen benutzerdefinierten Import-Hook schreiben, der den Quellcode eines Moduls im laufenden Betrieb vor dem Import/der Ausführung durch Python ändert. In Anbetracht des Quellcodes als String source unter dem Namen, den wesentlichen Code benötigt, um ein Modul zu erstellen ist folgende:Wie implementiert man einen Import-Hook, der den Quellcode im laufenden Betrieb mithilfe von importlib ändern kann?

module = imp.new_module(name) 
sys.modules[name] = module 
exec(source, module.__dict__) 

Da imp veraltet ist, würde Ich mag etwas ähnliches mit importlib tun. [BEARBEITEN: es gibt andere imp Methoden, die ersetzt werden müssen, um einen benutzerdefinierten Import-Hook zu erstellen - so die Antwort, die ich suche, ist nicht einfach, den obigen Code zu ersetzen.]

Allerdings habe ich nicht in der Lage zu sehen wie man das macht. Die importlib documentation hat eine function to create modules from "specs", die, soweit ich sagen kann, Objekte sind, die ihre eigenen Loader ohne offensichtliche Möglichkeit, sie neu zu definieren, um in der Lage zu sein, ein Modul aus einer Zeichenfolge zu erstellen.

Ich habe eine minimal example erstellt, um dies zu demonstrieren; Einzelheiten finden Sie in der Readme-Datei.

+0

Wenn Sie einen Blick auf die 'imp.new_module' Dokumentation nehmen, werden Sie' finden Veraltet seit Version 3.4: Verwenden Sie types.ModuleType instead.' Ist das Ihr Problem nicht lösen? –

+0

Ich sah, dass imp.module auf diese Weise ersetzt werden mussten, aber die Dokumentation zeigt an, dass module_from_spec (von importlib) verwendet werden soll. Ich benutze 3 Methoden von imp, um einen benutzerdefinierten Hook-Importer zu machen und finde das Äquivalent für importlib. –

Antwort

5

find_module und load_module sind beide veraltet. Sie müssen zu find_spec und (create_module und exec_module) Modul jeweils wechseln. Einzelheiten finden Sie unter importlibdocumentation.

Sie müssen auch prüfen, ob Sie eine MetaPathFinder oder eine PathEntryFinder als System verwenden möchten, um sie aufzurufen ist anders. Das heißt, der Metapathfinder geht zuerst und kann eingebaute Module überschreiben, während der Pfadeintragsfinder speziell für Module funktioniert, die unter sys.path gefunden werden.

Das folgende ist ein sehr einfacher Importeur, der versucht, die gesamte Importmaschinerie für zu ersetzen. Es zeigt, wie die Funktionen (find_spec, und exec_module) zu verwenden sind.

import sys 
import os.path 

from importlib.abc import Loader, MetaPathFinder 
from importlib.util import spec_from_file_location 

class MyMetaFinder(MetaPathFinder): 
    def find_spec(self, fullname, path, target=None): 
     if path is None or path == "": 
      path = [os.getcwd()] # top level import -- 
     if "." in fullname: 
      *parents, name = fullname.split(".") 
     else: 
      name = fullname 
     for entry in path: 
      if os.path.isdir(os.path.join(entry, name)): 
       # this module has child modules 
       filename = os.path.join(entry, name, "__init__.py") 
       submodule_locations = [os.path.join(entry, name)] 
      else: 
       filename = os.path.join(entry, name + ".py") 
       submodule_locations = None 
      if not os.path.exists(filename): 
       continue 

      return spec_from_file_location(fullname, filename, loader=MyLoader(filename), 
       submodule_search_locations=submodule_locations) 

     return None # we don't know how to import this 

class MyLoader(Loader): 
    def __init__(self, filename): 
     self.filename = filename 

    def create_module(self, spec): 
     return None # use default module creation semantics 

    def exec_module(self, module): 
     with open(self.filename) as f: 
      data = f.read() 

     # manipulate data some way... 

     exec(data, vars(module)) 

def install(): 
    """Inserts the finder into the import machinery""" 
    sys.meta_path.insert(0, MyMetaFinder()) 

Als nächstes ist eine etwas empfindlichere Version, die versucht, mehr der Import-Maschinen wiederzuverwenden. Daher müssen Sie nur definieren, wie die Quelle des Moduls abgerufen werden soll.

import sys 
from os.path import isdir 
from importlib import invalidate_caches 
from importlib.abc import SourceLoader 
from importlib.machinery import FileFinder 


class MyLoader(SourceLoader): 
    def __init__(self, fullname, path): 
     self.fullname = fullname 
     self.path = path 

    def get_filename(self, fullname): 
     return self.path 

    def get_data(self, filename): 
     """exec_module is already defined for us, we just have to provide a way 
     of getting the source code of the module""" 
     with open(filename) as f: 
      data = f.read() 
     # do something with data ... 
     # eg. ignore it... return "print('hello world')" 
     return data 


loader_details = MyLoader, [".py"] 

def install(): 
    # insert the path hook ahead of other path hooks 
    sys.path_hooks.insert(0, FileFinder.path_hook(loader_details)) 
    # clear any loaders that might already be in use by the FileFinder 
    sys.path_importer_cache.clear() 
    invalidate_caches() 
Verwandte Themen