Maison >développement back-end >Tutoriel Python >Comment développer Monkey Patch en programmation Python
La méthode Monkey Patch fait référence à l'ajout de code pendant le processus d'exécution du programme en ajoutant des classes ou des modules sans modifier le code original du programme. Ce qui suit est une explication plus détaillée de la méthode de développement Monkey Patch dans la programmation Python. L'application du
Monkey patch consiste à modifier le code existant au moment de l'exécution pour atteindre l'objectif du hot patch. Cette technique est largement utilisée dans Eventlet pour remplacer des composants de la bibliothèque standard, tels que les sockets. Tout d’abord, jetons un coup d’œil à l’implémentation la plus simple de Monkey Patch.
class Foo(object): def bar(self): print 'Foo.bar' def bar(self): print 'Modified bar' Foo().bar() Foo.bar = bar Foo().bar()
Étant donné que l'espace de noms en Python est ouvert et implémenté via dict, il est facile d'atteindre l'objectif de l'application de correctifs.
Espace de noms Python
Python a plusieurs espaces de noms, à savoir
locals
globaux
builtin
Les variables définies dans les fonctions appartiennent aux variables locales, tandis que les fonctions définies dans les modules appartiennent aux variables globales.
Importation de module Python et recherche de nom
Lorsque nous importons un module, Python fera les choses suivantes
Importer un module
et ajoutez l'objet module à sys.modules. Les importations ultérieures du module seront obtenues directement à partir du dict
. L'objet module est ajouté au dict global
Lorsque nous référençons un module, il sera recherché à partir des globaux. Si nous voulons remplacer un module standard ici, nous devons faire les deux choses suivantes
Ajouter notre propre module à sys.modules et remplacer le module d'origine. Si le module remplacé n'a pas été chargé, nous devons d'abord le charger, sinon le module standard sera chargé la première fois. (Il existe un hook d'importation disponible ici, mais cela nous oblige à implémenter le hook nous-mêmes. Nous pouvons également utiliser cette méthode pour hooker l'importation de module)
Si le module remplacé fait référence à d'autres modules, alors nous devons également le remplacer, mais ici, nous pouvons modifier le dict des globals et ajouter notre module aux globals pour accrocher ces modules référencés.
Implémentation d'Eventlet Patcher
Jetons maintenant un coup d'œil au code d'appel de Patcher dans l'eventlet. Ce code crée un patch singe pour la ftplib standard et remplace le GreenSocket standard de l'événement. événementlet.
from eventlet import patcher # *NOTE: there might be some funny business with the "SOCKS" module # if it even still exists from eventlet.green import socket patcher.inject('ftplib', globals(), ('socket', socket)) del patcher inject函数会将eventlet的socket模块注入标准的ftplib中,globals dict被传入以做适当的修改。 让我们接着来看一下inject的实现。 __exclude = set(('__builtins__', '__file__', '__name__')) def inject(module_name, new_globals, *additional_modules): """Base method for "injecting" greened modules into an imported module. It imports the module specified in *module_name*, arranging things so that the already-imported modules in *additional_modules* are used when *module_name* makes its imports. *new_globals* is either None or a globals dictionary that gets populated with the contents of the *module_name* module. This is useful when creating a "green" version of some other module. *additional_modules* should be a collection of two-element tuples, of the form (, ). If it's not specified, a default selection of name/module pairs is used, which should cover all use cases but may be slower because there are inevitably redundant or unnecessary imports. """ if not additional_modules: # supply some defaults additional_modules = ( _green_os_modules() + _green_select_modules() + _green_socket_modules() + _green_thread_modules() + _green_time_modules()) ## Put the specified modules in sys.modules for the duration of the import saved = {} for name, mod in additional_modules: saved[name] = sys.modules.get(name, None) sys.modules[name] = mod ## Remove the old module from sys.modules and reimport it while ## the specified modules are in place old_module = sys.modules.pop(module_name, None) try: module = __import__(module_name, {}, {}, module_name.split('.')[:-1]) if new_globals is not None: ## Update the given globals dictionary with everything from this new module for name in dir(module): if name not in __exclude: new_globals[name] = getattr(module, name) ## Keep a reference to the new module to prevent it from dying sys.modules['__patched_module_' + module_name] = module finally: ## Put the original module back if old_module is not None: sys.modules[module_name] = old_module elif module_name in sys.modules: del sys.modules[module_name] ## Put all the saved modules back for name, mod in additional_modules: if saved[name] is not None: sys.modules[name] = saved[name] else: del sys.modules[name] return module
Les commentaires expliquent clairement l'intention du code. Le code est relativement facile à comprendre. Il existe une fonction __import__, qui fournit un nom de module (chaîne) pour charger un module. Le nom que nous fournissons lors de l’importation ou du rechargement est un objet.
if new_globals is not None: ## Update the given globals dictionary with everything from this new module for name in dir(module): if name not in __exclude: new_globals[name] = getattr(module, name)
La fonction de ce code est d'ajouter des objets en ftplib standard au module ftplib de eventlet. Parce que nous avons appelé inject dans eventlet.ftplib et transmis les globals, et dans inject, nous __importons manuellement le module et n'avons obtenu qu'un seul objet de module, donc les objets du module ne seront pas ajoutés aux globals et devront être ajoutés manuellement.
La raison pour laquelle from ftplib import * n'est pas utilisé ici est probablement parce qu'il ne peut pas remplacer complètement ftplib. Parce que from...import * importera les symboles publics selon la liste __all__ dans __init__.py, et de cette manière, les symboles privés commençant par un trait de soulignement ne seront pas importés et une correction complète ne pourra pas être réalisée.
Pour plus d'articles liés aux méthodes de développement de Monkey Patch dans la programmation Python, veuillez faire attention au site Web PHP chinois !