【问题标题】:Python assign a function to another functionPython将一个函数分配给另一个函数
【发布时间】:2013-05-09 10:13:47
【问题描述】:

我正在尝试将一个函数分配给另一个函数,分配的左侧可以作为字符串提供给我。例如我正在寻找的方法的主体是

def change_function_defintion(name_of_function = 'module1.function1'
       , function_object):
    # Can I do eval(name_of_function) = function_object ? will that work?
    pass

问题:

  1. 如何实现这一目标?显然,如果我调用上述方法,然后调用 module.function1,我希望新函数能够被拾取。
  2. 我在单元测试的上下文中执行此操作,即模拟几个函数,运行测试,然后基本上“取消模拟”它们。上述方法有什么问题吗?

【问题讨论】:

  • 像类一样,函数也是一等对象。您可以动态导入模块,然后将函数作为属性检索。
  • 请注意,这绝对不同于“如何动态加载 Python 类”问题。我已经明确要求使用 eval 方法进行任何改进/问题,并且非常正确地指出了 python 的 MOCK 框架,它默认包含在 python 3.3 中
  • 模拟没有什么特别之处。您面临的问题是通过指定导入路径的字符串导入和引用一个对象(在这种情况下是一个函数,但这与引用一个类没有什么不同)。另一个问题为您回答了该特定问题。 mock 库在这方面做了完全相同的事情(参见 source code)。

标签: python python-2.7


【解决方案1】:

我认为使用像Mock 这样的模拟库会更好。使用patch,您可以在上下文管理器或函数的范围内更改函数的行为,然后让它恢复正常。例如:

from mock import patch

with patch('module1.function1') as function1:
    function1.side_effect = function_object
    # Do stuff

如果在with 块内调用function1,它将被function_object 替换。

类似地,在函数内打补丁:

@patch('module1.function1')
def my_test(function1):
    function1.side_effect = function_object
    # Do stuff

【讨论】:

  • 这就是我最终要做的。谢谢!!
【解决方案2】:

模拟存在一些问题,如果可能,您可以考虑使用不同的测试方法:

【讨论】:

    【解决方案3】:

    我的做法:

    import importlib
    
    def change_function_defintion(name_of_function = 'module1.function1'
       , function_object):
       my_mod = importlib.import_module('module1')
       setattr(my_mod, function1, function_object)
    

    现在是更长的咆哮:

    这种方法可能会奏效,如果 module1 已经导入本地命名空间,例如,您可以执行以下操作:

    >>> a = eval('str')
    >>> a
    <type 'str'>
    >>> a(123)
    '123'
    

    在模拟单元测试的上下文中,可能有更好的方法。

    您可以在这里查看: http://pycheesecake.org/wiki/PythonTestingToolsTaxonomy#MockTestingTools 用于一些库,它们可以让您在单元测试中对模拟对象进行更多控制。

    编辑:

    你可以这样做,动态导入模块:

    >>> import importlib
    >>> my_mod = importlib.import_module('mymodule1')
    

    然后,您可以访问模块内部的可用函数,或者通过 eval/getattr 获取它们:

    my_function = getattr(my_mod,'somefunction')
    

    当然,如果您想将该功能换成其他功能:

    my_mod.functionName = newFunction
    

    【讨论】:

    • 但是它要求module1已经被导入到本地命名空间中。
    【解决方案4】:

    我认为以下内容可以满足您的需求(但您可能需要更健壮的解析):

    def set_named_fun(funname, fun) :
        import sys
        modname, funname =  funname.rsplit('.')
        mod = sys.modules[modname]
        setattr(mod, funname, fun)
    

    这里的假设是:

    • 拥有该功能的模块已经导入,并且
    • 您需要将目标函数称为完全限定名称

    这两个假设有点紧张。一定有很多情况你可以简单地做到:

    import legend
    legend.monkey = lambda : "great sage, equal of heaven"
    

    【讨论】:

      【解决方案5】:

      Python 函数装饰器

      首先,您所说的概念是函数装饰器的概念。通过将函数装饰器放置在函数定义开始之前的行(符号@),将函数装饰器应用于函数定义。它是一种修改函数行为或操作函数组合的工具。这是一个例子

      class entryExit(object):
      
          def __init__(self, f):
              self.f = f
      
          def __call__(self):
              print "Entering", self.f.__name__
              self.f()
              print "Exited", self.f.__name__
      
      @entryExit # decorator
      def func1(): # decorated function
          print "inside func1()"
      
      @entryExit
      def func2():
          print "inside func2()"
      

      我跑了

      func1()
      func2()
      

      我明白了

      Entering func1
      inside func1()
      Exited func1
      Entering func2
      inside func2()
      Exited func2
      

      Python unittest.mock.patch()

      patch 充当函数装饰器、类装饰器或上下文 经理。在函数体或 with 语句内部,目标 用新对象修补。当函数/with语句退出时 补丁已撤消。

      Patch 允许您修改 with 语句中的函数行为。

      这是一个示例,其中 patch() 用作带有 with 语句的上下文管理器。

      >>> with patch.object(ProductionClass, 'method', return_value=None) 
        as mock_method:
      ...     thing = ProductionClass()
      ...     thing.method(1, 2, 3)
      ...
      >>> mock_method.assert_called_once_with(1, 2, 3)
      

      【讨论】:

        【解决方案6】:

        可以使用“getattr”通过函数的字符串名称获取函数(一个函数就是一个对象)。然后您可以更改名称并在新的命名调用中调用/调用其他东西(原始命名函数)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-12-18
          • 2019-07-25
          • 2012-08-02
          • 1970-01-01
          • 1970-01-01
          • 2011-04-30
          • 2017-10-20
          相关资源
          最近更新 更多