Heim  >  Artikel  >  Backend-Entwicklung  >  So entwickeln Sie Monkey Patch in der Python-Programmierung

So entwickeln Sie Monkey Patch in der Python-Programmierung

高洛峰
高洛峰Original
2017-03-02 16:04:102308Durchsuche

Die Monkey Patch-Methode bezieht sich auf das Hinzufügen von Code während des laufenden Prozesses des Programms durch Hinzufügen von Klassen oder Modulen, ohne den ursprünglichen Code des Programms zu ändern. Im Folgenden finden Sie eine weitere detaillierte Erläuterung der Monkey Patch-Entwicklungsmethode Die Anwendung des

Monkey-Patches besteht darin, den vorhandenen Code zur Laufzeit zu ändern, um den Zweck des Hot-Patches zu erreichen. Diese Technik wird in Eventlet häufig verwendet, um Komponenten in der Standardbibliothek, wie z. B. Sockets, zu ersetzen. Schauen wir uns zunächst die einfachste Implementierung von Monkey Patch an.

class Foo(object):
  def bar(self):
    print 'Foo.bar'

def bar(self):
  print 'Modified bar'

Foo().bar()

Foo.bar = bar

Foo().bar()

Da der Namespace in Python offen ist und über dict implementiert wird, ist es einfach, den Zweck des Patchens zu erreichen.

Python-Namespace

Python hat mehrere Namespaces, nämlich

  • Locals

  • globals

  • eingebaut

In Funktionen definierte Variablen gehören zu lokalen Variablen, während in Modulen definierte Funktionen zu globalen Variablen gehören.

Python-Modulimport und Namenssuche

Wenn wir ein Modul importieren, führt Python die folgenden Dinge aus:

  • Importieren eines Modul

  • und fügen Sie das Modulobjekt zu sys.modules hinzu. Nachfolgende Importe des Moduls werden direkt aus dem Diktat abgerufen Modulobjekt wird zum Globals-Dikt hinzugefügt

  • Wenn wir auf ein Modul verweisen, wird es anhand von Globals durchsucht. Wenn wir hier ein Standardmodul ersetzen möchten, müssen wir die folgenden zwei Dinge tun:

  • Unser eigenes Modul zu sys.modules hinzufügen und das ursprüngliche Modul ersetzen. Wenn das ersetzte Modul nicht geladen wurde, müssen wir es zuerst laden, sonst wird das Standardmodul beim ersten Mal geladen. (Hier ist ein Import-Hook verfügbar, aber dazu müssen wir den Hook selbst implementieren. Wir können diese Methode auch zum Hooken des Modulimports verwenden.)
Wenn das ersetzte Modul auf andere Module verweist, müssen wir es auch ersetzen, aber Hier können Sie das Globals-Dikt ändern und unser Modul zu Globals hinzufügen, um diese referenzierten Module einzubinden.

Eventlet-Patcher-Implementierung



Werfen wir nun einen Blick auf den aufrufenden Code von Patcher im Eventlet. Dieser Code erstellt einen Monkey-Patch für die Standard-FTPLIB und ersetzt den Standard-GreenSocket des Eventlet.-Socket.

Kommentare erläutern die Absicht des Codes klar. Der Code ist relativ einfach zu verstehen. Es gibt eine Funktion __import__, die einen Modulnamen (String) zum Laden eines Moduls bereitstellt. Der Name, den wir beim Importieren oder Neuladen angeben, ist ein Objekt.
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

Die Funktion dieses Codes besteht darin, Objekte in Standard-ftplib zum ftplib-Modul von Eventlet hinzuzufügen. Da wir inject in eventlet.ftplib aufgerufen und Globals übergeben haben und in inject das Modul manuell __import__ und nur ein Modulobjekt erhalten haben, werden die Objekte im Modul nicht zu Globals hinzugefügt und müssen manuell hinzugefügt werden.
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)
Der Grund, warum from ftplib import * hier nicht verwendet wird, liegt wahrscheinlich darin, dass es ftplib nicht vollständig ersetzen kann. Denn from...import * importiert öffentliche Symbole gemäß der __all__-Liste in __init__.py und auf diese Weise werden private Symbole, die mit einem Unterstrich beginnen, nicht importiert und ein vollständiges Patchen kann nicht erreicht werden.

Weitere Artikel zu Monkey Patch-Entwicklungsmethoden in der Python-Programmierung finden Sie auf der chinesischen PHP-Website!



Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn