Comment désérialiser un objet avec PyYAML en utilisant safe_load?
-
26-09-2019 - |
Question
Avoir un extrait comme ceci:
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 docs dit que ce n'est pas sûr d'appeler yaml.load avec toutes les données reçues à partir d'une source non fiable; donc, que dois-je modifier mon extrait \ classe à utiliser safe_load méthode
Est-il possible?
La solution
Il semble que safe_load, par définition, ne vous permet pas désérialiser vos propres classes. Si vous voulez qu'il soit sûr, je ferais quelque chose comme ceci:
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)
L'avantage ici est que vous avez un contrôle absolu sur la façon dont votre classe est (de) sérialisé. Cela signifie que vous n'obtenir un code exécutable au hasard sur le réseau et l'exécuter. L'inconvénient est que vous avez un contrôle absolu sur la façon dont votre classe est (de) sérialisé. Cela signifie que vous devez faire beaucoup plus de travail. ; -)
Autres conseils
Une autre façon existe. Des PyYAML docs:
Un objet python peut être marqué comme sûr et ainsi être reconnu par yaml.safe_load. Pour ce faire, dériver de yaml.YAMLObject [...] et définir explicitement sa propriété de classe yaml_loader à yaml.SafeLoader.
Vous devez également définir la propriété yaml_tag pour le faire fonctionner.
YAMLObject fait un peu de magie métaclasse pour faire le chargeable objet. Notez que si vous faites cela, les objets ne seront chargeable par le chargeur sûr, pas avec yaml.load régulière ().
Exemple de travail:
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)
L'avantage de celui-ci est qu'il est prety facile à faire; les inconvénients sont que cela fonctionne uniquement avec safe_load et votre classe avec encombre les attributs liés sérialisation et métaclasse.
Si vous avez beaucoup de balises et ne veulent pas créer des objets pour tous, ou si vous ne se soucient pas du type réel est revenu, seulement un accès en pointillé, vous attrapez tous les tags non définis avec le code suivant:
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)
Dans le cas où vous vous demandez pourquoi le my_construct_undefined
a un yield
au milieu: qui permet d'instancier l'objet séparément de la création de ses enfants. Une fois que l'objet existe, il peut être appelé dans le cas où il a un point d'ancrage et des enfants (ou leurs enfants) une référence. Le mechanisme réelle pour créer d'abord l'objet crée, fait alors un next(x)
sur elle pour le finaliser.