通过 camel 进行 Yaml 序列化:使用 base class load/dump 并在装饰器中访问 type(self)
Yaml serialization through camel: using base class load/dump and accessing type(self) in decorator
TL;DR: 如何在成员函数的装饰器中使用type(self)
?
我想对派生的 classes 进行序列化,并在 Python 中共享基础 class 中的一些序列化逻辑。
由于 pickle
和简单的 yaml
似乎无法可靠地处理这个问题,然后我偶然发现了 camel
,我认为这是解决问题 see this link 的一个非常巧妙的解决方案。
考虑两个极其简化的 classes 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)
我想出了两个可行的解决方案:
版本 1:转储和加载是在 class 之外的独立函数中完成的。问题:不是很优雅,函数dumpA
不能自动用于继承dumpB
中的class,函数命名比较麻烦,函数范围比必要的大
# 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: 加载和转储函数直接在构造函数中定义。函数在 subclass :/
中仍然不可用
# 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
我想实现如下所示的实现:
# 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
的类型定义在装饰器中工作?或者有没有人以不同的方式解决了这个问题?
我遇到了 但看不到将它应用到我的用例的直接方法。
您的版本 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
的实例在某些情况下被创建
除了在加载之前加载之外。那可能有用
如果您创建了两个 classes 的实例,然后转储,然后加载,
从一个程序中。但是如果你只创建 B
并想转储
它,那么 A
的功能还没有注册并且 A.dump()
是
无法使用。无论如何,如果一个程序同时转储和加载数据,
从一些持久存储中加载更为常见
首先,然后进行倾销,并在加载注册时
还没有发生。所以你需要一些额外的
所有 classes 的注册机制和至少创建
每个 classes 一个实例。可能不是你想要的。
在版本 1 中,您无法轻松找到 dumpA
,而在 dumpB
中,
尽管应该可以查看的内部结构
serializable_types
并找到 B
的父级 class,然而这是
非平凡的,丑陋的,并且有一个更好的方法通过最小化 dumpB
(和
dumpA
) 转化为 return 值 returned 某些方法 B
的函数
(resp. 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
之所以可行,是因为在调用 dumpB
时,您已经拥有类型 B
的实例
(否则你无法获得它的属性),以及
class B
了解 class A
.
请注意,return B(data.x)
不适用于您的任何版本
因为 B
的 __init__
需要两个参数。
我觉得上面的内容相当难读。
您表示“简单yaml
似乎无法处理
这可靠”。我不知道为什么这是真的,但有
很多关于 YAML 的误解¹
我建议你看看 ruamel.yaml
(免责声明:我是那个包的作者)。
它需要为转储和加载注册 classes,使用预定义的方法名称
用于加载和转储(from_yaml
resp. to_yaml
),以及 "registration office" 调用
这些方法包括 class 信息。所以没有必要推迟定义
这些方法,直到您像在版本 2 中那样构造一个对象。
您可以显式注册 class 或将 class 修饰为
一旦装饰器可用(即一旦你有了 YAML
实例)。由于 B
继承自 A
,您只需提供
to_yaml
和 from_yaml
in A
并且可以重复使用 dump
方法
来自前面的示例:
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
====================
b.x: 42, b.y: 196
上面代码中的yield
是处理实例所必需的
对自己有(间接)循环引用,为此,
显然,并非所有参数在对象时都可用
创作。
¹ 例如一个 YAML 1.2 参考
states
YAML 文档以 ---
开头,实际调用的地方
一种
directives-end-marker
而不是 document-start-marker 有充分的理由。而那个...
,
文档结束标记,后面只能跟指令或
---
,
而规范清楚地表明它后面可以有注释
以及裸文件。
TL;DR: 如何在成员函数的装饰器中使用type(self)
?
我想对派生的 classes 进行序列化,并在 Python 中共享基础 class 中的一些序列化逻辑。
由于 pickle
和简单的 yaml
似乎无法可靠地处理这个问题,然后我偶然发现了 camel
,我认为这是解决问题 see this link 的一个非常巧妙的解决方案。
考虑两个极其简化的 classes 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)
我想出了两个可行的解决方案:
版本 1:转储和加载是在 class 之外的独立函数中完成的。问题:不是很优雅,函数dumpA
不能自动用于继承dumpB
中的class,函数命名比较麻烦,函数范围比必要的大
# 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: 加载和转储函数直接在构造函数中定义。函数在 subclass :/
中仍然不可用# 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
我想实现如下所示的实现:
# 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
的类型定义在装饰器中工作?或者有没有人以不同的方式解决了这个问题?
我遇到了
您的版本 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
的实例在某些情况下被创建
除了在加载之前加载之外。那可能有用
如果您创建了两个 classes 的实例,然后转储,然后加载,
从一个程序中。但是如果你只创建 B
并想转储
它,那么 A
的功能还没有注册并且 A.dump()
是
无法使用。无论如何,如果一个程序同时转储和加载数据,
从一些持久存储中加载更为常见
首先,然后进行倾销,并在加载注册时
还没有发生。所以你需要一些额外的
所有 classes 的注册机制和至少创建
每个 classes 一个实例。可能不是你想要的。
在版本 1 中,您无法轻松找到 dumpA
,而在 dumpB
中,
尽管应该可以查看的内部结构
serializable_types
并找到 B
的父级 class,然而这是
非平凡的,丑陋的,并且有一个更好的方法通过最小化 dumpB
(和
dumpA
) 转化为 return 值 returned 某些方法 B
的函数
(resp. 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
之所以可行,是因为在调用 dumpB
时,您已经拥有类型 B
的实例
(否则你无法获得它的属性),以及
class B
了解 class A
.
请注意,return B(data.x)
不适用于您的任何版本
因为 B
的 __init__
需要两个参数。
我觉得上面的内容相当难读。
您表示“简单yaml
似乎无法处理
这可靠”。我不知道为什么这是真的,但有
很多关于 YAML 的误解¹
我建议你看看 ruamel.yaml
(免责声明:我是那个包的作者)。
它需要为转储和加载注册 classes,使用预定义的方法名称
用于加载和转储(from_yaml
resp. to_yaml
),以及 "registration office" 调用
这些方法包括 class 信息。所以没有必要推迟定义
这些方法,直到您像在版本 2 中那样构造一个对象。
您可以显式注册 class 或将 class 修饰为
一旦装饰器可用(即一旦你有了 YAML
实例)。由于 B
继承自 A
,您只需提供
to_yaml
和 from_yaml
in A
并且可以重复使用 dump
方法
来自前面的示例:
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
====================
b.x: 42, b.y: 196
上面代码中的yield
是处理实例所必需的
对自己有(间接)循环引用,为此,
显然,并非所有参数在对象时都可用
创作。
¹ 例如一个 YAML 1.2 参考
states
YAML 文档以 ---
开头,实际调用的地方
一种
directives-end-marker
而不是 document-start-marker 有充分的理由。而那个...
,
文档结束标记,后面只能跟指令或
---
,
而规范清楚地表明它后面可以有注释
以及裸文件。