Pythonのプログラミングの中でMonkey Patchのサルのつぎの開発方式の運用に対して詳しく説明します。


Monkey patchとは、運行時に既存のコードを修正し、hot patchを達成する目的です。Eventletでは、標準ライブラリのコンポーネント、例えばsocketを置き換えるために、この技法が多く使われています。まず簡単なモンキーパスの実現を見てみます。

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

def bar(self):
  print 'Modified bar'

Foo().bar()

Foo.bar = bar

Foo().bar()

Pythonの名前空間は開放的で、dictを通じて実現されるので、簡単にpatchの目的に達することができます。
Python namespace
Pythonにはいくつかのnamespaceがあります。それぞれです。
  • locas
  • globals
  • builtin
  • ここで定義されている変数はlocasに属し、モジュール内で定義されている関数はglobalsに属します。
    Python module Import&Name Look up
    私達はimportが一つのmoduleを作る時、pythonは次のようなことをします。
  • module
  • を導入します。
  • は、moduleオブジェクトをsys.modulesに追加し、その後のmoduleの導入はそのままこのdictから
  • を得る。
  • globals dictにmoduleオブジェクトを追加する
  • モジュールを参照すると、globalsから検索します。ここで標準モジュールを交換するなら、次の二つのことをします。
    私達自身のmoduleをsys.modulesに加入して、元のモジュールを交換します。もしモジュールがロードされていないなら、先にそれをロードしなければなりません。そうでなければ、初めて読み込む時は標準モジュールもロードします。ここでは、import Hookを使ってもいいですが、これは自分で実現する必要があります。この方法を使ってもいいかもしれません。
    もしモジュールが他のモジュールを引用したら、私たちも交替しなければなりませんが、ここではglobals dictを修正して、globalsに参加して、これらの参照されているモジュールに加入します。
    Eventlet Patch Implementation
    まず、eventletの中のPatchのコールコードを見てみましょう。このコードは標準のftplibに対してmokey patchを作り、eventletのGreen Socketを標準の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
    
    
    注釈はコードの意図を明確に説明しています。コードはやはり分かりやすいです。ここには関数があります。import_,この関数はモジュール名(文字列)を提供して、モジュールをロードします。私たちはimportやreloadで提供した名前が対象です。
    
    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)
    
    このコードの役割は、標準的なftplibのオブジェクトをeventletのftplibモジュールに追加することです。私達はeventlet.ftplibでinjectを呼び出して、globalsに入ってきました。injectの中で私達は手動でimport.このmoduleではモジュールオブジェクトが一つしか入手できませんので、モジュール内のオブジェクトはglobalsに入れられません。手動で追加する必要があります。
    ここでfrom ftplib import*を使わない理由は、ftplibを完全に置き換える目的ができないからです。from…import*によってはinit_.pyの中の_uall_このリストはpublic smbolを導入していますが、このようにアンダースコアの先頭のprvate smbolは導入されず、完全なpatchができません。