몽키패치(Monkey Patch) 방식은 프로그램의 원본 코드를 수정하지 않고 클래스나 모듈을 추가하여 프로그램을 실행하는 과정에서 코드를 추가하는 것을 말합니다.
Monkey 패치의 적용은 핫 패치의 목적을 달성하기 위해 런타임에 기존 코드를 수정하는 것입니다. 이 기술은 소켓과 같은 표준 라이브러리의 구성 요소를 대체하기 위해 Eventlet에서 광범위하게 사용됩니다. 먼저, 가장 간단한 원숭이 패치 구현을 살펴보겠습니다.
class Foo(object): def bar(self): print 'Foo.bar' def bar(self): print 'Modified bar' Foo().bar() Foo.bar = bar Foo().bar()
파이썬의 네임스페이스는 개방형이고 dict를 통해 구현되기 때문에 패치 목적을 달성하기 쉽습니다.
Python 네임스페이스
Python에는 여러 네임스페이스가 있습니다. 즉,
locals
globals
builtin
함수 내에 정의된 변수는 로컬에 속하고, 모듈 내에 정의된 함수는 전역에 속합니다.
Python 모듈 가져오기 및 이름 조회
모듈을 가져올 때 Python은 다음 작업을 수행합니다.
module
모듈 개체를 sys.modules에 추가합니다. 이후 모듈 가져오기는
에서 직접 가져옵니다. 모듈 객체는 전역 dict
에 추가됩니다. 모듈을 참조하면 전역에서 검색됩니다. 여기서 표준 모듈을 교체하려면
sys.modules에 자체 모듈을 추가하고 원래 모듈을 교체해야 합니다. 교체된 모듈이 로드되지 않은 경우 먼저 로드해야 하며, 그렇지 않으면 표준 모듈이 처음으로 로드됩니다. (여기서 사용할 수 있는 가져오기 후크가 있지만 이를 위해서는 후크를 직접 구현해야 합니다. 모듈 가져오기를 후크하는 데 이 방법을 사용할 수도 있습니다.)
교체된 모듈이 다른 모듈을 참조하는 경우 해당 모듈도 교체해야 하지만, 여기서는 전역 dict를 수정하고 모듈을 전역에 추가하여 이러한 참조 모듈을 연결할 수 있습니다.
Eventlet Patcher 구현
이제 이벤트렛에서 Patcher의 호출 코드를 살펴보겠습니다. 이 코드는 표준 ftplib에 대한 Monkey 패치를 만들고 ftplib의 표준 GreenSocket을 대체합니다. 이벤트렛.소켓.
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__ 함수가 있습니다. 가져오거나 다시 로드할 때 제공하는 이름은 개체입니다.
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에서 주입을 호출하고 전역을 전달했으며 주입에서는 모듈을 수동으로 __import__하고 하나의 모듈 객체만 얻었으므로 모듈의 객체는 전역에 추가되지 않으며 수동으로 추가해야 합니다.
여기서 from ftplib import *를 사용하지 않는 이유는 아마도 ftplib를 완전히 대체할 수 없기 때문일 것입니다. from...import *는 __init__.py의 __all__ 목록을 기반으로 공용 기호를 가져오고 밑줄로 시작하는 전용 기호는 가져오지 않으며 완전한 패치를 달성할 수 없습니다.
Python 프로그래밍의 Monkey Patch 개발 방법과 관련된 더 많은 기사를 보려면 PHP 중국어 웹사이트를 주목하세요!