棉花糖模式和类继承

unl*_*fox 8 python marshmallow

我是第一次使用 Marshmallow,不幸的是在互联网上找不到答案。

有两个类,一个类继承另一个类。两者都应该是可序列化和可反序列化的。反序列化后,它们应该可以再次作为 Python 对象使用。因此我使用post_load装饰器。但这似乎会引起问题。在下面的最小工作示例中,我得到以下异常:type object argument after ** must be a mapping, not Bicycle

现在可以查找该属性amount_of_tires,并在必要时传递数据。但这感觉不是正确的解决方案。

有解决这个问题的最佳实践吗?

from marshmallow import Schema, fields
from marshmallow.decorators import post_load


class Vehicle:
    def __init__(self, weight):
        self.weight = weight


class VehicleSchema(Schema):
    weight = fields.Float()

    @post_load
    def make_vehicle(self, data, **kwargs) -> Vehicle:
        return Vehicle(**data)


class Bicycle(Vehicle):
    def __init__(self, weight, amount_of_tires):
        Vehicle.__init__(self, weight=weight)
        self.amount_of_tires = amount_of_tires


class BicycleSchema(VehicleSchema):
    amount_of_tires = fields.Integer()

    @post_load
    def make_bicycle(self, data, **kwargs) -> Bicycle:
        return Bicycle(**data)


my_bicycle = Bicycle(weight=10, amount_of_tires=2)


schema = BicycleSchema()
json_string = schema.dumps(my_bicycle)

deserialised_my_bicycle = schema.loads(json_string)
print(deserialised_my_bicycle)


Run Code Online (Sandbox Code Playgroud)

Jac*_*ith 7

make_vehiclemake_bicycle被注册为post_load钩子,因此它们会被一个接一个地调用。当make_vehicle被调用时,make_bicycle已经被调用,所以type(data) == Bicycle,因此您会看到错误。

我推荐以下解决方案:

class VehicleSchema(Schema):
    model_class = Vehicle

    weight = fields.Float()

    @post_load
    def make_vehicle(self, data, **kwargs) -> Vehicle:
        return type(self).model_class(**data)

class BicycleSchema(VehicleSchema):
    model_class = Bicycle
    amount_of_tires = fields.Integer()
Run Code Online (Sandbox Code Playgroud)

要使用此解决方案获得更准确的类型提示 - 可以在基类上使用Generic和(请参阅https://docs.python.org/3/library/typing.html#typing.Generic)。TypeVarVehicleSchema

  • @unlimitedfox 很好发现我已经更新了。在此示例中,“make_vehicle”和返回类型“Vehicle”是合适的,因为继承意味着“Bicycle”是“Vehicle”。正如我所指出的,通过使用泛型,类型提示可以更加具体 - 但我认为这会在某种程度上分散对答案的注意力。 (3认同)