2016-11-16 37 views
7

Chciałbym, aby niektóre wywołania zwrotne były uruchamiane po zaimportowaniu określonego modułu. Na przykład (przy użyciu fałszywych @imp.when_imported funkcję, która tak naprawdę nie istnieje):Haki importu postów w Pythonie 3

@imp.when_imported('numpy') 
def set_linewidth(numpy): 
    import shutil 
    numpy.set_printoptions(linewidth=shutil.get_terminal_size()[0]) 

Funkcja ta została zaprojektowana w PEP 369: Post import hooks ale została wycofana z powodu:

Ten PEP został wycofany przez jego autora, Tak duża część szczegółowego projektu nie jest już ważna po migracji do importlib w Pythonie 3.3.

Ale importlib nie ma jednoznacznego rozwiązania. Jak używać importlib do implementacji haka po importu?

+2

Czy możesz dodać kod na samym końcu modułu? – BallpointBen

+0

Nie, chcę to zrobić dla modułów, których nie posiadam (szczególnie [numpy] (http://www.numpy.org)). –

+0

Czy musi to być zaraz po imporcie, czy może to być przed kodem głównym? –

Odpowiedz

3

Moduł wrapt zapewnia implementację tego.

Obejrzyj ten film o wrapt, w tym tej funkcji:

Nie myśl dokumentacja dla wrapt wspomina go jeszcze.

Niektóre z tych stanowisk blogów na koniec:

mówić o tym jednak.

Istnieje moduł dodatkowy dla wrapt o nazwie autowrapt, który umożliwia wykonywanie patchowania małp przy użyciu tego mechanizmu bez konieczności zmiany samego kodu aplikacji, aby go uruchomić.

+0

Dzięki za napiwek! Chciałbym unikać polegania na modułach stron trzecich, więc szukałem prostego rozwiązania, używając 'importlib', ponieważ PEP 369 sugerował, że jeden był łatwy. Jednak patrząc na [implementację wrapt] (https://github.com/GrahamDumpleton/wrapt/blob/develop/src/wrapt/importer.py) wydaje się, że odpowiedź na moje pytanie nie jest tak prosta. –

+0

Sugeruję zaktualizowanie rozwiązania do linku do https://github.com/GrahamDumpleton/wrapt/blob/master/src/wrapt/importer.py, który jest pełną implementacją PEP 369. Przypuszczam, że mógłbym skopiować plik i pasek aż do najpotrzebniejszych rzeczy, ale na razie po prostu użyję owijki. Dzięki! –

-2

to działa?

import importlib 

class ImportHook: 

    def __init__(self, func): 
     self.func = func 
     self.module = None 

    def __enter__(self): 
     return self 

    def get_module(self, module_name): 
     self.module = importlib.import_module(module_name) 
     return self.module 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     if self.module is not None: 
      self.func(self.module) 

def set_linewidth(module): 
    import shutil 
    module.set_printoptions(linewidth=shutil.get_terminal_size()[0]) 

with ImportHook(set_linewidth) as hook: 
    numpy = hook.get_module('numpy') 
+0

Nie. Nie rozumiem, jak to by działało. AFAICT, wszystko to tworzy zmienną 'numpy'. Jestem prawie pewien, że rozwiązanie wymaga ustawienia 'sys.path_hooks' lub' sys.meta_path'. –

+0

Blok 'with as' importuje numpy i wywołuje hook po zakończeniu. – BallpointBen

+0

@BallpointBen: Ale nie potrzebujesz do tego haka. Jeśli mimo to importujesz 'numpy', możesz po prostu uruchomić kod" hook ". Celem jest zarejestrowanie haka _ w case_ ktoś importuje 'numpy' później, ale nie zaimportowanie' numpy' w ogóle, chyba że ktoś go zaimportuje. Kod, który importuje 'numpy' nie wie o twoim haku i nie można go zmienić, aby dodać do niego obsługę. – ShadowRanger

2

byłbym wstrząśnięty, aby dowiedzieć się, że jest to najlepszy sposób to zrobić ... Jednakże, ponieważ wczesne wersje python2.x, małpa łatanie __import__ została poparta. Możemy skorzystać z tego tutaj:

try: 
    import builtins # python3.x 
except ImportError: 
    import __builtin__ as builtins # python2.x 
import sys 
import collections 

_builtin_import = builtins.__import__ 

def _my_import(name, globals=None, locals=None, fromlist=(), level=0): 
    already_imported = name in sys.modules 

    mod = _builtin_import(
     name, 
     globals=globals, 
     locals=locals, 
     fromlist=fromlist, 
     level=level) 

    if not already_imported and name in _post_import_hooks: 
     for hook in _post_import_hooks[name]: 
      hook() 
    return mod 

builtins.__import__ = _my_import 

_post_import_hooks = collections.defaultdict(list) 

def on_import(name): 
    def decorator(func): 
     _post_import_hooks[name].append(func) 
     return func 
    return decorator 

@on_import('numpy') 
def print_hi(): 
    print('Hello Numpy') 

print('before numpy') 
import numpy 
print('after numpy') 

Ta odpowiedź sprawia super- prosty rejestr rejestrowania wywołań zwrotnych. Dekorator właśnie rejestruje funkcję, a następnie ją zwraca. Nie sprawdza on wcale, czy moduł jest już załadowany, na przykład), ale można go łatwo rozszerzyć.

Oczywiście wadą jest to, że jeśli jakiś inny moduł zdecyduje się na patch małpki __import__, to masz pecha - albo ten moduł, albo ten drugi, prawdopodobnie się zepsuje.

Testowałem to i wygląda na to, że działa zarówno na python2.x jak i python3.x.

+0

Zastąpienie '' __import__'' zostało zmarszczone przez prawie tak długo, jak długo istniało. Jednym z powodów zmiany mechanizmu importu i wprowadzenia "sys.meta_path" było powstrzymanie ludzi. –

+0

@GrahamDumpleton - Pewnie. Podaję chociaż jeden powód, dla którego jest źle w mojej odpowiedzi (jeśli wiele modułów spróbuje go zastąpić, często tylko jeden się powiedzie). Stwierdzam również, że byłbym zszokowany, gdyby to był najlepszy sposób. Nadal czekam, aby dowiedzieć się, co jest najlepszym sposobem. Być może muszę poświęcić trochę czasu na próbę odkrycia kodu źródłowego pakietu, jaki proponujesz powyżej ... – mgilson