我正在尝试开发我的非ORM类的ORM版本,以便能够将对象存储在数据库中(并在可能的情况下将其取回)。
from ruamel.yaml import YAMLObject
class User(YAMLObject):
yaml_tag = u'user'
def __init__(self, name, age):
self.name = name
self.age = age
# Other useful methods
我现在想要实现的是一个类似的对象,其行为与Python世界中的
User
相似,但也可以用作ORM对象,因此能够将其存储在数据库中。我特意尝试的是:Base = declarative_base()
class SQLUser(Base, User):
id = Column(Integer, primary_key=True)
name = Column(String)
age = Column(Integer)
def __init__(self, name, age):
self.name = name
self.age = age
使用此类类层次结构运行示例在Python 2上会产生以下错误:
TypeError:调用元类基础时出错
元类冲突:派生类的元类必须是其所有基元元类的(非严格)子类
我相信这与
YAMLObject
元类有关...但是我需要它,因为我也希望能够将这些对象另存为YAML。对于我读到的有关此错误的信息,我可能应该使用从YAMLObject
元类和Base
继承的第三个元类,然后使用它来创建所需的类...class MetaMixinUser(type(User), type(Base)):
pass
class SQLUser(six.with_metaclass(MetaMixinUser)):
#[...]
不幸的是,这给出了另一个错误:
AttributeError:类型对象“ SQLUser”没有属性“ _decl_class_registry”
您能指出我的推理有问题的地方吗?
最佳答案
如果您很急:从ruamel.yaml
0.15.19开始,您可以使用一个语句来register classes,而不必将YAMLObject
子类化:
yaml = ruamel.yaml.YAML()
yaml.register_class(User)
YAMLObject
可以与PyYAML向后兼容,尽管它可能很方便,但出于以下三个原因,我不建议您真正使用它:它使您的类层次结构依赖于
YAMLObject
,正如您所注意到的,它可能会干扰其他依赖关系默认情况下,它使用不安全的
Loader
基于Python装饰器的解决方案将既方便又不那么麻烦。
子类化
YAMLObject
所做的唯一真实的事情是为该constructor
注册一个yaml_tag
和为子类注册一个representer
。如果您运行Python 2,所有示例均假定为
from __future__ import print_function
。如果具有以下内容,则基于子类别
YAMLObject
:import sys
import ruamel.yaml
from ruamel.std.pathlib import Path
yaml = ruamel.yaml.YAML(typ='unsafe')
class User(ruamel.yaml.YAMLObject):
yaml_tag = u'user'
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def to_yaml(cls, representer, node):
return representer.represent_scalar(cls.yaml_tag,
u'{.name}-{.age}'.format(node, node))
@classmethod
def from_yaml(cls, constructor, node):
# type: (Any, Any) -> Any
return User(*node.value.split('-'))
data = {'users': [User('Anthon', 18)]}
yaml.dump(data, sys.stdout)
print()
tmp_file = Path('tmp.yaml')
yaml.dump(data, tmp_file)
rd = yaml.load(tmp_file)
print(rd['users'][0].name, rd['users'][0].age)
这将使您:
users: [!<user> Anthon-18]
Anthon 18
通过执行以下操作,无需子类即可获得完全相同的结果:
import sys
import ruamel.yaml
from ruamel.std.pathlib import Path
yaml = ruamel.yaml.YAML(typ='safe')
class User(object):
yaml_tag = u'user'
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def to_yaml(cls, representer, node):
return representer.represent_scalar(cls.yaml_tag,
u'{.name}-{.age}'.format(node, node))
@classmethod
def from_yaml(cls, constructor, node):
# type: (Any, Any) -> Any
return User(*node.value.split('-'))
yaml.representer.add_representer(User, User.to_yaml)
yaml.constructor.add_constructor(User.yaml_tag, User.from_yaml)
data = {'users': [User('Anthon', 18)]}
yaml.dump(data, sys.stdout)
print()
tmp_file = Path('tmp.yaml')
yaml.dump(data, tmp_file)
rd = yaml.load(tmp_file)
print(rd['users'][0].name, rd['users'][0].age)
上面使用了
SafeLoader
(和SafeDumper
),这是朝着正确方向迈出的一步。但是,如果您有很多类,则在上面添加XXXX.add_YYY
行会很麻烦,因为这些条目几乎相同,但不完全相同。而且,它无法处理缺少to_yaml
和from_yaml
的类。为了解决上述问题,我建议您在文件
yaml_object
中创建装饰器myyaml.py
和帮助器类:import ruamel.yaml
yaml = ruamel.yaml.YAML(typ='safe')
class SafeYAMLObject(object):
def __init__(self, cls):
self._cls = cls
def to_yaml(self, representer, data):
return representer.represent_yaml_object(
self._cls.yaml_tag, data, self._cls,
flow_style=representer.default_flow_style)
def from_yaml(self, constructor, node):
return constructor.construct_yaml_object(node, self._cls)
def yaml_object(cls):
yaml.representer.add_representer(
cls, getattr(cls, 'to_yaml', SafeYAMLObject(cls).to_yaml))
yaml.constructor.add_constructor(
cls.yaml_tag, getattr(cls, 'from_yaml', SafeYAMLObject(cls).from_yaml))
return cls
拥有后,您可以执行以下操作:
import sys
from ruamel.std.pathlib import Path
from myyaml import yaml, yaml_object
@yaml_object
class User(object):
yaml_tag = u'user'
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def to_yaml(cls, representer, node):
return representer.represent_scalar(cls.yaml_tag,
u'{.name}-{.age}'.format(node, node))
@classmethod
def from_yaml(cls, constructor, node):
# type: (Any, Any) -> Any
return User(*node.value.split('-'))
data = {'users': [User('Anthon', 18)]}
yaml.dump(data, sys.stdout)
print()
tmp_file = Path('tmp.yaml')
yaml.dump(data, tmp_file)
rd = yaml.load(tmp_file)
print(rd['users'][0].name, rd['users'][0].age)
再次以相同的结果。如果删除
to_yaml
和from_yaml
方法,则最终值将相同,但YAML稍有不同:users:
- !<user> {age: 18, name: Anthon}
Anthon 18
我还不能测试这一点,但是在执行以下操作时,使用此装饰器而不是将
YAMLObject
子类化即可:class SQLUser(Base, User):
¹
免责声明:我是此答案中使用的
TypeError
软件包的作者。免责声明2:我并不是真正的18岁,但我确实遵循this专辑的主打歌中表达的Brian Adams的adagium