You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python print函数执行顺序异常:测试装饰器输出错位问题

问题原因

和GIL完全无关,核心是测试框架的输出捕获机制导致的顺序错乱:
比如你用的unittest这类框架,默认会捕获测试方法执行过程中所有stdout输出(包括装饰器finally块里的print内容),而框架自身的进度提示(如test_some_function ...)是直接输出到终端、不被捕获的。测试完成后,框架会先把捕获的输出追加到进度提示后面,再输出最终结果(如ok),就出现了你看到的顺序混乱。

解决方法

方法1:直接输出到stderr(最简单稳定)

测试框架一般不会捕获stderr的输出,把装饰器里的print改为输出到stderr,就能绕过捕获,让内容直接按代码顺序输出:

import sys
import datetime
import functools

TEST_INIT_START = datetime.datetime.now()

def timedisplay(func):
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        start = datetime.datetime.now()
        try:
            return func(self, *args, **kwargs)
        finally:
            # 所有输出指定到stderr
            print(file=sys.stderr)
            print(
                'Total time elapsed:',
                (datetime.datetime.now() - TEST_INIT_START).seconds,
                'seconds',
                file=sys.stderr
            )
            print(
                f'{func.__name__} test time elapsed:',
                (datetime.datetime.now() - start).seconds,
                'seconds',
                file=sys.stderr
            )
            # 去掉原来的Result: 输出,让框架自己输出结果(ok/fail)即可
    return wrapper

方法2:临时恢复原始stdout(绕过框架捕获)

在finally块中临时替换sys.stdout为终端原始输出流,输出完成后再恢复框架的捕获流:

import sys
import datetime
import functools

TEST_INIT_START = datetime.datetime.now()
original_stdout = sys.stdout

def timedisplay(func):
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        start = datetime.datetime.now()
        try:
            return func(self, *args, **kwargs)
        finally:
            # 临时切换到原始stdout,绕过捕获
            sys.stdout = original_stdout
            print()
            print(
                'Total time elapsed:',
                (datetime.datetime.now() - TEST_INIT_START).seconds,
                'seconds',
            )
            print(
                f'{func.__name__} test time elapsed:',
                (datetime.datetime.now() - start).seconds,
                'seconds',
            )
            print('Result: ', end='')
            # 恢复框架的捕获流(依赖unittest内部实现,可能随版本变化)
            sys.stdout = self._outcome.test_case._stdout
    return wrapper

方法3:使用日志模块输出

用logging模块替代print,日志输出默认不会被测试框架捕获,能保证顺序正确:

import logging
import datetime
import functools

logging.basicConfig(level=logging.INFO, format='%(message)s')
logger = logging.getLogger(__name__)

TEST_INIT_START = datetime.datetime.now()

def timedisplay(func):
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        start = datetime.datetime.now()
        try:
            result = func(self, *args, **kwargs)
            logger.info('')
            logger.info(f'Total time elapsed: {(datetime.datetime.now() - TEST_INIT_START).seconds} seconds')
            logger.info(f'{func.__name__} test time elapsed: {(datetime.datetime.now() - start).seconds} seconds')
            logger.info('Result: ok')
            return result
        except Exception:
            logger.info('')
            logger.info(f'Total time elapsed: {(datetime.datetime.now() - TEST_INIT_START).seconds} seconds')
            logger.info(f'{func.__name__} test time elapsed: {(datetime.datetime.now() - start).seconds} seconds')
            logger.info('Result: fail')
            raise
    return wrapper

内容的提问来源于stack exchange,提问作者Kevin K.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 13:54:53