可以对函数体中引入的局部变量进行 monkeypatch 吗?

it is possible to monkeypatch a local variable introduced in a function body?

py.test monkeypatching/mocking documentation中没有提到,但是可以对函数体中引入的局部变量进行monkeypatch?

我的实验:

def my_method():
    my_var = 'foo'
    return my_var[:2]

测试是:

def test_my_method(monkeypatch):
    monkeypatch.setattr(my_module.MyClass.my_method.my_var, lambda: 'bar')
    assert my_method() == 'ba'

AttributeError: 'function' object at MyClass.my_method has no attribute 'my_var'

这是不可能的,因为变量不提前存在,而且据我所知,py.test 无法挂接到局部变量的创建。

稍加小心,可以使用 ctypes.

修补函数代码对象中的常量
import ctypes
from contextlib import contextmanager

def tuple_setitem(tup, index, item):
    obj = ctypes.py_object(tup)
    item = ctypes.py_object(item)
    ref_count = ctypes.c_long.from_address(id(tup))
    original_count = ref_count.value
    if original_count != 1:
        ref_count.value = 1
    ctypes.pythonapi.Py_IncRef(item)
    ctypes.pythonapi.PyTuple_SetItem(obj, ctypes.c_ssize_t(index), item)
    ref_count.value = original_count

@contextmanager
def patch_tuple_item(tup, index, item):
    old = tup[index]
    try:
        tuple_setitem(tup, index, item)
        yield
    finally:
        tuple_setitem(tup, index, old)

演示:

>>> def my_method():
...     my_var = "foo"
...     return my_var[:2]
...
>>> consts = my_method.__code__.co_consts
>>> consts
(None, 'foo', 2)
>>> with patch_tuple_item(consts, index=1, item="bar"):
...     print(my_method())
...
ba
>>> print(my_method())
fo