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

为何导入Python类时会自动实例化该类?

PyTest套件无法退出及类导入时实例化的原因分析与解决

问题背景

测试套件执行完成后始终无法退出,断点调试发现DataListener.__init__会在test_controller.py执行from controller import Controller语句时被调用,疑惑为何导入模块中的类时会触发类的实例化。

相关代码

data_listener.py

from threading import Event, Thread

class DataListener:

    def __init__(self, port, host='localhost'):
        self._stop_monitor = Event()
        self._monitor = Thread(target=self.check_last_message)
        self._monitor.start()

    def __del__(self):
        self.stop()

    def stop(self):
        self._stop_monitor.set()
        self._monitor.join()

    def check_last_message(self):
        while not self._stop_monitor.is_set():
            # 心跳检查逻辑
            pass

controller.py

from data_listener import DataListener

DEFAULT_SUBSCRIBER_PORT = 1234  # 假设的默认端口
DEFAULT_PUBLISHER_PORT = 5678  # 假设的默认端口

class JsonPublisher:
    def __init__(self, port):
        pass

class Controller:
    def __init__(
        self,
        subscriber=DataListener(port=DEFAULT_SUBSCRIBER_PORT),
        publisher=JsonPublisher(DEFAULT_PUBLISHER_PORT)):

        pass

test_controller.py

from controller import Controller

def test_controller_work_loop():
    # 准备测试环境
    controller = Controller(subscriber=None, publisher=None)

    # 测试逻辑(已通过)
    # 测试完成后套件无法退出

原因分析

  1. 导入时实例化类的根源
    Python中,函数(包括类的构造方法__init__)的默认参数是在函数定义阶段(模块导入时)就完成计算和初始化的,而非函数调用阶段。因此Controller.__init__中subscriber=DataListener(...)这个默认参数,会在controller.py模块被导入(即test_controller.py执行from controller import Controller)时直接执行DataListener(),触发类的实例化。

  2. 测试套件无法退出的原因
    DataListener实例化时启动了一个后台线程_monitor,Python默认创建的线程是非守护线程——解释器会等待所有非守护线程执行完毕才会退出。即使测试中创建Controller时传入了subscriber=None,但模块导入阶段创建的那个默认参数DataListener实例依然存在,其后台线程持续运行,导致测试套件无法正常退出。

解决方案

方案1:避免默认参数直接实例化类

将默认参数设为None,在__init__内部按需创建实例,确保实例化逻辑只在类被调用时执行:

class Controller:
    def __init__(
        self,
        subscriber=None,
        publisher=None):
        if subscriber is None:
            subscriber = DataListener(port=DEFAULT_SUBSCRIBER_PORT)
        if publisher is None:
            publisher = JsonPublisher(DEFAULT_PUBLISHER_PORT)
        # 原业务逻辑

方案2:将后台线程设为守护线程

在创建_monitor线程时添加daemon=True参数,这样主线程退出时,守护线程会自动终止,避免残留线程阻塞退出:

self._monitor = Thread(target=self.check_last_message, daemon=True)

建议同时使用两种方案,既避免不必要的预实例化,又能防止线程残留导致的退出问题。

内容的提问来源于stack exchange,提问作者Jon Cage

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 15:26:21