英文:
Why doesn't the `nonlocal` keyword propogate the outer-scoped variable to the calling module?
问题
以下是您要翻译的内容:
所以这涉及到一系列可能不太常见的事情:
A.py
from B import call
def make_call():
print("我正在打电话!")
call(phone_number="867-5309")
B.py
def call(phone_number):
pass
test_A.py
import pytest
from A import make_call
@pytest.fixture
def patch_A_call(monkeypatch):
number = "未输入"
number_list = []
def call(phone_number):
nonlocal number
number = phone_number
number_list.append(phone_number)
monkeypatch.setattr("A.call", call)
return (number, number_list)
def test_make_call(patch_A_call):
make_call()
print(f"number: {patch_A_call[0]}")
print(f"number_list: {patch_A_call[1]}")
打印出的内容是:
number: 未输入
number_list: [867-5309]
我预期"867-5309"应该是两个结果的值。
我知道在Python中,列表是通过引用传递的,但我假设nonlocal
声明会沿着链路传递变量。为什么它不起作用呢?
英文:
So this involves a maybe unusual chain of things:
A.py
from B import call
def make_call():
print("I'm making a call!")
call(phone_number="867-5309")
B.py
def call(phone_number):
pass
test_A.py
import pytest
from A import make_call
@pytest.fixture
def patch_A_call(monkeypatch):
number = "NOT ENTERED"
number_list = []
def call(phone_number):
nonlocal number
number = phone_number
number_list.append(phone_number)
monkeypatch.setattr("A.call", call)
return (number, number_list)
def test_make_call(patch_A_call):
make_call()
print(f"number: {patch_A_call[0]}")
print(f"number_list: {patch_A_call[1]}")
What's printed is:
number: NOT ENTERED
number_list: [867-5309]
I expected "867-5309" to be the value for both results.
I know that lists are passed by reference in Python—but I assumed that the nonlocal
declaration would pass the variable along down the chain.
Why doesn't it work this way?
答案1
得分: 2
如果您想查看如何调用`call`,我认为一个更简单的选择是将其替换为一个模拟对象:
```python
from unittest import mock
from A import make_call
@mock.patch('A.call')
def test_make_call(fake_call):
make_call()
assert fake_call.call_args.kwargs['phone_number'] == '867-5309'
在这里,我们将A.call
替换为unittest.mock.Mock
对象。这由make_call
调用,并且模拟对象记录了调用参数供以后检查。
这需要的代码要少得多。
请注意,我在这里使用了一个assert
语句,但如果这是您的目标,您也可以打印出或以其他方式记录phone_number
的值。
您的解决方案的主要问题是,您的patch_A_call
夹具在test_make_call
方法执行之前被调用一次。
所以虽然nonlocal
关键字的工作方式是预期的...您从未看到结果,因为在调用make_call
之前,return (number, number_list)
语句已经运行了。
您在列表中看到结果是因为列表是一个"容器" -- 当调用make_call
时,您将数字添加到它中,并且您可以看到结果,因为返回的列表是从您修补的call
方法内部可用的相同对象。
对于我的解决方案,我们不必使用mock.patch()
; 我们可以像这样重写您的夹具:
import pytest
from unittest import mock
from A import make_call
@pytest.fixture
def patch_A_call(monkeypatch):
call_recorder = mock.Mock()
monkeypatch.setattr("A.call", call_recorder)
return call_recorder
def test_make_call(patch_A_call):
make_call()
assert patch_A_call.call_args.kwargs["phone_number"] == "867-5309"
这几乎实现了相同的目标。
<details>
<summary>英文:</summary>
If you want to see how `call` is called, I think a simpler option is to replace it with a Mock object:
from unittest import mock
from A import make_call
@mock.patch('A.call')
def test_make_call(fake_call):
make_call()
assert fake_call.call_args.kwargs['phone_number'] == '867-5309'
Here, we're replacing `A.call` with a `unittest.mock.Mock` object. This gets called by `make_call`, and the call arguments are recorded by the Mock object for later inspection.
This requires substantially less code.
Note that I'm using an `assert` statement here, but you could instead print out or otherwise record the value of `phone_number` if that's your goal.
---
The primary problem with your solution is that your `patch_A_call` fixture is called once, **before** your `test_make_call` method executes.
So while the `nonlocal` keyword is working as intended...you never see the result, because that `return (number, number_list)` statement ran **before** you made the call to `make_call`.
You see the result in the list because a list is a "container" -- you add the number to it when calling `make_call`, and you can see the result because the returned list is the same object available from inside your patched `call` method.
---
For my solution, we don't have to use `mock.patch()`; we can rewrite your fixture like this:
import pytest
from unittest import mock
from A import make_call
@pytest.fixture
def patch_A_call(monkeypatch):
call_recorder = mock.Mock()
monkeypatch.setattr("A.call", call_recorder)
return call_recorder
def test_make_call(patch_A_call):
make_call()
assert patch_A_call.call_args.kwargs["phone_number"] == "867-5309"
This accomplishes pretty much the same thing.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论