如何使用safe_load反序列化PyYAML对象?
有一个这样的代码片段:
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 个回答
如果你有很多标签,但又不想为每一个标签都创建对象,或者你不在乎返回的具体类型,只关心如何通过点来访问这些标签,你可以用下面的代码来处理所有未定义的标签:
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)
来完成后续的操作。
还有另一种方法。根据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,并且会让你的类里多出一些与序列化相关的属性和元类,显得有些杂乱。
看起来,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)
这样做的好处是,你可以完全掌控你的类是怎么被序列化和反序列化的。这意味着你不会从网络上接收到随机的可执行代码并执行它。坏处就是,你也得完全掌控你的类是怎么被序列化和反序列化的。这意味着你需要做更多的工作。;-)