Python3中包装类初始化参数不匹配报错排查与优化
环境类继承改造后的初始化错误问题
问题描述
维护机器学习项目时,新增继承自gym.Env的抽象父类Environment,并修改SushiGoEnv继承该父类后,运行代码触发如下错误:
TypeError: SushiGoEnv.__init__() takes from 1 to 3 positional arguments but 4 were given
错误原因
核心是Python类继承与抽象基类的参数校验冲突,具体拆解:
- 改造后的
SushiGoEnv的__init__仅接受self、verbose、manual三个参数(后两个带默认值) selfplay_wrapper中通过继承创建SelfPlayEnv时,由于Environment使用ABCMeta抽象元类,Python会在类创建阶段做严格的参数签名校验。而SelfPlayEnv的__init__调用父类初始化的逻辑,与SushiGoEnv本身的__init__参数签名不兼容,触发了参数数量不匹配的错误- 原代码中
SushiGoEnv直接继承gym.Env时无此问题,因为gym.Env未使用抽象元类,不会触发该层级的参数校验
更优的包装类实现方案
推荐使用组合而非继承的方式实现selfplay包装器,这是gym生态中环境包装的标准做法,能彻底避免继承带来的耦合与参数冲突:
优化后的selfplay_wrapper代码
def selfplay_wrapper(env_class): class SelfPlayEnv(gym.Wrapper): def __init__(self, opponent_type, verbose=False, manual=False): # 初始化原始环境实例 self.base_env = env_class(verbose=verbose, manual=manual) super().__init__(self.base_env) self.opponent_type = opponent_type self.opponent_models = load_all_models(self.base_env) self.best_model_name = get_best_model_name(self.base_env.name) def reset(self): obs = self.base_env.reset() self.setup_opponents() if self.base_env.current_player_num != self.agent_player_num: self.continue_game() return obs # 其他方法直接委托给原始环境 def step(self, action): return self.base_env.step(action) def render(self, mode='human'): return self.base_env.render(mode) return SelfPlayEnv
初始化代码(无需修改)
logger.info('\nSetting up the selfplay training environment opponents...') base_env = get_environment(args.env_name) env = selfplay_wrapper(base_env)(opponent_type=args.opponent_type, verbose=args.verbose) env.seed(workerseed)
方案优势
- 彻底避免继承冲突:通过组合方式包装原始环境,完全避开初始化参数、元类校验等问题
- 符合gym标准:继承
gym.Wrapper可自动获得gym环境的所有默认方法,无需手动实现 - 解耦性更强:包装器与原始环境耦合度极低,后续修改原始环境参数不会影响包装器
可选临时修复(不推荐长期使用)
若需快速修复,可调整SushiGoEnv的__init__参数,使其与父类兼容:
class SushiGoEnv(Environment): metadata = {'render.modes': ['human']} def __init__(self, verbose=False, manual=False, n_players=3, device='cuda', turns_taken=0): super(SushiGoEnv, self).__init__(n_players=n_players, device=device, turns_taken=turns_taken) self.name = 'sushigo' self.manual = manual self.verbose = verbose
同时修改SelfPlayEnv的初始化调用:
def __init__(self, opponent_type, verbose): super(SelfPlayEnv, self).__init__(verbose=verbose) # 其他代码不变
但此方式会增加参数冗余,长期来看不如组合方案简洁。
内容的提问来源于stack exchange,提问作者pbuchheit
相关产品推荐
相关产品推荐

