Python 通过camel进行Yaml序列化:在decorator中使用基类加载/转储和访问类型(self)

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()

TL;DR:如何在成员函数的装饰器中使用
类型(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)
在您的任何版本中都不起作用 as
B
\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)