YAML构造器标签闭包冲突:多类添加后标签实例化异常
闭包导致YAML构造器标签冲突问题解决
问题描述
编写了一个辅助函数用于将对象构造器包装为YAML标签,但出现构造器冲突:添加!B标签后,访问!A标签返回的却是B类的实例,而非预期的A类实例。
原代码
import yaml from yaml.constructor import ConstructorError def add_init_constuctors(*klasses, loader=yaml.SafeLoader): for klass in klasses: name = klass.__name__ def constructor(loader, node): try: fields = loader.construct_mapping(node) except ConstructorError: fields = {} return klass(**fields) yaml.add_constructor( f"!{name}", constructor, Loader=loader, ) print(f"added !{name}") print(yaml.load(f"!{name}", Loader=loader)) print( [ (klass.__name__, yaml.load(f"!{klass.__name__}", Loader=loader)) for klass in klasses ] ) class A(dict): def __repr__(self): return "A()" class B(dict): def __repr__(self): return "B()" if __name__ == "__main__": add_init_constuctors(A, B)
运行结果
added !A A() added !B B() [('A', B()), ('B', B())]
问题原因
这是Python闭包的典型陷阱:循环内定义的闭包函数constructor捕获的是循环变量klass的引用,而非循环迭代时的当前值。当循环结束后,klass变量最终指向最后一个遍历的类B,因此所有构造器函数在实际调用时,都会使用B类来创建实例。
解决方案
方法1:利用默认参数绑定当前值
修改闭包函数的定义,将klass作为默认参数传入,这样每次循环都会把当前的klass值绑定到函数的默认参数中:
def add_init_constuctors(*klasses, loader=yaml.SafeLoader): for klass in klasses: name = klass.__name__ # 将klass作为默认参数,绑定当前循环的实例 def constructor(loader, node, klass=klass): try: fields = loader.construct_mapping(node) except ConstructorError: fields = {} return klass(**fields) yaml.add_constructor( f"!{name}", constructor, Loader=loader, ) print(f"added !{name}") print(yaml.load(f"!{name}", Loader=loader)) print( [ (klass.__name__, yaml.load(f"!{klass.__name__}", Loader=loader)) for klass in klasses ] )
方法2:使用工厂函数封装构造器
创建一个工厂函数,接收目标类作为参数,返回绑定了该类的构造器函数,确保每个构造器都关联正确的类:
import yaml from yaml.constructor import ConstructorError # 工厂函数,返回绑定了目标类的构造器 def make_constructor(target_klass): def constructor(loader, node): try: fields = loader.construct_mapping(node) except ConstructorError: fields = {} return target_klass(**fields) return constructor def add_init_constuctors(*klasses, loader=yaml.SafeLoader): for klass in klasses: name = klass.__name__ # 通过工厂函数获取绑定当前klass的构造器 constructor = make_constructor(klass) yaml.add_constructor( f"!{name}", constructor, Loader=loader, ) print(f"added !{name}") print(yaml.load(f"!{name}", Loader=loader)) print( [ (klass.__name__, yaml.load(f"!{klass.__name__}", Loader=loader)) for klass in klasses ] ) class A(dict): def __repr__(self): return "A()" class B(dict): def __repr__(self): return "B()" if __name__ == "__main__": add_init_constuctors(A, B)
修改后运行结果
added !A A() added !B B() [('A', A()), ('B', B())]
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

