pythonpython-3.xmetaprogrammingpython-unittest.mock

What is the role of `Base` class used in the built-in module `unittest.mock`?


While having deep dive into how the built-in unittest.mock was designed, I run into these lines in the official source code of mock.py:

class Base(object):
    _mock_return_value = DEFAULT
    _mock_side_effect = None
    def __init__(self, /, *args, **kwargs):
        pass



class NonCallableMock(Base):
    ...
    # and later in the init
    def _init__ (...):
        ...
        _safe_super(NonCallableMock, self).__init__(
            spec, wraps, name, spec_set, parent,
            _spec_state
        )

What is the role of Base class besides providing common class attributes _mock_return_value and _mock_side_effect as it has just an empty __init__ ?

And why NonCallableMock has to call _safe_super(NonCallableMock, self).__init__(), which I believe is exactly just the empty __init__ method of the Base class ?

Thanks a lot for your answers in advance.

I tried to understand the code but couldn't see the rationale behind the design.


Solution

  • It's for multiple inheritance, which unittest.mock uses for classes like class MagicMock(MagicMixin, Mock):.

    Despite the name, super doesn't mean "call the superclass method". Instead, it finds the next method implementation in a type's method resolution order, which might not come from a parent of the current class when multiple inheritance is involved. When you don't know what method super will call, you have to be a lot more careful about what arguments you pass to that method.

    Most of the classes in this class hierarchy forward arguments like spec and wraps to the next __init__ implementation when they call _safe_super(...).__init__(...). Even if none of their ancestors need those arguments, a sibling implementation could still need them. If all of the classes were to forward these arguments, then object.__init__ would eventually receive those arguments, and object.__init__ would throw an exception.

    Some class has to handle the job of specifically not passing those arguments to object.__init__, and that class has to sit at the root of the class hierarchy, right below object itself.

    Thus, this Base class: a class for all the other classes in this multiple inheritance hierarchy to inherit from, existing almost solely for the purpose of preventing object.__init__ from complaining.