在 运行 时间替换 python Mixin class

Replace a python Mixin class at run time

我有一个数据访问 class 可以从数据库中获取数据。它是作为一个 mixin 实现的,即 ReferenceData 我想用 MockReferenceData class 交换这个 class 所以在单元测试中我不必访问数据库。但是没有用。

*注意:我不能使用依赖注入。由于团队不想使用它,因为考虑到这将是非常大的代码更改。所以我希望在运行时替换 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_instance 方法指向新的模拟 class 输出将是:

Fetch from DB
Fetch from DB
10

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

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。您使用 "wrong" 缩进深度 2,导致像我这样的开发人员出现编辑问题。方法名称也是如此。

如果您查看 class CarMRO,您可以看到:

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

因此首先在 Car 上查找方法,然后在 ReferenceData 上查找,最后在 object 上查找。

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

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

这包含 Car class 的 MRO。这里的方法仍然首先在 Car 上查找,然后在 ReferenceData 上查找,因此当在 ReferenceData 上找到 totalNumberOfSeats 时,不使用 MockReferenceData 的实现。

你可以做的是将你的模拟 class 插入 MRO before the Car class:

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

现在将首先在 MockReferenceData class 中查找方法,如果它们不存在,则会回退到以前的版本。所以这个 extend_instance 方法应该适用于这个简单的案例:

def extend_instance(obj, cls):
    """Apply mixins to a class instance after creation"""
    obj.__class__ = type(obj.__class__.__name__, (cls, obj.__class__),{})

谢谢@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),{})