代码之家  ›  专栏  ›  技术社区  ›  Rahul

在运行时替换python Mixin类

  •  2
  • Rahul  · 技术社区  · 7 年前

    我有一个数据访问类,它从数据库中获取数据。它实现为一个mixin,即ReferenceData,我想用MockReferenceData类交换这个类,这样在单元测试中我就不必访问数据库了。但不起作用。

    *注意:我不能使用依赖注入。因为团队不想使用它,因为这将是非常大的代码更改。所以我希望在运行时替换mixin。

    class MockReferenceData(object):
      def dbName(self):
        return 'mock'
    
      def totalNumberOfSeats(self):
        return '10'
    
    class ReferenceData(object):
      def dbName(self):
        return 'real DB'
    
      def totalNumberOfSeats(self):
        return 'Fetch from DB'
    
    class Car(ReferenceData):
      def showNumberOfSeats(self):
        print self.totalNumberOfSeats()
    
    
    class Train(ReferenceData):
      def showNumberOfSeats(self):
        print self.totalNumberOfSeats()
    
    c = Car()
    c.showNumberOfSeats()
    t = Train()
    t.showNumberOfSeats()
    
    def extend_instance(obj, cls):
        """Apply mixins to a class instance after creation"""
        base_cls = obj.__class__
        base_cls_name = obj.__class__.__name__
        obj.__class__ = type(base_cls_name, (base_cls, cls),{})
    
    extend_instance(c, MockReferenceData)
    c.showNumberOfSeats() // output now should be 10
    

    Fetch from DB
    Fetch from DB
    Fetch from DB
    

    我曾经希望 extend\u实例 指向新模拟类输出的方法将是:

    Fetch from DB
    Fetch from DB
    10
    
    3 回复  |  直到 7 年前
        1
  •  1
  •   mata    7 年前

    如果你看看 MRO 班级的 Car 您可以看到:

    >>> Car.__mro__
    (<class '__main__.Car'>, <class '__main__.ReferenceData'>, <type 'object'>)
    

    因此首先要查找方法 汽车 ,然后 ReferenceData 最后 object .

    将此与新类的MRO进行比较(我使用 NewCar 为清晰起见,名称为):

    >>> type('NewCar', (Car, MockReferenceData), {}).__mro__
    (<class '__main__.NewCar'>, <class '__main__.Car'>, <class '__main__.ReferenceData'>, <class '__main__.MockReferenceData'>, <type 'object'>)
    

    其中包含 汽车 班这里的方法仍然是首先查找的 汽车 然后继续 参考数据 ,以便 totalNumberOfSeats 在上找到 参考数据 实施自 MockReferenceData 未使用。

    您可以做的是在MRO中插入模拟类 之前 这个 汽车

    >>> type('NewCar', (MockReferenceData, Car), {}).__mro__
    (<class '__main__.NewCar'>, <class '__main__.MockReferenceData'>, <class '__main__.Car'>, <class '__main__.ReferenceData'>, <type 'object'>)
    

    现在方法将首先在 模拟参考数据 类,如果它们不存在,则返回到以前的版本。所以这个 extend_instance 方法应适用于这种简单情况:

    def extend_instance(obj, cls):
        """Apply mixins to a class instance after creation"""
        obj.__class__ = type(obj.__class__.__name__, (cls, obj.__class__),{})
    
        2
  •  1
  •   Rahul    7 年前

    谢谢@mata,我也做了同样的事情,我改变了顺序,它成功了。

    def extend_instance(obj, cls):
        """Apply mixins to a class instance after creation"""
        base_cls = obj.__class__
        base_cls_name = obj.__class__.__name__
        obj.__class__ = type(base_cls_name, (cls, base_cls),{})
    
        3
  •  0
  •   deets    7 年前

    不要使用继承。在许多情况下,这是一个过度使用的抽象,这就是一个例子。通过使用依赖注入,您可以很容易地默认为实际的DB,但可以用模拟的DB替换后端。

    class MockReferenceData(object):
      def dbName(self):
        return 'mock'
    
      def totalNumberOfSeats(self):
        return '10'
    
    class ReferenceData(object):
      def dbName(self):
        return 'real DB'
    
      def totalNumberOfSeats(self):
        return 'Fetch from DB'
    
    class Car(object):
    
        def __init__(self, datasource_class=ReferenceData):
            self._datasource = datasource_class() # instead of creation here, you could also pass in an
    
        def showNumberOfSeats(self):
            print(self._datasource.totalNumberOfSeats())
    
    
    real_car = Car()
    real_car.showNumberOfSeats()
    
    mocked_car = Car(datasource_class=MockReferenceData)
    mocked_car.showNumberOfSeats()
    

    另外,请遵循PEP8在Python中的编码约定。您使用了“错误的”缩进深度2,这会给像我这样的开发人员带来编辑问题。方法名也是如此。