Home  >  Article  >  Backend Development  >  How to develop Monkey Patch in Python programming

How to develop Monkey Patch in Python programming

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

The Monkey Patch monkey patch method refers to adding code during the running process of the program by adding classes or modules without modifying the original code of the program. The following is a further detailed explanation of the Monkey Patch monkey patch development method in Python programming. The application of

Monkey patch is to modify the existing code at runtime to achieve the purpose of hot patch. This technique is used extensively in Eventlet to replace components in the standard library, such as sockets. First, let’s take a look at the simplest implementation of monkey patch.

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

def bar(self):
  print 'Modified bar'

Foo().bar()

Foo.bar = bar

Foo().bar()

Since the name space in Python is open and implemented through dict, it is easy to achieve the purpose of patching.

Python namespace

Python has several namespaces, namely

  • locals

  • globals

  • builtin

Variables defined within functions belong to locals, while functions defined within modules belong to globals.

Python module Import & Name Lookup

When we import a module, python will do the following things

  • Import a module

  • Add the module object to sys.modules, and subsequent imports of the module will be obtained directly from the dict

  • will The module object is added to the globals dict

When we reference a module, it will be searched from globals. If we want to replace a standard module here, we have to do the following two things

Add our own module to sys.modules and replace the original module. If the replaced module has not been loaded, then we have to load it first, otherwise the standard module will be loaded the first time. (There is an import hook available here, but this requires us to implement the hook ourselves. We may also use this method hook module import)
If the replaced module references other modules, then we also need to replace it, but here we You can modify the globals dict and add our module to globals to hook these referenced modules.
Eventlet Patcher Implementation

Now let’s take a look at the calling code of Patcher in the eventlet. This code makes a monkey patch to the standard ftplib and replaces the standard GreenSocket of the eventlet. socket.

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

Comments explain the intent of the code more clearly. The code is relatively easy to understand. There is a function __import__, which provides a module name (string) to load a module. The name we provide when importing or reloading is an object.

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)

The function of this code is to add objects in standard ftplib to the ftplib module of eventlet. Because we called inject in eventlet.ftplib and passed in globals, and in inject we manually __import__ the module and only got one module object, so the objects in the module will not be added to globals and need to be added manually. .
The reason why from ftplib import * is not used here is probably because it cannot completely replace ftplib. Because from...import * will import public symbols based on the __all__ list in __init__.py, and private symbols starting with an underscore will not be imported, and complete patching cannot be achieved.

For more articles related to Monkey Patch development methods in Python programming, please pay attention to the PHP Chinese website!


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn