如何在不模拟的情况下临时替换Python类方法?

问题描述 投票:0回答:1

我一直在想替换Python 3.8中观察到的类方法时的这种行为,并得出结论我不理解它。我怀疑这可能与@classmethod装饰器的丢失或类似原因有关,但很茫然。

  1. 那里到底出了什么问题?

  2. 什么是使最后一种情况起作用的好方法不使用模拟库

注意:我知道以下代码不是最佳实践。这是关于尝试了解有关Python的更多信息并了解幕后发生的事情。

from unittest import mock


class SomeClass:
    @classmethod
    def hello(cls) -> str:
        return cls.__name__


class DerivedClass(SomeClass):
    pass


def test_works_as_expected():
    assert SomeClass.hello() == 'SomeClass'           # True
    assert DerivedClass.hello() == 'DerivedClass'     # True


def test_replace_with_mock():
    # This works just fine
    assert DerivedClass.hello() == 'DerivedClass'     # True

    with mock.patch.object(SomeClass, 'hello', new=lambda: 'replacement'):
        assert DerivedClass.hello() == 'replacement'  # True

    assert DerivedClass.hello() == 'DerivedClass'     # True


def test_this_does_not_work():
    assert DerivedClass.hello() == 'DerivedClass'     # True

    original_fn = SomeClass.hello
    SomeClass.hello = lambda: 'replacement'
    assert DerivedClass.hello() == 'replacement'      # True
    SomeClass.hello = original_fn                     # This should put things back in order, but does not

    assert DerivedClass.hello() == 'DerivedClass'     # AssertionError: assert 'SomeClass' == 'DerivedClass'

# After executing the above DerivedClass.hello() no longer works correctly in this module or in any other
python python-3.x pytest python-unittest
1个回答
0
投票

欢迎使用Descriptor protocol

考虑此代码:

hello1 = SomeClass.hello
hello2 = DerivedClass.hello
print(hello1())  # 'SomeClass'
print(hello2())  # 'DerivedClass'

[hello1hello2都不同,即使它们都是从hello的相同定义中检索出来的。

这是因为普通函数在类内部定义classmethod都实现了描述符协议,每当从类或对象中检索值时都将使用该描述符协议。

SomeClass.hello(以及SomeClass().hello)将带有cls参数(如果不是self则为classmethod)的基础函数返回到从其检索的类(或实例)中。让我们检查一下:

print(SomeClass.hello)  # <bound method SomeClass.hello of <class '__main__.SomeClass'>>
print(DerivedClass.hello)  # <bound method SomeClass.hello of <class '__main__.DerivedClass'>>

如果要为hello保存并恢复SomeClass的原始值,则不能使用对象访问。让我们改用__dict__

hello = SomeClass.__dict__['hello']
print(hello)  # <classmethod at 0x12345678>
SomeClass.hello = lambda: 'replacement'
print(DerivedClass.hello())  # 'replacement'
SomeClass.hello = hello
print(DerivedClass.hello())  # 'DerivedClass'

(是的,当然,嘲笑是一种代码味道,所有代码仅出于解释目的。)

© www.soinside.com 2019 - 2024. All rights reserved.