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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 08:25:19