Maison >développement back-end >Tutoriel Python >Comment développer Monkey Patch en programmation Python

Comment développer Monkey Patch en programmation Python

高洛峰
高洛峰original
2017-03-02 16:04:102413parcourir

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 !


Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn