【问题标题】:Find Out If a Function has been Called查明是否调用了函数
【发布时间】:2012-03-27 01:42:37
【问题描述】:

我正在用 Python 编程,我想知道是否可以测试我的代码中是否调用了函数

def example():
    pass
example()
#Pseudocode:
if example.has_been_called:
   print("foo bar")

我该怎么做?

【问题讨论】:

  • 我写了一个counting decorator,它会告诉你一个函数被调用了多少次。如果您愿意,您可以根据自己的需要进行调整。
  • 您希望如何处理这些信息?

标签: python python-3.x


【解决方案1】:

如果函数知道自己的名字就可以了,可以使用函数属性:

def example():
    example.has_been_called = True
    pass
example.has_been_called = False


example()

#Actual Code!:
if example.has_been_called:
   print("foo bar")

你也可以使用装饰器来设置属性:

import functools

def trackcalls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        wrapper.has_been_called = True
        return func(*args, **kwargs)
    wrapper.has_been_called = False
    return wrapper

@trackcalls
def example():
    pass


example()

#Actual Code!:
if example.has_been_called:
   print("foo bar")

【讨论】:

  • 知道函数可以获取属性很有趣,因为 Python 中的一切都是对象。函数是“函数”类的对象。并且你可以为实例分配一个属性,因为你不必在 Python 中声明变量,所以你可以在运行时分配它们。
  • 请注意,使用函数属性会导致 mypy 检查失败
  • @DanielBraun mypy 检查会以什么方式失败?
【解决方案2】:

使用标准库中的unittest.mock.Mock 的最小示例:

from unittest.mock import Mock

def example():
    pass

example_mock = Mock(side_effect=example)
example_mock()
#Pseudocode:
if example_mock.called:
   print("foo bar")

运行脚本后的控制台输出:

foo bar

这种方法很好,因为它不需要您修改 example 函数本身,如果您想在某些单元测试代码中执行此检查,而不修改源代码本身(EG 存储has_been_called 属性,或将函数包装在装饰器中)。

说明

the documentation for the unittest.mock.Mock class 中所述,Mock() 构造函数的side_effect 参数指定“每当调用 Mock 时调用的函数”。

Mock.called 属性指定“一个布尔值,表示是否已调用模拟对象”。

Mock 类还有其他您可能会觉得有用的属性,例如:

call_count: 一个整数,告诉你模拟对象被调用了多少次

call_args: 这要么是 None (如果尚未调用模拟),要么是上次调用模拟的参数

call_args_list:这是一个按顺序对模拟对象进行的所有调用的列表(所以列表的长度就是它被调用的次数)。在进行任何调用之前,它是一个空列表

Mock 类还具有方便的方法,可以根据调用 Mock 对象的次数以及调用它的参数来生成断言语句,例如:

assert_called_once_with(*args, **kwargs): 断言模拟只被调用了一次,并且该调用带有指定的参数

【讨论】:

    【解决方案3】:

    我们可以使用 mock.Mock

    from unittest import mock
    
    
    def check_called(func):
        return mock.Mock(side_effect=func)
    
    
    @check_called
    def summator(a, b):
        print(a + b)
    
    
    summator(1, 3)
    summator.assert_called()
    assert summator.called == True
    assert summator.call_count > 0
    
    summator.assert_called_with(1, 3)
    
    summator.assert_called_with(1, 5)  # error
    # AssertionError: Expected call: mock(1, 5)
    # Actual call: mock(1, 3)
    

    【讨论】:

      【解决方案4】:

      Memoization 函数自 1960 年代就已存在。在 python 中,您可以将它们用作 example() 函数的装饰器。

      标准的记忆功能看起来像这样:

      def memoize(func):
          memo = {}
          def wrapper(*args):
              if not args in memo:
                  memo[args] = func(*args)
              return memo[args]
          return wrapper 
      

      然后你像这样装饰你的函数:

      @memoize
      def example():
          pass
      

      在python3.2中,可以使用functools.lru_cache代替memoziation函数。

      import functools
      
      @functools.lru_cache(maxsize=None)
      def example():
           pass
      

      【讨论】:

        【解决方案5】:

        这是一个装饰器,它将使用 colorama 监视您的所有功能,并返回一个不错的输出。

        try:
            import colorama
        except ImportError:
            class StdClass: pass
            def passer(*args, **kwargs): pass
            colorama = StdClass()
            colorama.init = passer
            colorama.Fore = StdClass()
            colorama.Fore.RED = colorama.Fore.GREEN = ''
        
        def check_for_use(show=False):
            if show:
                try:
                    check_for_use.functions
                except AttributeError:
                    return
                no_error = True
                for function in check_for_use.functions.keys():
                    if check_for_use.functions[function][0] is False:
                        print(colorama.Fore.RED + 'The function {!r} hasn\'t been called. Defined in "{}" '.format(function, check_for_use.functions[function][1].__code__.co_filename))
                        no_error = False
                if no_error:
                    print(colorama.Fore.GREEN + 'Great! All your checked function are being called!')
                return check_for_use.functions
            try:
                check_for_use.functions
            except AttributeError:
                check_for_use.functions = {}
                if colorama:
                    colorama.init(autoreset=True)
        
            def add(function):
                check_for_use.functions[function.__name__] = [False, function]
                def func(*args, **kwargs):
                    check_for_use.functions[function.__name__] = [True, function]
                    function(*args, **kwargs)
                return func
            return add
        
        @check_for_use()
        def hello():
            print('Hello world!')
        
        @check_for_use()
        def bonjour(nb):
            print('Bonjour tout le monde!')
        
        
        # hello(); bonjour(0)
        
        hello()
        
        
        check_for_use(True) # outputs the following
        
        输出:
        Hello world!
        The function 'bonjour' hasn't been called. Defined in "path_to_file.py" 
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-11
          • 2013-08-22
          • 1970-01-01
          • 2011-04-15
          • 2021-12-08
          • 1970-01-01
          相关资源
          最近更新 更多