Pythonのプログラミングの中でMonkey Patchのサルのつぎの開発方式の運用に対して詳しく説明します。
Monkey patchとは、運行時に既存のコードを修正し、hot patchを達成する目的です。Eventletでは、標準ライブラリのコンポーネント、例えばsocketを置き換えるために、この技法が多く使われています。まず簡単なモンキーパスの実現を見てみます。
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 ftplib import*を使わない理由は、ftplibを完全に置き換える目的ができないからです。from…import*によってはinit_.pyの中の_uall_このリストはpublic smbolを導入していますが、このようにアンダースコアの先頭のprvate smbolは導入されず、完全なpatchができません。
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があります。それぞれです。
Python module Import&Name Look up
私達はimportが一つのmoduleを作る時、pythonは次のようなことをします。
私達自身の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ができません。