如何使用safe_load反序列化PyYAML对象?

10 投票
3 回答
17455 浏览
提问于 2025-04-15 21:32

有一个这样的代码片段:

import yaml
class User(object):
    def __init__(self, name, surname):
       self.name= name
       self.surname= surname

user = User('spam', 'eggs')
serialized_user = yaml.dump(user)
#Network
deserialized_user = yaml.load(serialized_user)
print "name: %s, sname: %s" % (deserialized_user.name, deserialized_user.surname)

Yaml 文档提到,从不可信的来源接收到的数据,使用yaml.load来处理是不安全的;那么,我应该如何修改我的代码片段或类,来使用safe_load方法呢?
这可能吗?

3 个回答

2

如果你有很多标签,但又不想为每一个标签都创建对象,或者你不在乎返回的具体类型,只关心如何通过点来访问这些标签,你可以用下面的代码来处理所有未定义的标签:

import yaml

class Blob(object):
    def update(self, kw):
        for k in kw:
            setattr(self, k, kw[k])

from yaml.constructor import SafeConstructor

def my_construct_undefined(self, node):
    data = Blob()
    yield data
    value = self.construct_mapping(node)
    data.update(value)

SafeConstructor.add_constructor(None, my_construct_undefined)


class User(object):
    def __init__(self, name, surname):
        self.name= name
        self.surname= surname

user = User('spam', 'eggs')
serialized_user = yaml.dump(user)
#Network
deserialized_user = yaml.safe_load(serialized_user)
print "name: %s, sname: %s" % (deserialized_user.name, deserialized_user.surname)

你可能会好奇,为什么 my_construct_undefined 中间有一个 yield:这个设计让你可以在创建对象的时候,先单独实例化这个对象,再去创建它的子对象。一旦这个对象存在了,如果它有一个锚点,就可以通过这个锚点来引用它,以及它的子对象(或者子对象的子对象)。实际的机制是,先创建这个对象,然后对它执行 next(x) 来完成后续的操作。

27

还有另一种方法。根据PyYaml的文档:

一个Python对象可以被标记为安全的,这样就可以通过yaml.safe_load来识别。要做到这一点,你需要让它继承自yaml.YAMLObject [...],并明确设置它的类属性yaml_loader为yaml.SafeLoader。

你还需要设置yaml_tag属性才能让它正常工作。

YAMLObject使用了一些元类的魔法,使得这个对象可以被加载。需要注意的是,如果你这样做,这些对象只能通过安全加载器来加载,而不能用普通的yaml.load()。

下面是一个工作示例:

import yaml

class User(yaml.YAMLObject):
    yaml_loader = yaml.SafeLoader
    yaml_tag = u'!User'

    def __init__(self, name, surname):
       self.name= name
       self.surname= surname

user = User('spam', 'eggs')
serialized_user = yaml.dump(user)

#Network

deserialized_user = yaml.safe_load(serialized_user)
print "name: %s, sname: %s" % (deserialized_user.name, deserialized_user.surname)

这个方法的优点是比较简单;缺点是它只适用于safe_load,并且会让你的类里多出一些与序列化相关的属性和元类,显得有些杂乱。

13

看起来,safe_load这个东西,按定义来说,是不允许你反序列化你自己定义的类的。如果你想要安全的话,我会这样做:

import yaml
class User(object):
    def __init__(self, name, surname):
       self.name= name
       self.surname= surname

    def yaml(self):
       return yaml.dump(self.__dict__)

    @staticmethod
    def load(data):
       values = yaml.safe_load(data)
       return User(values["name"], values["surname"])

user = User('spam', 'eggs')
serialized_user = user.yaml()
print "serialized_user:  %s" % serialized_user.strip()

#Network
deserialized_user = User.load(serialized_user)
print "name: %s, sname: %s" % (deserialized_user.name, deserialized_user.surname)

这样做的好处是,你可以完全掌控你的类是怎么被序列化和反序列化的。这意味着你不会从网络上接收到随机的可执行代码并执行它。坏处就是,你也得完全掌控你的类是怎么被序列化和反序列化的。这意味着你需要做更多的工作。;-)

撰写回答