为什么在基类上修补类方法会影响子类?

Why does patching a classmethod on a base class effect subclasses?

提问人:Aage Torleif 提问时间:11/7/2023 最后编辑:juanpa.arrivillagaAage Torleif 更新时间:11/7/2023 访问量:76

问:

我在基类上修补 a 时遇到了问题。问题在于修补基类效应,调用子类上的类方法。classmethod

我希望它产生输出,但是在调用 classmethod 之前调用的结果是 .["foo", "bar"]mocker.spy(Foo, "wrapper")["foo", "foo"]

这是怎么回事?

def test_patching_parent_class(mocker):
    """
    pip install pytest pytest-mock
    """
    calls = []

    class Foo:
        def func2(self):
            calls.append("foo")

        @classmethod
        def wrapper(cls):
            cls().func2()

    class Bar(Foo):
        def func2(self):
            calls.append("bar")

    # Remove this line it will produce the expected result
    spy = mocker.spy(Foo, "wrapper")
    Foo.wrapper()
    Bar.wrapper()
    assert calls == ["foo", "bar"]
python mocking pytest 补丁

评论

1赞 Frank Yellin 11/7/2023
没有方法。当您调用 python 时,它会在类层次结构中向上移动,直到它在类层次结构中找到具有该名称的方法,在本例中为 .你已经改变了,但同样的过程仍然发生。Bar.wrapperBar.wrapper()Foo.wrapperFoo.wrapper
0赞 juanpa.arrivillaga 11/7/2023
@FrankYellin是的,但仍然应该只是calls["foo", "bar"]

答:

2赞 juanpa.arrivillaga 11/7/2023 #1

这似乎是 .这是来源的链接pytest_mock.MockFixture.spy

你可以看到,它基本上是从对象(在本例中为类)中检索“方法”,但由于是一个类,并且引用了一个,现在这是一个绑定方法FooobjFoonameclassmethod

method = getattr(obj, name)

它与 绑定这就是类方法的工作方式。它们的作用类似于在实例上调用的常规函数,当它们本身在类上被调用时。如果这是一个常规函数,那么它只是普通函数(因为尝试检索类上的函数属性,而不是在实例上,通常只会返回函数本身)。然后,它创建一个调用该方法的包装器:Foomethod

def wrapper(*args, **kwargs):
    spy_obj.spy_return = None
    spy_obj.spy_exception = None
    try:
        r = method(*args, **kwargs)
    except BaseException as e:
        spy_obj.spy_exception = e
        raise
    else:
        spy_obj.spy_return = r
    return r

这个包装器是用模拟对象修补的:

spy_obj = self.patch.object(obj, name, side_effect=wrapped, autospec=autospec)

它充当您的“间谍”。

但同样,它与 .因此,无论如何调用,它的第一个参数总是绑定 ,从不 。所以解析为而不是.FoowrapperFooBarcls.func2Foo.func2Bar.func2

评论

0赞 blhsing 11/7/2023
我不认为是问题所在。如果您简单地替换为 (参见演示),OP 的代码就会起作用。由于只是一个薄薄的包装器,这表明这是包装器所做的事情导致了错误,我还没有弄清楚,但绝对不是这里的问题。pytest_mock.MockFixture.spyMockerFixture._Patcher.objectunittest.mock.patch.objectMockerFixture._Patcher.objectunittest.mock._patch_objectpytest_mock.MockFixture.spy
0赞 blhsing 11/7/2023
那为什么用代替代替不是问题呢?side_effect=wrappedunittest.mock.patch.objectMockerFixture._Patcher.object
0赞 juanpa.arrivillaga 11/7/2023
@blhsing如果我设置了一个断点,然后不再返回一个对象,还不知道为什么。因此,他们不再被“监视”。Foo.wrapperBar.wrapperMock
0赞 blhsing 11/7/2023
是的,我的断点揭示了同样多的内容。我自己也看不出为什么。好挠头。
1赞 blhsing 11/7/2023
啊,因为 never got ed 返回的对象仍然绑定并且没有成为对象。两种实现之间的不兼容让我失望。因此,您的答案是正确的。+1_patchunittest.mock.patch.objectstartBar.wrapperBarMockpatch.object