9

第1部分

我有一个设置,我有一组要模拟的类,我的想法是,在我想这样做的情况下,我将一个mock关键字参数传递给构造函数并__new__拦截它,而是传回一个模拟版本那个物体。

它看起来像这样(在@mgilsons 建议之后编辑了关键字查找):

class RealObject(object):
    def __new__(cls, *args, **kwargs):
        if kwargs.pop('mock', None):
            return MockRealObject()
        return super(RealObect, cls).__new__(cls, *args, **kwargs)

    def __init__(self, whatever = None):
        '''
        Constructor
        '''
        #stuff happens

然后我像这样调用构造函数:

ro = RealObject(mock = bool)

我在这里遇到的问题是,当我收到以下错误boolFalse

TypeError: __init__() got an unexpected keyword argument 'mock'

如果我将mock其作为关键字参数添加,则此方法有效,__init__但我要问的是是否可以避免这种情况。我什至mockkwargs dict.

也是一个关于设计的问题。有一个更好的方法吗?(当然!)我想尝试这样做,而不使用工厂或超类或任何东西。但是,我应该使用另一个关键字吗?__call__?

第 2 部分基于 jsbueno 的回答

所以我想将元类和__new__函数提取到一个单独的模块中。我这样做了:

class Mockable(object):

    def __new__(cls, *args, **kwargs):

        if kwargs.pop('mock', None):
            mock_cls = eval('{0}{1}'.format('Mock',cls.__name__))
            return super(mock_cls, mock_cls).__new__(mock_cls)

        return super(cls, cls).__new__(cls,*args, **kwargs)


class MockableMetaclass(type):

    def __call__(self, *args, **kwargs):
        obj = self.__new__(self, *args, **kwargs)
        if "mock" in kwargs:
            del kwargs["mock"]

        obj.__init__(*args, **kwargs)

        return obj

我已经在一个单独的模块中定义了类RealObjectMockRealObject. 我现在有两个问题:

  1. 如果MockableMetaclass并且Mockable不在与RealObject班级相同的模块中,evalNameError如果我提供mock = True.
  2. 如果mock = False代码将进入一个以令人印象深刻的RuntimeError: maximum recursion depth exceeded while calling a Python objec. 我猜这是因为RealObjects 的超类不再是object,而是Mockable

我该如何解决这些问题?我的方法不正确吗?我应该改为Mockable装饰师吗?我试过了,但这似乎不起作用,因为__new__一个实例似乎只是只读的。

4

2 回答 2

12

这是一份工作__metaclass__ :-)

负责在实例化 Python 新样式对象时调用的代码位于类元类的__new__方法中。(或语义上等价的)。__init____call__

换句话说 - 当你这样做时:

RealObject()——真正叫的是RealObject.__class__.__call__方法。由于没有显式声明元类,因此元类是type,它是type.__call__被调用的。

大多数处理元类的__new__方法都是处理方法的子类化——在创建类时自动执行操作。但是重写__call__我们可以在类被实例化时采取行动,而不是。

在这种情况下,只需在调用之前删除“mock”关键字参数(如果有)__init__

class MetaMock(type):
    def __call__(cls, *args, **kw):
       obj = cls.__new__(cls, *args, **kw)
       if "mock" in kw:
           del kw["mock"]
       obj.__init__(*args, **kw)
       return obj

class RealObject(object):
    __metaclass__ = MetaMock
    ...
于 2013-02-07T16:58:10.770 回答
1

子类非常重要,因为__new__总是将参数传递给构造函数调用__init__方法。如果您通过类装饰器添加子类作为 mixin,那么您可以拦截mock子类中的参数__init__

def mock_with(mock_cls):
    class MockMixin(object):
        def __new__(cls, *args, **kwargs):
            if kwargs.pop('mock'):
                return mock_cls()
            return super(MockMixin, cls).__new__(cls, *args, **kwargs)
        def __init__(self, *args, **kwargs):
            kwargs.pop('mock')
            super(MockMixin, self).__init__(*args, **kwargs)
    def decorator(real_cls):
        return type(real_cls.__name__, (MockMixin, real_cls), {})
    return decorator

class MockRealObject(object):
    pass

@mock_with(MockRealObject)
class RealObject(object):
    def __init__(self, whatever=None):
        pass

r = RealObject(mock=False)
assert isinstance(r, RealObject)
m = RealObject(mock=True)
assert isinstance(m, MockRealObject)

另一种方法是让子类__new__方法返回RealObject(cls, *args, **kwargs);在这种情况下,因为返回的对象不是子类的实例。但是在这种情况下,isinstance检查将失败。

于 2013-02-07T16:54:54.323 回答