为何导入Python类时会自动实例化该类?
问题背景
测试套件执行完成后始终无法退出,断点调试发现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) # 测试逻辑(已通过) # 测试完成后套件无法退出
原因分析
导入时实例化类的根源
Python中,函数(包括类的构造方法__init__)的默认参数是在函数定义阶段(模块导入时)就完成计算和初始化的,而非函数调用阶段。因此Controller.__init__中subscriber=DataListener(...)这个默认参数,会在controller.py模块被导入(即test_controller.py执行from controller import Controller)时直接执行DataListener(),触发类的实例化。测试套件无法退出的原因
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

