如何使用基类的映射方法进行派生类?

How to use mapping method of base class for derived class?

提问人:briston82 提问时间:10/26/2023 更新时间:10/27/2023 访问量:55

问:

想象一下,我在 python 中有以下内容(实际语言并不重要,因为我在 php 等其他语言中也有问题。基类“CarModel”和派生类“TruckModel”,源自“CarModel”。

class CarModel:
    def __init__(self):
        self.__wheel_count: int = 0

    def set_wheel_count(self, value: int) -> None:
        self.__wheel_count = value

class TruckModel(CarModel):
    def __init__(self):
        super().__init__()
        self.__load_in_kg: int = 0

    def set_load_in_kg(self, value: int) -> None:
        self.__load_in_kg= value

如果我现在有一个映射类,它应该将例如 Dict 转换为我的模型,我该如何为我的派生类重用映射方法? 特别是如果我有一个包含大量 setter 方法的基类,我必须重复我不喜欢的代码(“dry”)。

class VehicleMapper:
    def map_car_dict_to_car_model(dict: Dict) -> CarModel:
        model: CarModel = CarModel()
        model.set_wheel_count(dict['wheelCount'])
        return model

    def map_truck_dict_to_truck_model(dict: Dict) -> TruckModel:
        model: TruckModel= TruckModel()
        model.set_load_in_kg(dict['loadInKg'])
 
        model.set_wheel_count(dict['wheelCount']) #  ??? How can I re-use the map-method for the base class here ???
        
        return model

我可以将映射方法移动到模型类,然后这将起作用。但是我被教导说,模型类应该只“保存”数据,不应该做任何事情。这就是为什么有映射器类,对吧?

Python 类型 orm 映射 派生类

评论


答:

1赞 RomanPerekhrest 10/26/2023 #1

更干净、更可靠的方法是使用单独的映射器/工厂。
而且它更合理,因为在您的情况下,您还需要从字典键到相应模型属性名称的可配置映射。

请考虑以下模式:

class CarModel:
    def __init__(self):
        self.__wheel_count: int = 0

    def wheel_count(self, value: int) -> None:
        self.__wheel_count = value

    wheel_count = property(None, wheel_count)

class TruckModel(CarModel):
    def __init__(self):
        super().__init__()
        self.__load_in_kg: int = 0

    def load_in_kg(self, value: int) -> None:
        self.__load_in_kg= value

    load_in_kg = property(None, load_in_kg)


class VehicleFactory:
    """Vehicle base factory"""
    __model__ = None
    __map_keys__ = None

    @classmethod
    def create(cls, data):
        model = cls.__model__()
        for k, attr in cls.__map_keys__.items():
            setattr(model, attr, data[k])
        return model

class CarFactory(VehicleFactory):
    __model__ = CarModel
    __map_keys__ = {'wheelCount': 'wheel_count'}

class TruckFactory(VehicleFactory):
    __model__ = TruckModel
    __map_keys__ = {'wheelCount': 'wheel_count',
                    'loadInKg': 'load_in_kg'}

用法:

car = CarFactory.create({'wheelCount': 4})
print(vars(car))    # {'_CarModel__wheel_count': 4}

truck = TruckFactory.create({'wheelCount': 4, 'loadInKg': 500})
print(vars(truck))  # {'_CarModel__wheel_count': 4, '_TruckModel__load_in_kg': 500}

评论

0赞 briston82 10/27/2023
谢谢你的回答罗曼!这是一个非常好的方法,我特别喜欢工厂内的映射部分。唯一的问题是,我倾向于避免使用魔术方法,例如setattr()等。问题在于,“在我看来”代码的可读性降低。在我的IDE中,我无法右键单击函数并要求“显示用法”或类似内容。所以我倾向于对所有东西进行硬编码。但我想这种方法没有解决方案,所以我会按照你的方式去做!再次感谢!
1赞 blhsing 10/27/2023 #2

一种更简洁的方法是使用装饰器函数来注释每个 setter 方法,使用映射到它的键,并将键到setter 的映射存储为类属性,以便可以重新使用 的键和 setter for ,并且在构建映射时不会重复键或 setter 的名称,从而更好地遵循 DRY 原则:CarModelTruckModel

class FromDictable:
    key_to_setter = {}

    @classmethod
    def bind_key(cls, key):
        def decorator(setter):
            cls.key_to_setter[key] = setter
        return decorator

    @classmethod
    def from_dict(cls, d: dict):
        instance = cls()
        for key, value in d.items():
            cls.key_to_setter[key](instance, value)
        return instance

bind_key = FromDictable.bind_key

class CarModel(FromDictable):
    def __init__(self):
        self.__wheel_count: int = 0

    @bind_key('wheelCount')
    def set_wheel_count(self, value: int) -> None:
        self.__wheel_count = value

class TruckModel(CarModel):
    def __init__(self):
        super().__init__()
        self.__load_in_kg: int = 0

    @bind_key('loadInKg')
    def set_load_in_kg(self, value: int) -> None:
        self.__load_in_kg= value

因此:

print(vars(CarModel.from_dict({'wheelCount': 4})))
print(vars(TruckModel.from_dict({'wheelCount': 6, 'loadInKg': 8000})))

输出:

{'_CarModel__wheel_count': 4}
{'_CarModel__wheel_count': 6, '_TruckModel__load_in_kg': 8000}

演示在这里