Python 仅在一个模块中使用修补方法

Python 仅在一个模块中使用修补方法,python,python-3.x,python-unittest,python-mock,python-unittest.mock,Python,Python 3.x,Python Unittest,Python Mock,Python Unittest.mock,例如,我有一些模块(foo.py)和下一个代码: import requests def get_ip(): return requests.get('http://jsonip.com/').content 模块bar.py具有类似代码: import requests def get_fb(): return requests.get('https://fb.com/').content 我只是不明白为什么接下来会发生: from mock import patch

例如,我有一些模块(
foo.py
)和下一个代码:

import requests

def get_ip():
    return requests.get('http://jsonip.com/').content
模块
bar.py
具有类似代码:

import requests

def get_fb():
    return requests.get('https://fb.com/').content
我只是不明白为什么接下来会发生:

from mock import patch

from foo import get_ip
from bar import get_fb

with patch('foo.requests.get'):
    print(get_ip())
    print(get_fb())
他们是两个被嘲笑的人:

由于使用补丁('foo.requests.get')的
,它似乎只修补
foo.get\u ip
方法,但事实并非如此。 我知道我可以通过
作用域从
调用
,但在某些情况下,我只是在上下文管理器中运行一个调用许多其他方法的方法,我只想在一个模块中修补
请求

有什么办法解决这个问题吗?在不更改模块中的导入的情况下,两个位置
foo.requests.get
bar.requests.get
引用同一个对象,因此在一个位置模拟它,在另一个位置模拟它

想象一下您可能如何实现补丁。您必须找到符号的位置,并用模拟对象替换符号。退出with上下文时,需要恢复符号的原始值。类似于(未经测试):

因此,您的问题是,您正在模拟请求模块中的对象,然后您将从foo和bar引用该对象


按照@elethan的建议,您可以在foo中模拟requests模块,甚至在get方法上提供副作用:

from unittest import mock
import requests

from foo import get_ip
from bar import get_fb

def fake_get(*args, **kw):
    print("calling get with", args, kw)
    return mock.DEFAULT

replacement = mock.MagicMock(requests)
replacement.get = mock.Mock(requests.get, side_effect=fake_get, wraps=requests.get)
with mock.patch('foo.requests', new=replacement):
    print(get_ip())
    print(get_fb())

一个更直接的解决方案是改变代码,以便
foo
bar
将对
get
的引用直接拉到它们的名称空间中

foo.py:

from requests import get

def get_ip():
    return get('http://jsonip.com/').content
bar.py:

from requests import get

def get_ip():
    return get('https://fb.com/').content
main.py:

from mock import patch

from foo import get_ip
from bar import get_fb

with patch('foo.get'):
    print(get_ip())
    print(get_fb())
制作:

<MagicMock name='get().content' id='4350500992'>
b'<!DOCTYPE html>\n<html lang="en" id="facebook" ...


b'\n不要偷那不勒斯人的雷霆,但另一种选择是简单地模仿
foo.requests
,而不是
foo.requests.get

with patch('foo.requests'):
    print(get_ip())
    print(get_fb())
我认为,在您的案例中,这两种方法都会被模拟的原因是,由于
requests.get
没有在
foo.py
中显式导入,
mock
必须在
requests
模块中查找该方法并在那里进行模拟,而不是在已经导入到
foo
requests
对象中模拟它,这样当
bar
以后导入
requests
并访问
requests.get
时,它将获得模拟版本。但是,如果您改为
修补
foo.requests
,则只需修补已导入到
foo
中的模块对象,原始的
请求
模块将不会受到影响


虽然对这个特定问题没有特别的帮助,但对于理解
补丁的微妙之处非常有用,据我所知,保持foo.py和bar.py不变,并模仿
foo.requests
而不是
foo.requests.get
谢谢,但我也知道这个解决方案,并在帖子中提到,
不改变模块中的导入。我希望有一个解决方案,不需要改变脚本imports@elethan这很有效,thnx;)但是为什么在模拟
foo.requests
时,它只模拟
foo.py
中的
请求
,而在模拟
foo.requests.get
时,它模拟
foo.py
bar.py
。你能写出一个完整的答案吗?@hasam我认为这是因为当你模拟
foo.requests
时,它模拟的是已经导入
foo
的模块对象,因此不会影响将导入
bar
的模块对象。但是,当您模拟
foo.requests.get
时,它将查找在
foo
中导入的
requests
对象,然后在原始模块中查找
get
,并对其进行模拟,因此当
bar
导入
requests
时,它将获得模拟的
get
方法。这有意义吗?@elethan是的,谢谢。我想你应该给我的问题写一个答案,我会接受的,因为这正是我所需要的。有任何方法可以对
请求产生
副作用。用这种方法获得
方法吗?@hasam是的,只要你
在你的测试模块中导入foo
,你应该能够做
foo.requests.get.side\u effect=无论什么副作用
,因为
foo.requests.get
将是一个
Mock
对象。我认为使用装饰程序来修补你的函数应该可以做到这一点。
with patch('foo.requests'):
    print(get_ip())
    print(get_fb())