【问题标题】:Python monkey patch private functionPython猴子补丁私有函数
【发布时间】:2012-12-12 08:49:45
【问题描述】:

我有一个带有函数的模块(称为a()),它调用在同一模块中定义的另一个函数(称为__b())。 __b() 是一个通过urllib2 与网站对话并获取一些数据的函数。现在我正在尝试测试a(),但当然不希望我的单元测试与公共互联网对话。因此,我在想如果我可以用一个返回预设数据的函数对__b() 进行修补,然后我可以为a() 编写测试。

更具体地说,我的模块看起来有点像:

def a():
    return __b("someval")

def __b(args):
    return something_complex_with_args

所以现在我想测试a(),但我需要修改__b。问题是 A)关于猴子补丁的绝大多数信息适用于类的方法,而不是模块中的函数,并且 B)我想要猴子补丁的函数是私有的。我愿意将__b 更改为非私有,如果它使该过程更可行,但宁愿不这样做。

建议?

编辑:就目前而言,测试类看起来像:

from unittest import TestCase

import mymodule

def newfn(args):
    return {"a" : "b"}

mymodule._b = newfn

class TestMyModule(TestCase):
    def test_basic(self):
        print(mymodule.a('somearg'))

当我运行它时,如果猴子补丁根本没有完成,我会看到输出,而不是看到 {'a': 'b'} 被打印出来。

【问题讨论】:

  • 一般来说,如果模块外的anything需要访问一个函数(包括测试),你可能不应该使用__name格式。改用_name 格式来表示它是一个内部函数,同时仍然使需要访问它的事物能够访问它。
  • 名称修改 (__x) 与 private 不同,除非您确切知道它是什么,否则您可能不应该使用它。 Python 没有数据隐藏功能。
  • @Lattyware:我知道它与私有不同,但它已尽可能接近。为什么会担心用双下划线命名它?
  • @AdamParkin 因为双下划线提供了名称修饰 - 名称被更改(查看文档了解为什么以及究竟发生了什么)。您不需要在 Python 中将内容设为私有。您的问题是一个非常清楚的例子,说明了为什么这是一个坏主意。 Python 不是 Java 或 C++,像这样写会导致错误的代码。
  • “to docs”是指 (docs.python.org/2/tutorial/classes.html#tut-private)?所以我仍然不明白为什么它通常很糟糕。也许将其移至聊天,因为它与我的问题不是特别相关?

标签: python monkeypatching


【解决方案1】:

如果您的模块被命名为 'foo',那么以下应该可以工作。

import foo

def patched_version():
    return 'Hello'

foo.__b = patched_version

print (foo.a())

foo.py 在哪里

def a():
    return __b()

def __b():
    return 'Goodbye'

【讨论】:

  • 不,原始版本的 __b() 仍然被调用(不管是 __b 还是 _b)。
  • 再看一下,你的答案是有效的,但是当我将打印更改为测试方法中时(就像我的问题一样),__b 的原始版本被调用。跨度>
  • 这是因为 unittest 的执行方式与“普通”Python 非常不同。尝试将foo.__b = patched_version 放入setUp 函数中进行测试。如果你使用的不是unittest,YMMV。
  • 我正在使用 unittest 并将其移至 setUp 并没有解决问题。
【解决方案2】:

我遇到了同样的问题,但最终找到了解决方案。问题是在 unittest.TestCase 类中使用猴子补丁时。这是一个很好的解决方案:

如果您使用的是 Python 2,则需要使用 easy_install 或其他方式安装“模拟”库 (http://www.voidspace.org.uk/python/mock/)。该库已与 Python 3 捆绑在一起。

代码如下:

from mock import patch

    class TestMyModule(TestCase):
        def test_basic(self):
            with patch('mymodule._b') as mock:
                mock.return_value={"a" : "b"} # put here what you want the mock function to return. You can make multiple tests varying these values.
                #keep the indentation. Determines the scope for the patch.
                print(mymodule.a('somearg'))

虽然与制作模拟函数相比,这种方式显然不太方便,我们可以模拟实际的子函数 _b(),它具有基于不同参数返回不同值的逻辑,但随后我们不必要地增加了更多机会的错误。在这种方法中,我们只是硬编码我们希望模拟函数返回的内容并测试我们要测试的实际函数,即 a()。

【讨论】:

  • 不,仍然没有为我工作。我得到:AttributeError: <module 'mymodule' from 'mymodule/__init__.py'> does not have the attribute '_b' 将“私有”函数添加到 init.py 使错误消失,但随后修补无效(仍在调用原始版本)。
【解决方案3】:

我似乎无法重现您的问题(我已经稍微调整了您的示例,因为它根本没有按原样运行)。你是不是打错了什么东西(比如mymodule._b 而不是mymodule.__b)?

mymodule.py

def a(x):
    return __b("someval")

def __b(args):
    return "complex_thingy: {}".format(args)

mytest.py

from unittest import TestCase

import mymodule

def newfn(args):
    return {"a" : "b"}

mymodule.__b = newfn

class TestMyModule(TestCase):
    def test_basic(self):
        print(mymodule.a('somearg'))

输出

C:\TEMP>python -m unittest mytest
{'a': 'b'}
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

C:\TEMP>

似乎工作正常。


或在单元测试之外:

mytest2.py

import mymodule

def newfn(args):
    return {"a" : "b"}

mymodule.__b = newfn

print(mymodule.a('somearg'))

输出

C:\TEMP>python mytest2.py
{'a': 'b'}

C:\TEMP>

【讨论】:

    猜你喜欢
    • 2012-12-18
    • 1970-01-01
    • 2011-04-15
    • 1970-01-01
    • 2014-01-24
    • 2015-09-03
    • 2017-10-05
    • 2016-09-01
    • 2012-09-16
    相关资源
    最近更新 更多