Python의 다른 모듈에서 클래스를 패치하는 Monkey
다른 사람이 작성한 모듈을 사용하고 있습니다. __init__
모듈에 정의 된 클래스 의 메서드 를 원숭이 패치하고 싶습니다 . 이 작업을 수행하는 방법을 보여주는 예제는 모두 내가 클래스를 직접 호출한다고 가정했습니다 (예 : Monkey-patch Python class ). 그러나 이것은 사실이 아닙니다. 제 경우에는 클래스가 다른 모듈의 함수 내에서 초기화됩니다. 아래의 (매우 단순화 된) 예를 참조하십시오.
thirdpartymodule_a.py
class SomeClass(object):
def __init__(self):
self.a = 42
def show(self):
print self.a
thirdpartymodule_b.py
import thirdpartymodule_a
def dosomething():
sc = thirdpartymodule_a.SomeClass()
sc.show()
mymodule.py
import thirdpartymodule_b
thirdpartymodule.dosomething()
mymodule.py에서 호출 될 때 예를 들어 42 대신 43을 인쇄 하도록 __init__
메서드 를 수정하는 방법이 있습니까? 이상적으로는 기존 방법을 래핑 할 수 있습니다.SomeClass
dosomething
다른 스크립트가 기존 기능에 의존하기 때문에 thirdpartymodule * .py 파일을 변경할 수 없습니다. 변경해야하는 변경 사항이 매우 간단하므로 모듈의 자체 복사본을 만들 필요가 없습니다.
2013-10-24 수정
위의 예에서 작지만 중요한 세부 사항을 간과했습니다. SomeClass
다음 thirdpartymodule_b
과 같이 가져옵니다 from thirdpartymodule_a import SomeClass
..
FJ I에 의해 제안 된 패치를하려면에 복사를 교체해야 thirdpartymodule_b
보다는 thirdpartymodule_a
. 예 : thirdpartymodule_b.SomeClass.__init__ = new_init
.
다음이 작동합니다.
import thirdpartymodule_a
import thirdpartymodule_b
def new_init(self):
self.a = 43
thirdpartymodule_a.SomeClass.__init__ = new_init
thirdpartymodule_b.dosomething()
새 초기화가 이전 초기화를 호출하도록하려면 new_init()
정의를 다음으로 바꿉니다.
old_init = thirdpartymodule_a.SomeClass.__init__
def new_init(self, *k, **kw):
old_init(self, *k, **kw)
self.a = 43
mock
라이브러리를 사용하십시오 .
import thirdpartymodule_a
import thirdpartymodule_b
import mock
def new_init(self):
self.a = 43
with mock.patch.object(thirdpartymodule_a.SomeClass, '__init__', new_init):
thirdpartymodule_b.dosomething() # -> print 43
thirdpartymodule_b.dosomething() # -> print 42
또는
import thirdpartymodule_b
import mock
def new_init(self):
self.a = 43
with mock.patch('thirdpartymodule_a.SomeClass.__init__', new_init):
thirdpartymodule_b.dosomething()
thirdpartymodule_b.dosomething()
더럽지 만 작동합니다.
class SomeClass2(object):
def __init__(self):
self.a = 43
def show(self):
print self.a
import thirdpartymodule_b
# Monkey patch the class
thirdpartymodule_b.thirdpartymodule_a.SomeClass = SomeClass2
thirdpartymodule_b.dosomething()
# output 43
Andrew Clark의 것과 매우 유사한 또 다른 가능한 접근 방식 은 wrapt 라이브러리 를 사용하는 것 입니다. 다른 유용한 것들 중에서이 라이브러리는 wrap_function_wrapper
및 patch_function_wrapper
도우미를 제공 합니다. 다음과 같이 사용할 수 있습니다.
import wrapt
import thirdpartymodule_a
import thirdpartymodule_b
@wrapt.patch_function_wrapper(thirdpartymodule_a.SomeClass, '__init__')
def new_init(wrapped, instance, args, kwargs):
# here, wrapped is the original __init__,
# instance is `self` instance (it is not true for classmethods though),
# args and kwargs are tuple and dict respectively.
# first call original init
wrapped(*args, **kwargs) # note it is already bound to the instance
# and now do our changes
instance.a = 43
thirdpartymodule_b.do_something()
또는 때로는 wrap_function_wrapper
데코레이터가 아니지만 같은 방식으로 작동하는 것을 사용하고 싶을 수도 있습니다 .
def new_init(wrapped, instance, args, kwargs):
pass # ...
wrapt.wrap_function_wrapper(thirdpartymodule_a.SomeClass, '__init__', new_init)
약간 덜 해킹 된 버전 중 하나는 전역 변수를 매개 변수로 사용합니다.
sentinel = False
class SomeClass(object):
def __init__(self):
global sentinel
if sentinel:
<do my custom code>
else:
# Original code
self.a = 42
def show(self):
print self.a
sentinel이 거짓이면 이전과 똑같이 작동합니다. 그것이 사실이라면, 당신은 새로운 행동을 얻습니다. 코드에서 다음을 수행합니다.
import thirdpartymodule_b
thirdpartymodule_b.sentinel = True
thirdpartymodule.dosomething()
thirdpartymodule_b.sentinel = False
물론 기존 코드에 영향을주지 않고이를 적절한 수정으로 만드는 것은 매우 간단합니다. 그러나 다른 모듈을 약간 변경해야합니다.
import thirdpartymodule_a
def dosomething(sentinel = False):
sc = thirdpartymodule_a.SomeClass(sentinel)
sc.show()
init에 전달하십시오.
class SomeClass(object):
def __init__(self, sentinel=False):
if sentinel:
<do my custom code>
else:
# Original code
self.a = 42
def show(self):
print self.a
Existing code will continue to work - they will call it with no arguments, which will keep the default false value, which will keep the old behaviour. But your code now has a way to tell the whole stack on down that new behaviour is available.
Here is an example I came up with to monkeypatch Popen
using pytest
.
import the module:
# must be at module level in order to affect the test function context
from some_module import helpers
A MockBytes
object:
class MockBytes(object):
all_read = []
all_write = []
all_close = []
def read(self, *args, **kwargs):
# print('read', args, kwargs, dir(self))
self.all_read.append((self, args, kwargs))
def write(self, *args, **kwargs):
# print('wrote', args, kwargs)
self.all_write.append((self, args, kwargs))
def close(self, *args, **kwargs):
# print('closed', self, args, kwargs)
self.all_close.append((self, args, kwargs))
def get_all_mock_bytes(self):
return self.all_read, self.all_write, self.all_close
A MockPopen
factory to collect the mock popens:
def mock_popen_factory():
all_popens = []
class MockPopen(object):
def __init__(self, args, stdout=None, stdin=None, stderr=None):
all_popens.append(self)
self.args = args
self.byte_collection = MockBytes()
self.stdin = self.byte_collection
self.stdout = self.byte_collection
self.stderr = self.byte_collection
pass
return MockPopen, all_popens
And an example test:
def test_copy_file_to_docker():
MockPopen, all_opens = mock_popen_factory()
helpers.Popen = MockPopen # replace builtin Popen with the MockPopen
result = copy_file_to_docker('asdf', 'asdf')
collected_popen = all_popens.pop()
mock_read, mock_write, mock_close = collected_popen.byte_collection.get_all_mock_bytes()
assert mock_read
assert result.args == ['docker', 'cp', 'asdf', 'some_container:asdf']
This is the same example, but using pytest.fixture
it overrides the builtin Popen
class import within helpers
:
@pytest.fixture
def all_popens(monkeypatch): # monkeypatch is magically injected
all_popens = []
class MockPopen(object):
def __init__(self, args, stdout=None, stdin=None, stderr=None):
all_popens.append(self)
self.args = args
self.byte_collection = MockBytes()
self.stdin = self.byte_collection
self.stdout = self.byte_collection
self.stderr = self.byte_collection
pass
monkeypatch.setattr(helpers, 'Popen', MockPopen)
return all_popens
def test_copy_file_to_docker(all_popens):
result = copy_file_to_docker('asdf', 'asdf')
collected_popen = all_popens.pop()
mock_read, mock_write, mock_close = collected_popen.byte_collection.get_all_mock_bytes()
assert mock_read
assert result.args == ['docker', 'cp', 'asdf', 'fastload_cont:asdf']
참고URL : https://stackoverflow.com/questions/19545982/monkey-patching-a-class-in-another-module-in-python
'programing tip' 카테고리의 다른 글
Python을 사용하여 디렉토리 내용을 디렉토리에 복사 (0) | 2020.12.08 |
---|---|
사용자 정의 비교기를 사용하여 C ++에서 priority_queue 선언 (0) | 2020.12.08 |
npm 패키지를 제거하는 방법은 무엇입니까? (0) | 2020.12.08 |
Pandas에서 색인 이름 제거 (0) | 2020.12.08 |
Docker 분리 모드 (0) | 2020.12.08 |