Python 通过camel进行Yaml序列化:在decorator中使用基类加载/转储和访问类型(self)
TL;DR:如何在成员函数的装饰器中使用Python 通过camel进行Yaml序列化:在decorator中使用基类加载/转储和访问类型(self),python,serialization,types,self,pyyaml,Python,Serialization,Types,Self,Pyyaml,TL;DR:如何在成员函数的装饰器中使用类型(self) 我想对派生类进行序列化,并在Python的基类中共享一些序列化逻辑。 由于泡菜< /代码>和简单 YAML似乎无法可靠地处理这个问题,于是我绊倒了 CAMEL ,我认为这是一个非常巧妙的解决方案。 考虑两个极其简化的类B和A,其中B继承自A。我希望能够在我的主函数中序列化B,如下所示: from camel import Camel, CamelRegistry serializable_types = CamelRegistry()
类型(self)
我想对派生类进行序列化,并在Python的基类中共享一些序列化逻辑。
由于<代码>泡菜< /代码>和简单<代码> YAML似乎无法可靠地处理这个问题,于是我绊倒了<代码> CAMEL <代码>,我认为这是一个非常巧妙的解决方案。
考虑两个极其简化的类B
和A
,其中B
继承自A
。我希望能够在我的主函数中序列化B
,如下所示:
from camel import Camel, CamelRegistry
serializable_types = CamelRegistry()
# ... define A and B with dump and load functions ...
if __name__ == "__main__":
serialization_interface = Camel([serializable_types])
b = B(x=3, y=4)
s = serialization_interface.dump(b)
print(s)
# VERSION 3 - dump and load functions are member functions
# ERROR: name 'A' is not defined
class A:
def __init__(self, x):
self._x = x
@serializable_types.dumper(A, 'object_A', version=None)
def dump(a):
return {'x': a._x}
@serializable_types.loader('object_A', version=None)
def load(data, version):
return A(data.x)
class B(A):
def __init__(self, x, y):
super().__init__(x)
self._y = y
@serializable_types.dumper(B, 'object_B', version=None)
def dump(b):
b_data = super().dump(b)
b_data.update({'y': b._y})
return b_data
@serializable_types.loader('object_B', version=None)
def load(data, version):
return B(data.x)
我提出了两个可行的解决方案:
版本1:转储和加载在类外的独立函数中完成。问题:不是很优雅,函数dumpA
不能自动用于dumpB
中的继承类,函数命名更麻烦,函数范围超出必要范围
# VERSION 1 - dump and load in external functions
class A:
def __init__(self, x):
self._x = x
@serializable_types.dumper(A, 'object_A', version=None)
def dumpA(a):
return {'x': a._x}
@serializable_types.loader('object_A', version=None)
def loadA(data, version):
return A(data.x)
class B(A):
def __init__(self, x, y):
super().__init__(x)
self._y = y
@serializable_types.dumper(B, 'object_B', version=None)
def dumpB(b):
b_data = dumpA(b)
b_data.update({'y': b._y})
return b_data
@serializable_types.loader('object_B', version=None)
def loadB(data, version):
return B(data.x)
版本2:加载和卸载功能直接在构造函数中定义。函数在子类中仍然不可用:/
# VERSION 2 - dump and load functions defined in constructor
class A:
def __init__(self, x):
self._x = x
@serializable_types.dumper(A, 'object_A', version=None)
def dump(a):
a.to_dict()
@serializable_types.loader('object_A', version=None)
def load(data, version):
return A(data.x)
def to_dict(self):
return {'x': self._x}
class B(A):
def __init__(self, x, y):
super().__init__(x)
self._y = y
@serializable_types.dumper(B, 'object_B', version=None)
def dump(b):
b_data = b.to_dict()
return b_data
@serializable_types.loader('object_B', version=None)
def load(data, version):
return B(data.x)
def to_dict(self):
b_data = super().to_dict()
b_data.update({'y': b._y})
return b_data
我希望实现如下实现:
from camel import Camel, CamelRegistry
serializable_types = CamelRegistry()
# ... define A and B with dump and load functions ...
if __name__ == "__main__":
serialization_interface = Camel([serializable_types])
b = B(x=3, y=4)
s = serialization_interface.dump(b)
print(s)
# VERSION 3 - dump and load functions are member functions
# ERROR: name 'A' is not defined
class A:
def __init__(self, x):
self._x = x
@serializable_types.dumper(A, 'object_A', version=None)
def dump(a):
return {'x': a._x}
@serializable_types.loader('object_A', version=None)
def load(data, version):
return A(data.x)
class B(A):
def __init__(self, x, y):
super().__init__(x)
self._y = y
@serializable_types.dumper(B, 'object_B', version=None)
def dump(b):
b_data = super().dump(b)
b_data.update({'y': b._y})
return b_data
@serializable_types.loader('object_B', version=None)
def load(data, version):
return B(data.x)
这将不起作用,因为在dump
功能的定义中,A
和B
未定义。然而,从软件设计的角度来看,我认为这是代码最少的最干净的解决方案。
有没有办法让a
和B
的类型定义在decorator中工作?还是有人用不同的方式解决了这个问题?
我遇到了一些问题,但没有找到一种直接的方法将其应用到我的用例中。您的版本3将无法工作,因为正如您可能注意到的,在 调用装饰程序时,
A
尚未定义
如果你愿意写信给你的装饰师
在将@
语法糖添加到Python之前:
def some_decorator(fun):
return fun
@some_decorator
def xyz():
pass
,即:
def some_decorator(fun):
return fun
def xyz():
pass
some_decorator(xyz)
那么这应该马上就清楚了
您的版本2延迟了加载程序和转储程序的注册 例程,直到在某些数据库中创建了
A
和B
的实例
在您可以加载之前,以其他方式加载。那可能有用
如果您创建了这两个类的实例,然后进行了转储,然后进行了加载,
从一个程序中。但是如果您只创建B
并希望转储
如果已注册,则A
的函数尚未注册,并且A.dump()
已注册
无法使用的。不管怎样,如果程序同时转储和加载数据,
从一些持久性存储进行加载更为常见
首先,然后做倾倒,并在装载过程中进行登记
还不会发生。所以你需要一些额外的
所有类的注册机制以及至少
每个类都有一个实例。可能不是你想要的
在版本1中,在
dumpB
中无法轻松找到dumpA
,
虽然应该可以查看
serializable_类型
并查找B
的父类,但这是
不平凡、丑陋,还有一个更好的方法,就是最小化dumpB
(和
dumpA
)到函数中,这些函数返回返回的值是B
(分别为A
),适当命名为dump
:
from camel import CamelRegistry, Camel
serializable_types = CamelRegistry()
# VERSION 1 - dump and load in external functions
class A:
def __init__(self, x):
self._x = x
def dump(self):
return {'x': self._x}
@serializable_types.dumper(A, 'object_A', version=None)
def dumpA(a):
return a.dump()
@serializable_types.loader('object_A', version=None)
def loadA(data, version):
return A(data.x)
class B(A):
def __init__(self, x, y):
super().__init__(x)
self._y = y
def dump(self):
b_data = A.dump(self)
b_data.update({'y': b._y})
return b_data
@serializable_types.dumper(B, 'object_B', version=None)
def dumpB(b):
return b.dump()
@serializable_types.loader('object_B', version=None)
def loadB(data, version):
return B(data.x)
if __name__ == "__main__":
serialization_interface = Camel([serializable_types])
b = B(x=3, y=4)
s = serialization_interface.dump(b)
print(s)
其中:
!object_B
x: 3
y: 4
!object_B
x: 3
y: 4
====================
b.x: 42, b.y: 196
这是因为在调用dumpB
时,您有一个类型为B
(否则您无法获取其属性),以及
班级B
了解班级A
请注意,执行返回B(data.x)
在您的任何版本中都不起作用
asB
的\uuuu init\uuuu
需要两个参数
我觉得上面的内容很难理解
您指出“simple
yaml
似乎无法处理
这是可靠的”。我不知道为什么这会是真的,但确实有
关于YAML的很多误解
我建议您看看ruamel.yaml
(免责声明:我是该软件包的作者)。
它需要为转储和加载注册类,使用预定义的方法名
装载和倾倒(从_yaml
分别至_yaml
),以及“登记处”电话
这些方法包括类信息。因此,没有必要推迟定义
直到您在版本2中构造一个对象为止
您可以显式注册一个类,也可以将该类装饰为
只要装饰师可用(即,一旦您有了YAML
实例)。由于B
来自A
,因此您只需提供
到_yaml
和从_yaml
在A
中,并且可以重复使用转储
方法
根据上一个示例:
import sys
class A:
yaml_tag = u'!object_A'
def __init__(self, x):
self._x = x
@classmethod
def to_yaml(cls, representer, node):
return representer.represent_mapping(cls.yaml_tag, cls.dump(node))
@classmethod
def from_yaml(cls, constructor, node):
instance = cls.__new__(cls)
yield instance
state = ruamel.yaml.constructor.SafeConstructor.construct_mapping(
constructor, node, deep=True)
instance.__dict__.update(state)
def dump(self):
return {'x': self._x}
import ruamel.yaml # delayed import so A cannot be decorated
yaml = ruamel.yaml.YAML()
@yaml.register_class
class B(A):
yaml_tag = u'!object_B'
def __init__(self, x, y):
super().__init__(x)
self._y = y
def dump(self):
b_data = A.dump(self)
b_data.update({'y': b._y})
return b_data
yaml.register_class(A)
# B not registered, because it is already decorated
b = B(x=3, y=4)
yaml.dump(b, sys.stdout)
print('=' * 20)
b = yaml.load("""\
!object_B
x: 42
y: 196
""")
print('b.x: {.x}, b.y: {.y}'.format(b, b))
其中:
!object_B
x: 3
y: 4
!object_B
x: 3
y: 4
====================
b.x: 42, b.y: 196
上述代码中的屈服
是处理以下情况所必需的
具有(间接)循环引用,并且,
显然,在创建对象时并非所有参数都可用
创造
例如,一个YAML 1.2参考
YAML文档以--
开头,在这里实际被调用
A.
而不是文档开始标记,这是有充分理由的。而且,…
,这个
文档结束标记,只能后跟指令或--
,
然而,规范明确指出,可以在其后面添加注释
还有一些简单的文档。您试图解决的问题有点不清楚。在Python中可以转储/加载嵌套类,其中Y在类X中定义(尽管在PyYAML中没有定义自己的转储程序/加载程序)。然后您会问“如何在成员函数的装饰器中使用
type(self)
”