Describes how to use the Monkey Patch development method and pythonpatch in Python programming.
Monkey patch is to modify the existing code at runtime to achieve the goal of hot patch. This technique is widely used in Eventlet to replace components in the standard library, such as socket. First, let's take a look at the implementation of the simplest monkey patch.
class Foo(object): def bar(self): print 'Foo.bar'def bar(self): print 'Modified bar'Foo().bar()Foo.bar = barFoo().bar()
Because the namespace in Python is open and implemented through dict, it is easy to achieve the purpose of patch.
Python namespace
Python has several namespaces:
The variables defined in the function belong to locals, and the functions defined in the module belong to globals.
Python module Import & Name Lookup
When we import a module, python will do the following:
- Import a module
- Add the module object to sys. modules. Subsequent import of the module will be obtained directly from the dict.
- Add the module object to globals dict.
When we reference a module, it will be searched from globals. To replace a standard module, we need to do the following two things:
Add our module to sys. modules to replace the original module. If the replaced module has not been loaded, We must load it first. Otherwise, the standard module will be loaded for the first time. (An import hook can be used here, but we need to implement this hook by ourselves. We may also use this method to hook module import)
If the replaced module references other modules, we also need to replace them. But here we can modify globals dict and add our module to globals to hook these referenced modules.
Eventlet Patcher Implementation
Now let's take a look at the Patcher call code in eventlet. This Code performs a monkey patch on the standard ftplib and replaces the GreenSocket of eventlet with the standard socket.
From eventlet import patcher # * NOTE: there might be some funny business with the "SOCKS" module # if it even still existsfrom eventlet. green import socketpatcher. inject ('ftplib ', globals (), ('socket', socket) del patcherinject function will inject the socket module of eventlet into the standard ftplib, globals dict is passed in for proper modification. Let's take a look at the implementation of 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 * shocould 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 shoshould cover all use cases but may be slower because there are inevitably redundant or unnecessary imports. "if not exist: # supply some defaults tables = (_ 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
Annotations clearly explain the intent of the Code. The code is easy to understand. Here is a function _ import __, which provides a module name (string) to load a module. The name provided during import or reload 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)
This code is used to add objects in the standard ftplib to the ftplib module of eventlet. Because in eventlet. in ftplib, inject is called and globals is passed in. In inject, we manually _ import _ this module and only get a module object, therefore, the objects in the module are not added to globals and need to be manually added.
The reason for not using the from ftplib import * is that it cannot completely replace ftplib. Because from... Import * imports the public symbol according to the _ init _. py list. In this way, the private symbol starting with the underline is not imported and cannot be completely patched.