在 运行 时间替换 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 Car
的 MRO,您可以看到:
>>> 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),{})
我有一个数据访问 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 Car
的 MRO,您可以看到:
>>> 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),{})